xref: /haiku/src/add-ons/kernel/file_systems/btrfs/Inode.cpp (revision 17889a8c70dbb3d59c1412f6431968753c767bab)
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 		if (user_memcpy(buffer, extent_data->inline_data, *_length) < B_OK)
274 			return B_BAD_ADDRESS;
275 	} else if (compression == BTRFS_EXTENT_COMPRESS_ZLIB) {
276 		char in[2048];
277 		z_stream zStream = {
278 			(Bytef*)in,		// next in
279 			sizeof(in),		// avail in
280 			0,				// total in
281 			NULL,			// next out
282 			0,				// avail out
283 			0,				// total out
284 			0,				// msg
285 			0,				// state
286 			Z_NULL,			// zalloc
287 			Z_NULL,			// zfree
288 			Z_NULL,			// opaque
289 			0,				// data type
290 			0,				// adler
291 			0,				// reserved
292 		};
293 
294 		int status;
295 		ssize_t offset = 0;
296 		uint32 inline_size = item_size - 13;
297 		bool headerRead = false;
298 
299 		TRACE("Inode::ReadAt(%" B_PRIdINO ") diff %" B_PRIdOFF " size %"
300 			B_PRIuSIZE "\n", ID(), diff, item_size);
301 
302 		do {
303 			ssize_t bytesRead = min_c(sizeof(in), inline_size - offset);
304 			if (bytesRead <= 0) {
305 				status = Z_STREAM_ERROR;
306 				break;
307 			}
308 			memcpy(in, extent_data->inline_data + offset, bytesRead);
309 
310 			zStream.avail_in = bytesRead;
311 			zStream.next_in = (Bytef*)in;
312 
313 			if (!headerRead) {
314 				headerRead = true;
315 
316 				zStream.avail_out = length;
317 				zStream.next_out = (Bytef*)buffer;
318 
319 				status = inflateInit2(&zStream, 15);
320 				if (status != Z_OK) {
321 					return B_ERROR;
322 				}
323 			}
324 
325 			status = inflate(&zStream, Z_SYNC_FLUSH);
326 			offset += bytesRead;
327 			if (diff > 0) {
328 				zStream.next_out -= max_c(bytesRead, diff);
329 				diff -= max_c(bytesRead, diff);
330 			}
331 
332 			if (zStream.avail_in != 0 && status != Z_STREAM_END) {
333 				TRACE("Inode::ReadAt() didn't read whole block: %s\n",
334 					zStream.msg);
335 			}
336 		} while (status == Z_OK);
337 
338 		inflateEnd(&zStream);
339 
340 		if (status != Z_STREAM_END) {
341 			TRACE("Inode::ReadAt() inflating failed: %d!\n", status);
342 			return B_BAD_DATA;
343 		}
344 
345 		*_length = zStream.total_out;
346 
347 	} else {
348 		panic("unknown extent compression; %d\n", compression);
349 		return B_BAD_DATA;
350 	}
351 	return B_OK;
352 
353 }
354 
355 
356 status_t
357 Inode::FindParent(ino_t* id)
358 {
359 	btrfs_key search_key;
360 	search_key.SetType(BTRFS_KEY_TYPE_INODE_REF);
361 	search_key.SetObjectID(fID);
362 	search_key.SetOffset(-1);
363 	BTree::Path path(fVolume->FSTree());
364 
365 	void* node_ref;
366 	if (fVolume->FSTree()->FindPrevious(&path, search_key, &node_ref) != B_OK) {
367 		ERROR("Inode::FindParent(): Couldn't find inode for %" B_PRIdINO "\n",
368 			fID);
369 		return B_ERROR;
370 	}
371 
372 	free(node_ref);
373 	*id = search_key.Offset();
374 	TRACE("Inode::FindParent() for %" B_PRIdINO ": %" B_PRIdINO "\n", fID,
375 		*id);
376 
377 	return B_OK;
378 }
379 
380 
381 uint64
382 Inode::FindNextIndex(BTree::Path* path) const
383 {
384 	btrfs_key key;
385 	key.SetObjectID(fID);
386 	key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
387 	key.SetOffset(-1);
388 
389 	if (fVolume->FSTree()->FindPrevious(path, key, NULL))
390 		return 2;		// not found any dir index item
391 
392 	return key.Offset() + 1;
393 }
394 
395 
396 /* Insert inode_item
397  */
398 status_t
399 Inode::Insert(Transaction& transaction, BTree::Path* path)
400 {
401 	BTree* tree = path->Tree();
402 
403 	btrfs_entry item;
404 	item.key.SetObjectID(fID);
405 	item.key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
406 	item.key.SetOffset(0);
407 	item.SetSize(sizeof(btrfs_inode));
408 
409 	void* data[1];
410 	data[0] = (void*)&fNode;
411 	status_t status = tree->InsertEntries(transaction, path, &item, data, 1);
412 	if (status != B_OK)
413 		return status;
414 
415 	return B_OK;
416 }
417 
418 
419 /* Remove inode_item
420  */
421 status_t
422 Inode::Remove(Transaction& transaction, BTree::Path* path)
423 {
424 	BTree* tree = path->Tree();
425 	btrfs_key key;
426 	key.SetObjectID(fID);
427 	key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
428 	key.SetOffset(0);
429 	status_t status = tree->RemoveEntries(transaction, path, key, NULL, 1);
430 	if (status != B_OK)
431 		return status;
432 
433 	return B_OK;
434 }
435 
436 
437 /* Insert 3 items: inode_ref, dir_item, dir_index
438  * Basically, make a link between name and its node (location)
439  */
440 status_t
441 Inode::MakeReference(Transaction& transaction, BTree::Path* path,
442 	Inode* parent, const char* name, int32 mode)
443 {
444 	BTree* tree = fVolume->FSTree();
445 	uint16 nameLength = strlen(name);
446 	uint64 index = parent->FindNextIndex(path);
447 
448 	// insert inode_ref
449 	btrfs_inode_ref* inodeRef = (btrfs_inode_ref*)malloc(sizeof(btrfs_inode_ref)
450 		+ nameLength);
451 	if (inodeRef == NULL)
452 		return B_NO_MEMORY;
453 	inodeRef->index = index;
454 	inodeRef->SetName(name, nameLength);
455 
456 	btrfs_entry entry;
457 	entry.key.SetObjectID(fID);
458 	entry.key.SetType(BTRFS_KEY_TYPE_INODE_REF);
459 	entry.key.SetOffset(parent->ID());
460 	entry.SetSize(inodeRef->Length());
461 
462 	status_t status = tree->InsertEntries(transaction, path, &entry,
463 		(void**)&inodeRef, 1);
464 	free(inodeRef);
465 	if (status != B_OK)
466 		return status;
467 
468 	// insert dir_entry
469 	uint32 hash = calculate_crc((uint32)~1, (uint8*)name, nameLength);
470 	btrfs_dir_entry* directoryEntry =
471 		(btrfs_dir_entry*)malloc(sizeof(btrfs_dir_entry) + nameLength);
472 	if (directoryEntry == NULL)
473 		return B_NO_MEMORY;
474 	directoryEntry->location.SetObjectID(fID);
475 	directoryEntry->location.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
476 	directoryEntry->location.SetOffset(0);
477 	directoryEntry->SetTransactionID(transaction.SystemID());
478 	// TODO: xattribute, 0 for standard directory
479 	directoryEntry->SetName(name, nameLength);
480 	directoryEntry->SetAttributeData(NULL, 0);
481 	directoryEntry->type = get_filetype(mode);
482 
483 	entry.key.SetObjectID(parent->ID());
484 	entry.key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
485 	entry.key.SetOffset(hash);
486 	entry.SetSize(directoryEntry->Length());
487 
488 	status = tree->InsertEntries(transaction, path, &entry,
489 		(void**)&directoryEntry, 1);
490 	if (status != B_OK) {
491 		free(directoryEntry);
492 		return status;
493 	}
494 
495 	// insert dir_index (has same data with dir_entry)
496 	entry.key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
497 	entry.key.SetOffset(index);
498 
499 	status = tree->InsertEntries(transaction, path, &entry,
500 		(void**)&directoryEntry, 1);
501 	if (status != B_OK) {
502 		free(directoryEntry);
503 		return status;
504 	}
505 
506 	free(directoryEntry);
507 	return B_OK;
508 }
509 
510 
511 // Remove the "name" and unlink it with inode.
512 status_t
513 Inode::Dereference(Transaction& transaction, BTree::Path* path, ino_t parentID,
514 	const char* name)
515 {
516 	BTree* tree = path->Tree();
517 
518 	// remove inode_ref item
519 	btrfs_key key;
520 	key.SetObjectID(fID);
521 	key.SetType(BTRFS_KEY_TYPE_INODE_REF);
522 	key.SetOffset(parentID);
523 	btrfs_inode_ref* inodeRef;
524 	status_t status = tree->RemoveEntries(transaction, path, key,
525 		(void**)&inodeRef, 1);
526 	if (status != B_OK)
527 		return status;
528 
529 	// remove dir_item
530 	uint32 hash = calculate_crc((uint32)~1, (uint8*)name, strlen(name));
531 	key.SetObjectID(parentID);
532 	key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
533 	key.SetOffset(hash);
534 	status = tree->RemoveEntries(transaction, path, key, NULL, 1);
535 	if (status != B_OK)
536 		return status;
537 
538 	// remove dir_index
539 	uint64 index = inodeRef->Index();
540 	free(inodeRef);
541 	key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
542 	key.SetOffset(index);
543 	status = tree->RemoveEntries(transaction, path, key, NULL, 1);
544 	if (status != B_OK)
545 		return status;
546 
547 	return B_OK;
548 }
549