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