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