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