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
28Inode::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
47Inode::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
63Inode::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
75Inode::~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
84status_t
85Inode::InitCheck()
86{
87	return fInitStatus;
88}
89
90
91status_t
92Inode::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 */
117Inode*
118Inode::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
159status_t
160Inode::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
171status_t
172Inode::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
209status_t
210Inode::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
355status_t
356Inode::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
380uint64
381Inode::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 */
397status_t
398Inode::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 */
420status_t
421Inode::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 */
439status_t
440Inode::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.
511status_t
512Inode::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