xref: /haiku/src/add-ons/kernel/file_systems/btrfs/Inode.cpp (revision 1a3518cf757c2da8006753f83962da5935bbc82b)
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 			if (bytesRead <= 0) {
304 				status = Z_STREAM_ERROR;
305 				break;
306 			}
307 			memcpy(in, extent_data->inline_data + offset, bytesRead);
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