xref: /haiku/src/add-ons/kernel/file_systems/btrfs/Inode.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
1 /*
2  * Copyright 2017, Chế Vũ Gia Hy, cvghy116@gmail.com.
3  * Copyright 2011, Jérôme Duval, korli@users.berlios.de.
4  * Copyright 2008-2014, Axel Dörfler, axeld@pinc-software.de.
5  * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de.
6  * This file may be used under the terms of the MIT License.
7  */
8 
9 
10 #include "Inode.h"
11 #include "CachedBlock.h"
12 #include "CRCTable.h"
13 #include "Utility.h"
14 
15 
16 #undef ASSERT
17 //#define TRACE_BTRFS
18 #ifdef TRACE_BTRFS
19 #	define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x)
20 #	define ASSERT(x) { if (!(x)) kernel_debugger("btrfs: assert failed: " #x "\n"); }
21 #else
22 #	define TRACE(x...) ;
23 #	define ASSERT(x) ;
24 #endif
25 #define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x)
26 
27 
28 Inode::Inode(Volume* volume, ino_t id)
29 	:
30 	fVolume(volume),
31 	fID(id),
32 	fCache(NULL),
33 	fMap(NULL)
34 {
35 	rw_lock_init(&fLock, "btrfs inode");
36 
37 	fInitStatus = UpdateNodeFromDisk();
38 	if (fInitStatus == B_OK) {
39 		if (!IsDirectory() && !IsSymLink()) {
40 			fCache = file_cache_create(fVolume->ID(), ID(), Size());
41 			fMap = file_map_create(fVolume->ID(), ID(), Size());
42 		}
43 	}
44 }
45 
46 
47 Inode::Inode(Volume* volume, ino_t id, const btrfs_inode& item)
48 	:
49 	fVolume(volume),
50 	fID(id),
51 	fCache(NULL),
52 	fMap(NULL),
53 	fInitStatus(B_OK),
54 	fNode(item)
55 {
56 	if (!IsDirectory() && !IsSymLink()) {
57 		fCache = file_cache_create(fVolume->ID(), ID(), Size());
58 		fMap = file_map_create(fVolume->ID(), ID(), Size());
59 	}
60 }
61 
62 
63 Inode::Inode(Volume* volume)
64 	:
65 	fVolume(volume),
66 	fID(0),
67 	fCache(NULL),
68 	fMap(NULL),
69 	fInitStatus(B_NO_INIT)
70 {
71 	rw_lock_init(&fLock, "btrfs inode");
72 }
73 
74 
75 Inode::~Inode()
76 {
77 	TRACE("Inode destructor\n");
78 	file_cache_delete(FileCache());
79 	file_map_delete(Map());
80 	TRACE("Inode destructor: Done\n");
81 }
82 
83 
84 status_t
85 Inode::InitCheck()
86 {
87 	return fInitStatus;
88 }
89 
90 
91 status_t
92 Inode::UpdateNodeFromDisk()
93 {
94 	btrfs_key search_key;
95 	search_key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
96 	search_key.SetObjectID(fID);
97 	search_key.SetOffset(0);
98 	BTree::Path path(fVolume->FSTree());
99 
100 	btrfs_inode* node;
101 	if (fVolume->FSTree()->FindExact(&path, search_key, (void**)&node)
102 		!= B_OK) {
103 		ERROR("Inode::UpdateNodeFromDisk(): Couldn't find inode %"
104 			B_PRIdINO "\n", fID);
105 		return B_ENTRY_NOT_FOUND;
106 	}
107 
108 	memcpy(&fNode, node, sizeof(btrfs_inode));
109 	free(node);
110 	return B_OK;
111 }
112 
113 
114 /*
115  * Create new Inode object with inode_item
116  */
117 Inode*
118 Inode::Create(Transaction& transaction, ino_t id, Inode* parent, int32 mode,
119 	uint64 size, uint64 flags)
120 {
121 	TRACE("Inode::Create() id % " B_PRIu64 " mode %" B_PRId32 " flags %"
122 		B_PRIu64"\n", id, flags, mode);
123 
124 	Volume* volume = parent->GetVolume();
125 	uint64 nbytes = size;	// allocated size
126 	if (size > volume->MaxInlineSize())
127 		nbytes = (size / volume->SectorSize() + 1) * volume->SectorSize();
128 
129 	btrfs_inode inode;
130 
131 	inode.generation = B_HOST_TO_LENDIAN_INT64(transaction.SystemID());
132 	inode.transaction_id = B_HOST_TO_LENDIAN_INT64(transaction.SystemID());
133 	inode.size = B_HOST_TO_LENDIAN_INT64(size);
134 	inode.nbytes = B_HOST_TO_LENDIAN_INT64(nbytes);
135 	inode.blockgroup = 0;	// normal inode only
136 	inode.num_links = B_HOST_TO_LENDIAN_INT32(1);
137 	inode.uid = B_HOST_TO_LENDIAN_INT32(geteuid());
138 	inode.gid = B_HOST_TO_LENDIAN_INT32(parent ? parent->GroupID() : getegid());
139 	inode.mode = B_HOST_TO_LENDIAN_INT32(mode);;
140 	inode.rdev = 0;	// normal file only
141 	inode.flags = B_HOST_TO_LENDIAN_INT64(flags);
142 	inode.sequence = 0;	// incremented each time mtime value is changed
143 
144 	uint64 now = real_time_clock_usecs();
145 	struct timespec timespec;
146 	timespec.tv_sec = now / 1000000;
147 	timespec.tv_nsec = (now % 1000000) * 1000;
148 	btrfs_inode::SetTime(inode.access_time, timespec);
149 	btrfs_inode::SetTime(inode.creation_time, timespec);
150 	btrfs_inode::SetTime(inode.change_time, timespec);
151 	btrfs_inode::SetTime(inode.modification_time, timespec);
152 
153 	return new Inode(volume, id, inode);
154 }
155 
156 
157 status_t
158 Inode::CheckPermissions(int accessMode) const
159 {
160 	// you never have write access to a read-only volume
161 	if ((accessMode & W_OK) != 0 && fVolume->IsReadOnly())
162 		return B_READ_ONLY_DEVICE;
163 
164 	return check_access_permissions(accessMode, Mode(), (gid_t)fNode.GroupID(),
165 		(uid_t)fNode.UserID());
166 }
167 
168 
169 status_t
170 Inode::FindBlock(off_t pos, off_t& physical, off_t* _length)
171 {
172 	btrfs_key search_key;
173 	search_key.SetType(BTRFS_KEY_TYPE_EXTENT_DATA);
174 	search_key.SetObjectID(fID);
175 	search_key.SetOffset(pos + 1);
176 	BTree::Path path(fVolume->FSTree());
177 
178 	btrfs_extent_data* extent_data;
179 	status_t status = fVolume->FSTree()->FindPrevious(&path, search_key,
180 		(void**)&extent_data);
181 	if (status != B_OK) {
182 		ERROR("Inode::FindBlock(): Couldn't find extent_data 0x%" B_PRIx32
183 			"\n", status);
184 		return status;
185 	}
186 
187 	TRACE("Inode::FindBlock(%" B_PRIdINO ") key.Offset() %" B_PRId64 "\n",
188 		ID(), search_key.Offset());
189 
190 	off_t diff = pos - search_key.Offset();
191 	off_t logical = 0;
192 	if (extent_data->Type() == BTRFS_EXTENT_DATA_REGULAR)
193 		logical = diff + extent_data->disk_offset;
194 	else
195 		panic("unknown extent type; %d\n", extent_data->Type());
196 	status = fVolume->FindBlock(logical, physical);
197 	if (_length != NULL)
198 		*_length = extent_data->Size() - diff;
199 	TRACE("Inode::FindBlock(%" B_PRIdINO ") %" B_PRIdOFF " physical %"
200 		B_PRIdOFF "\n", ID(), pos, physical);
201 
202 	free(extent_data);
203 	return status;
204 }
205 
206 
207 status_t
208 Inode::ReadAt(off_t pos, uint8* buffer, size_t* _length)
209 {
210 	size_t length = *_length;
211 
212 	// set/check boundaries for pos/length
213 	if (pos < 0) {
214 		ERROR("inode %" B_PRIdINO ": ReadAt failed(pos %" B_PRIdOFF
215 			", length %lu)\n", ID(), pos, length);
216 		return B_BAD_VALUE;
217 	}
218 
219 	if (pos >= Size() || length == 0) {
220 		TRACE("inode %" B_PRIdINO ": ReadAt 0 (pos %" B_PRIdOFF
221 			", length %lu)\n", ID(), pos, length);
222 		*_length = 0;
223 		return B_NO_ERROR;
224 	}
225 
226 	// the file cache doesn't seem to like non block aligned file offset
227 	// so we avoid the file cache for inline extents
228 	btrfs_key search_key;
229 	search_key.SetType(BTRFS_KEY_TYPE_EXTENT_DATA);
230 	search_key.SetObjectID(fID);
231 	search_key.SetOffset(pos + 1);
232 	BTree::Path path(fVolume->FSTree());
233 
234 	uint32 item_size;
235 	btrfs_extent_data* extent_data;
236 	status_t status = fVolume->FSTree()->FindPrevious(&path, search_key,
237 		(void**)&extent_data, &item_size);
238 	if (status != B_OK) {
239 		ERROR("Inode::FindBlock(): Couldn't find extent_data 0x%" B_PRIx32
240 			"\n", status);
241 		return status;
242 	}
243 	MemoryDeleter deleter(extent_data);
244 
245 
246 	uint8 compression = extent_data->Compression();
247 	if (FileCache() != NULL
248 		&& extent_data->Type() == BTRFS_EXTENT_DATA_REGULAR) {
249 		TRACE("inode %" B_PRIdINO ": ReadAt cache (pos %" B_PRIdOFF ", length %lu)\n",
250 			ID(), pos, length);
251 		if (compression == BTRFS_EXTENT_COMPRESS_NONE)
252 			return file_cache_read(FileCache(), NULL, pos, buffer, _length);
253 		else if (compression == BTRFS_EXTENT_COMPRESS_ZLIB)
254 			panic("zlib isn't unsupported for regular extent\n");
255 		else
256 			panic("unknown extent compression; %d\n", compression);
257 		return B_BAD_DATA;
258 	}
259 
260 	TRACE("Inode::ReadAt(%" B_PRIdINO ") key.Offset() %" B_PRId64 "\n", ID(),
261 		search_key.Offset());
262 
263 	off_t diff = pos - search_key.Offset();
264 	if (extent_data->Type() != BTRFS_EXTENT_DATA_INLINE) {
265 		panic("unknown extent type; %d\n", extent_data->Type());
266 		return B_BAD_DATA;
267 	}
268 
269 	*_length = min_c(extent_data->Size() - diff, *_length);
270 	if (compression == BTRFS_EXTENT_COMPRESS_NONE)
271 		memcpy(buffer, extent_data->inline_data, *_length);
272 	else if (compression == BTRFS_EXTENT_COMPRESS_ZLIB) {
273 		char in[2048];
274 		z_stream zStream = {
275 			(Bytef*)in,		// next in
276 			sizeof(in),		// avail in
277 			0,				// total in
278 			NULL,			// next out
279 			0,				// avail out
280 			0,				// total out
281 			0,				// msg
282 			0,				// state
283 			Z_NULL,			// zalloc
284 			Z_NULL,			// zfree
285 			Z_NULL,			// opaque
286 			0,				// data type
287 			0,				// adler
288 			0,				// reserved
289 		};
290 
291 		int status;
292 		ssize_t offset = 0;
293 		uint32 inline_size = item_size - 13;
294 		bool headerRead = false;
295 
296 		TRACE("Inode::ReadAt(%" B_PRIdINO ") diff %" B_PRIdOFF " size %"
297 			B_PRIuSIZE "\n", ID(), diff, item_size);
298 
299 		do {
300 			ssize_t bytesRead = min_c(sizeof(in), inline_size - offset);
301 			memcpy(in, extent_data->inline_data + offset, bytesRead);
302 			if (bytesRead != (ssize_t)sizeof(in)) {
303 				if (bytesRead <= 0) {
304 					status = Z_STREAM_ERROR;
305 					break;
306 				}
307 			}
308 
309 			zStream.avail_in = bytesRead;
310 			zStream.next_in = (Bytef*)in;
311 
312 			if (!headerRead) {
313 				headerRead = true;
314 
315 				zStream.avail_out = length;
316 				zStream.next_out = (Bytef*)buffer;
317 
318 				status = inflateInit2(&zStream, 15);
319 				if (status != Z_OK) {
320 					return B_ERROR;
321 				}
322 			}
323 
324 			status = inflate(&zStream, Z_SYNC_FLUSH);
325 			offset += bytesRead;
326 			if (diff > 0) {
327 				zStream.next_out -= max_c(bytesRead, diff);
328 				diff -= max_c(bytesRead, diff);
329 			}
330 
331 			if (zStream.avail_in != 0 && status != Z_STREAM_END) {
332 				TRACE("Inode::ReadAt() didn't read whole block: %s\n",
333 					zStream.msg);
334 			}
335 		} while (status == Z_OK);
336 
337 		inflateEnd(&zStream);
338 
339 		if (status != Z_STREAM_END) {
340 			TRACE("Inode::ReadAt() inflating failed: %d!\n", status);
341 			return B_BAD_DATA;
342 		}
343 
344 		*_length = zStream.total_out;
345 
346 	} else {
347 		panic("unknown extent compression; %d\n", compression);
348 		return B_BAD_DATA;
349 	}
350 	return B_OK;
351 
352 }
353 
354 
355 status_t
356 Inode::FindParent(ino_t* id)
357 {
358 	btrfs_key search_key;
359 	search_key.SetType(BTRFS_KEY_TYPE_INODE_REF);
360 	search_key.SetObjectID(fID);
361 	search_key.SetOffset(-1);
362 	BTree::Path path(fVolume->FSTree());
363 
364 	void* node_ref;
365 	if (fVolume->FSTree()->FindPrevious(&path, search_key, &node_ref) != B_OK) {
366 		ERROR("Inode::FindParent(): Couldn't find inode for %" B_PRIdINO "\n",
367 			fID);
368 		return B_ERROR;
369 	}
370 
371 	free(node_ref);
372 	*id = search_key.Offset();
373 	TRACE("Inode::FindParent() for %" B_PRIdINO ": %" B_PRIdINO "\n", fID,
374 		*id);
375 
376 	return B_OK;
377 }
378 
379 
380 uint64
381 Inode::FindNextIndex(BTree::Path* path) const
382 {
383 	btrfs_key key;
384 	key.SetObjectID(fID);
385 	key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
386 	key.SetOffset(-1);
387 
388 	if (fVolume->FSTree()->FindPrevious(path, key, NULL))
389 		return 2;		// not found any dir index item
390 
391 	return key.Offset() + 1;
392 }
393 
394 
395 /* Insert inode_item
396  */
397 status_t
398 Inode::Insert(Transaction& transaction, BTree::Path* path)
399 {
400 	BTree* tree = path->Tree();
401 
402 	btrfs_entry item;
403 	item.key.SetObjectID(fID);
404 	item.key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
405 	item.key.SetOffset(0);
406 	item.SetSize(sizeof(btrfs_inode));
407 
408 	void* data[1];
409 	data[0] = (void*)&fNode;
410 	status_t status = tree->InsertEntries(transaction, path, &item, data, 1);
411 	if (status != B_OK)
412 		return status;
413 
414 	return B_OK;
415 }
416 
417 
418 /* Remove inode_item
419  */
420 status_t
421 Inode::Remove(Transaction& transaction, BTree::Path* path)
422 {
423 	BTree* tree = path->Tree();
424 	btrfs_key key;
425 	key.SetObjectID(fID);
426 	key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
427 	key.SetOffset(0);
428 	status_t status = tree->RemoveEntries(transaction, path, key, NULL, 1);
429 	if (status != B_OK)
430 		return status;
431 
432 	return B_OK;
433 }
434 
435 
436 /* Insert 3 items: inode_ref, dir_item, dir_index
437  * Basically, make a link between name and its node (location)
438  */
439 status_t
440 Inode::MakeReference(Transaction& transaction, BTree::Path* path,
441 	Inode* parent, const char* name, int32 mode)
442 {
443 	BTree* tree = fVolume->FSTree();
444 	uint16 nameLength = strlen(name);
445 	uint64 index = parent->FindNextIndex(path);
446 
447 	// insert inode_ref
448 	btrfs_inode_ref* inodeRef = (btrfs_inode_ref*)malloc(sizeof(btrfs_inode_ref)
449 		+ nameLength);
450 	if (inodeRef == NULL)
451 		return B_NO_MEMORY;
452 	inodeRef->index = index;
453 	inodeRef->SetName(name, nameLength);
454 
455 	btrfs_entry entry;
456 	entry.key.SetObjectID(fID);
457 	entry.key.SetType(BTRFS_KEY_TYPE_INODE_REF);
458 	entry.key.SetOffset(parent->ID());
459 	entry.SetSize(inodeRef->Length());
460 
461 	status_t status = tree->InsertEntries(transaction, path, &entry,
462 		(void**)&inodeRef, 1);
463 	free(inodeRef);
464 	if (status != B_OK)
465 		return status;
466 
467 	// insert dir_entry
468 	uint32 hash = calculate_crc((uint32)~1, (uint8*)name, nameLength);
469 	btrfs_dir_entry* directoryEntry =
470 		(btrfs_dir_entry*)malloc(sizeof(btrfs_dir_entry) + nameLength);
471 	if (directoryEntry == NULL)
472 		return B_NO_MEMORY;
473 	directoryEntry->location.SetObjectID(fID);
474 	directoryEntry->location.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
475 	directoryEntry->location.SetOffset(0);
476 	directoryEntry->SetTransactionID(transaction.SystemID());
477 	// TODO: xattribute, 0 for standard directory
478 	directoryEntry->SetName(name, nameLength);
479 	directoryEntry->SetAttributeData(NULL, 0);
480 	directoryEntry->type = get_filetype(mode);
481 
482 	entry.key.SetObjectID(parent->ID());
483 	entry.key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
484 	entry.key.SetOffset(hash);
485 	entry.SetSize(directoryEntry->Length());
486 
487 	status = tree->InsertEntries(transaction, path, &entry,
488 		(void**)&directoryEntry, 1);
489 	if (status != B_OK) {
490 		free(directoryEntry);
491 		return status;
492 	}
493 
494 	// insert dir_index (has same data with dir_entry)
495 	entry.key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
496 	entry.key.SetOffset(index);
497 
498 	status = tree->InsertEntries(transaction, path, &entry,
499 		(void**)&directoryEntry, 1);
500 	if (status != B_OK) {
501 		free(directoryEntry);
502 		return status;
503 	}
504 
505 	free(directoryEntry);
506 	return B_OK;
507 }
508 
509 
510 // Remove the "name" and unlink it with inode.
511 status_t
512 Inode::Dereference(Transaction& transaction, BTree::Path* path, ino_t parentID,
513 	const char* name)
514 {
515 	BTree* tree = path->Tree();
516 
517 	// remove inode_ref item
518 	btrfs_key key;
519 	key.SetObjectID(fID);
520 	key.SetType(BTRFS_KEY_TYPE_INODE_REF);
521 	key.SetOffset(parentID);
522 	btrfs_inode_ref* inodeRef;
523 	status_t status = tree->RemoveEntries(transaction, path, key,
524 		(void**)&inodeRef, 1);
525 	if (status != B_OK)
526 		return status;
527 
528 	// remove dir_item
529 	uint32 hash = calculate_crc((uint32)~1, (uint8*)name, strlen(name));
530 	key.SetObjectID(parentID);
531 	key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
532 	key.SetOffset(hash);
533 	status = tree->RemoveEntries(transaction, path, key, NULL, 1);
534 	if (status != B_OK)
535 		return status;
536 
537 	// remove dir_index
538 	uint64 index = inodeRef->Index();
539 	free(inodeRef);
540 	key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
541 	key.SetOffset(index);
542 	status = tree->RemoveEntries(transaction, path, key, NULL, 1);
543 	if (status != B_OK)
544 		return status;
545 
546 	return B_OK;
547 }
548