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