/*
 * Copyright 2017, Chế Vũ Gia Hy, cvghy116@gmail.com.
 * Copyright 2011, Jérôme Duval, korli@users.berlios.de.
 * Copyright 2008-2014, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de.
 * This file may be used under the terms of the MIT License.
 */
 
 
#include "Inode.h"
#include "CachedBlock.h"
#include "CRCTable.h"
#include "Utility.h"
 
 
#undef ASSERT
//#define TRACE_BTRFS
#ifdef TRACE_BTRFS
#	define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x)
#	define ASSERT(x) { if (!(x)) kernel_debugger("btrfs: assert failed: " #x "\n"); }
#else
#	define TRACE(x...) ;
#	define ASSERT(x) ;
#endif
#define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x)
 
 
Inode::Inode(Volume* volume, ino_t id)
	:
	fVolume(volume),
	fID(id),
	fCache(NULL),
	fMap(NULL)
{
	rw_lock_init(&fLock, "btrfs inode");
 
	fInitStatus = UpdateNodeFromDisk();
	if (fInitStatus == B_OK) {
		if (!IsDirectory() && !IsSymLink()) {
			fCache = file_cache_create(fVolume->ID(), ID(), Size());
			fMap = file_map_create(fVolume->ID(), ID(), Size());
		}
	}
}
 
 
Inode::Inode(Volume* volume, ino_t id, const btrfs_inode& item)
	:
	fVolume(volume),
	fID(id),
	fCache(NULL),
	fMap(NULL),
	fInitStatus(B_OK),
	fNode(item)
{
	if (!IsDirectory() && !IsSymLink()) {
		fCache = file_cache_create(fVolume->ID(), ID(), Size());
		fMap = file_map_create(fVolume->ID(), ID(), Size());
	}
}
 
 
Inode::Inode(Volume* volume)
	:
	fVolume(volume),
	fID(0),
	fCache(NULL),
	fMap(NULL),
	fInitStatus(B_NO_INIT)
{
	rw_lock_init(&fLock, "btrfs inode");
}
 
 
Inode::~Inode()
{
	TRACE("Inode destructor\n");
	file_cache_delete(FileCache());
	file_map_delete(Map());
	TRACE("Inode destructor: Done\n");
}
 
 
status_t
Inode::InitCheck()
{
	return fInitStatus;
}
 
 
status_t
Inode::UpdateNodeFromDisk()
{
	btrfs_key search_key;
	search_key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
	search_key.SetObjectID(fID);
	search_key.SetOffset(0);
	BTree::Path path(fVolume->FSTree());
 
	btrfs_inode* node;
	if (fVolume->FSTree()->FindExact(&path, search_key, (void**)&node)
		!= B_OK) {
		ERROR("Inode::UpdateNodeFromDisk(): Couldn't find inode %"
			B_PRIdINO "\n", fID);
		return B_ENTRY_NOT_FOUND;
	}
 
	memcpy(&fNode, node, sizeof(btrfs_inode));
	free(node);
	return B_OK;
}
 
 
/*
 * Create new Inode object with inode_item
 */
Inode*
Inode::Create(Transaction& transaction, ino_t id, Inode* parent, int32 mode,
	uint64 size, uint64 flags)
{
	TRACE("Inode::Create() id % " B_PRIu64 " mode %" B_PRId32 " flags %"
		B_PRIu64"\n", id, flags, mode);
 
	Volume* volume = parent != NULL ?
		parent->GetVolume() : transaction.GetJournal()->GetVolume();
	uint64 nbytes = size;	// allocated size
	if (size > volume->MaxInlineSize())
		nbytes = (size / volume->SectorSize() + 1) * volume->SectorSize();
 
	btrfs_inode inode;
 
	inode.generation = B_HOST_TO_LENDIAN_INT64(transaction.SystemID());
	inode.transaction_id = B_HOST_TO_LENDIAN_INT64(transaction.SystemID());
	inode.size = B_HOST_TO_LENDIAN_INT64(size);
	inode.nbytes = B_HOST_TO_LENDIAN_INT64(nbytes);
	inode.blockgroup = 0;	// normal inode only
	inode.num_links = B_HOST_TO_LENDIAN_INT32(1);
	inode.uid = B_HOST_TO_LENDIAN_INT32(geteuid());
	inode.gid = B_HOST_TO_LENDIAN_INT32(parent != NULL ?
		parent->GroupID() : getegid());
	inode.mode = B_HOST_TO_LENDIAN_INT32(mode);;
	inode.rdev = 0;	// normal file only
	inode.flags = B_HOST_TO_LENDIAN_INT64(flags);
	inode.sequence = 0;	// incremented each time mtime value is changed
 
	uint64 now = real_time_clock_usecs();
	struct timespec timespec;
	timespec.tv_sec = now / 1000000;
	timespec.tv_nsec = (now % 1000000) * 1000;
	btrfs_inode::SetTime(inode.access_time, timespec);
	btrfs_inode::SetTime(inode.creation_time, timespec);
	btrfs_inode::SetTime(inode.change_time, timespec);
	btrfs_inode::SetTime(inode.modification_time, timespec);
 
	return new Inode(volume, id, inode);
}
 
 
status_t
Inode::CheckPermissions(int accessMode) const
{
	// you never have write access to a read-only volume
	if ((accessMode & W_OK) != 0 && fVolume->IsReadOnly())
		return B_READ_ONLY_DEVICE;
 
	return check_access_permissions(accessMode, Mode(), (gid_t)fNode.GroupID(),
		(uid_t)fNode.UserID());
}
 
 
status_t
Inode::FindBlock(off_t pos, off_t& physical, off_t* _length)
{
	btrfs_key search_key;
	search_key.SetType(BTRFS_KEY_TYPE_EXTENT_DATA);
	search_key.SetObjectID(fID);
	search_key.SetOffset(pos + 1);
	BTree::Path path(fVolume->FSTree());
 
	btrfs_extent_data* extent_data;
	status_t status = fVolume->FSTree()->FindPrevious(&path, search_key,
		(void**)&extent_data);
	if (status != B_OK) {
		ERROR("Inode::FindBlock(): Couldn't find extent_data 0x%" B_PRIx32
			"\n", status);
		return status;
	}
 
	TRACE("Inode::FindBlock(%" B_PRIdINO ") key.Offset() %" B_PRId64 "\n",
		ID(), search_key.Offset());
 
	off_t diff = pos - search_key.Offset();
	off_t logical = 0;
	if (extent_data->Type() == BTRFS_EXTENT_DATA_REGULAR)
		logical = diff + extent_data->disk_offset;
	else
		panic("unknown extent type; %d\n", extent_data->Type());
	status = fVolume->FindBlock(logical, physical);
	if (_length != NULL)
		*_length = extent_data->Size() - diff;
	TRACE("Inode::FindBlock(%" B_PRIdINO ") %" B_PRIdOFF " physical %"
		B_PRIdOFF "\n", ID(), pos, physical);
 
	free(extent_data);
	return status;
}
 
 
status_t
Inode::ReadAt(off_t pos, uint8* buffer, size_t* _length)
{
	size_t length = *_length;
 
	// set/check boundaries for pos/length
	if (pos < 0) {
		ERROR("inode %" B_PRIdINO ": ReadAt failed(pos %" B_PRIdOFF
			", length %lu)\n", ID(), pos, length);
		return B_BAD_VALUE;
	}
 
	if (pos >= Size() || length == 0) {
		TRACE("inode %" B_PRIdINO ": ReadAt 0 (pos %" B_PRIdOFF
			", length %lu)\n", ID(), pos, length);
		*_length = 0;
		return B_NO_ERROR;
	}
 
	// the file cache doesn't seem to like non block aligned file offset
	// so we avoid the file cache for inline extents
	btrfs_key search_key;
	search_key.SetType(BTRFS_KEY_TYPE_EXTENT_DATA);
	search_key.SetObjectID(fID);
	search_key.SetOffset(pos + 1);
	BTree::Path path(fVolume->FSTree());
 
	uint32 item_size;
	btrfs_extent_data* extent_data;
	status_t status = fVolume->FSTree()->FindPrevious(&path, search_key,
		(void**)&extent_data, &item_size);
	if (status != B_OK) {
		ERROR("Inode::FindBlock(): Couldn't find extent_data 0x%" B_PRIx32
			"\n", status);
		return status;
	}
	MemoryDeleter deleter(extent_data);
 
 
	uint8 compression = extent_data->Compression();
	if (FileCache() != NULL
		&& extent_data->Type() == BTRFS_EXTENT_DATA_REGULAR) {
		TRACE("inode %" B_PRIdINO ": ReadAt cache (pos %" B_PRIdOFF ", length %lu)\n",
			ID(), pos, length);
		if (compression == BTRFS_EXTENT_COMPRESS_NONE)
			return file_cache_read(FileCache(), NULL, pos, buffer, _length);
		else if (compression == BTRFS_EXTENT_COMPRESS_ZLIB)
			panic("zlib isn't unsupported for regular extent\n");
		else
			panic("unknown extent compression; %d\n", compression);
		return B_BAD_DATA;
	}
 
	TRACE("Inode::ReadAt(%" B_PRIdINO ") key.Offset() %" B_PRId64 "\n", ID(),
		search_key.Offset());
 
	off_t diff = pos - search_key.Offset();
	if (extent_data->Type() != BTRFS_EXTENT_DATA_INLINE) {
		panic("unknown extent type; %d\n", extent_data->Type());
		return B_BAD_DATA;
	}
 
	*_length = min_c(extent_data->Size() - diff, *_length);
	if (compression == BTRFS_EXTENT_COMPRESS_NONE)
		memcpy(buffer, extent_data->inline_data, *_length);
	else if (compression == BTRFS_EXTENT_COMPRESS_ZLIB) {
		char in[2048];
		z_stream zStream = {
			(Bytef*)in,		// next in
			sizeof(in),		// avail in
			0,				// total in
			NULL,			// next out
			0,				// avail out
			0,				// total out
			0,				// msg
			0,				// state
			Z_NULL,			// zalloc
			Z_NULL,			// zfree
			Z_NULL,			// opaque
			0,				// data type
			0,				// adler
			0,				// reserved
		};
 
		int status;
		ssize_t offset = 0;
		uint32 inline_size = item_size - 13;
		bool headerRead = false;
 
		TRACE("Inode::ReadAt(%" B_PRIdINO ") diff %" B_PRIdOFF " size %"
			B_PRIuSIZE "\n", ID(), diff, item_size);
 
		do {
			ssize_t bytesRead = min_c(sizeof(in), inline_size - offset);
			memcpy(in, extent_data->inline_data + offset, bytesRead);
			if (bytesRead != (ssize_t)sizeof(in)) {
				if (bytesRead <= 0) {
					status = Z_STREAM_ERROR;
					break;
				}
			}
 
			zStream.avail_in = bytesRead;
			zStream.next_in = (Bytef*)in;
 
			if (!headerRead) {
				headerRead = true;
 
				zStream.avail_out = length;
				zStream.next_out = (Bytef*)buffer;
 
				status = inflateInit2(&zStream, 15);
				if (status != Z_OK) {
					return B_ERROR;
				}
			}
 
			status = inflate(&zStream, Z_SYNC_FLUSH);
			offset += bytesRead;
			if (diff > 0) {
				zStream.next_out -= max_c(bytesRead, diff);
				diff -= max_c(bytesRead, diff);
			}
 
			if (zStream.avail_in != 0 && status != Z_STREAM_END) {
				TRACE("Inode::ReadAt() didn't read whole block: %s\n",
					zStream.msg);
			}
		} while (status == Z_OK);
 
		inflateEnd(&zStream);
 
		if (status != Z_STREAM_END) {
			TRACE("Inode::ReadAt() inflating failed: %d!\n", status);
			return B_BAD_DATA;
		}
 
		*_length = zStream.total_out;
 
	} else {
		panic("unknown extent compression; %d\n", compression);
		return B_BAD_DATA;
	}
	return B_OK;
 
}
 
 
status_t
Inode::FindParent(ino_t* id)
{
	btrfs_key search_key;
	search_key.SetType(BTRFS_KEY_TYPE_INODE_REF);
	search_key.SetObjectID(fID);
	search_key.SetOffset(-1);
	BTree::Path path(fVolume->FSTree());
 
	void* node_ref;
	if (fVolume->FSTree()->FindPrevious(&path, search_key, &node_ref) != B_OK) {
		ERROR("Inode::FindParent(): Couldn't find inode for %" B_PRIdINO "\n",
			fID);
		return B_ERROR;
	}
 
	free(node_ref);
	*id = search_key.Offset();
	TRACE("Inode::FindParent() for %" B_PRIdINO ": %" B_PRIdINO "\n", fID,
		*id);
 
	return B_OK;
}
 
 
uint64
Inode::FindNextIndex(BTree::Path* path) const
{
	btrfs_key key;
	key.SetObjectID(fID);
	key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
	key.SetOffset(-1);
 
	if (fVolume->FSTree()->FindPrevious(path, key, NULL))
		return 2;		// not found any dir index item
 
	return key.Offset() + 1;
}
 
 
/* Insert inode_item
 */
status_t
Inode::Insert(Transaction& transaction, BTree::Path* path)
{
	BTree* tree = path->Tree();
 
	btrfs_entry item;
	item.key.SetObjectID(fID);
	item.key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
	item.key.SetOffset(0);
	item.SetSize(sizeof(btrfs_inode));
 
	void* data[1];
	data[0] = (void*)&fNode;
	status_t status = tree->InsertEntries(transaction, path, &item, data, 1);
	if (status != B_OK)
		return status;
 
	return B_OK;
}
 
 
/* Remove inode_item
 */
status_t
Inode::Remove(Transaction& transaction, BTree::Path* path)
{
	BTree* tree = path->Tree();
	btrfs_key key;
	key.SetObjectID(fID);
	key.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
	key.SetOffset(0);
	status_t status = tree->RemoveEntries(transaction, path, key, NULL, 1);
	if (status != B_OK)
		return status;
 
	return B_OK;
}
 
 
/* Insert 3 items: inode_ref, dir_item, dir_index
 * Basically, make a link between name and its node (location)
 */
status_t
Inode::MakeReference(Transaction& transaction, BTree::Path* path,
	Inode* parent, const char* name, int32 mode)
{
	BTree* tree = fVolume->FSTree();
	uint16 nameLength = strlen(name);
	uint64 index = parent->FindNextIndex(path);
 
	// insert inode_ref
	btrfs_inode_ref* inodeRef = (btrfs_inode_ref*)malloc(sizeof(btrfs_inode_ref)
		+ nameLength);
	if (inodeRef == NULL)
		return B_NO_MEMORY;
	inodeRef->index = index;
	inodeRef->SetName(name, nameLength);
 
	btrfs_entry entry;
	entry.key.SetObjectID(fID);
	entry.key.SetType(BTRFS_KEY_TYPE_INODE_REF);
	entry.key.SetOffset(parent->ID());
	entry.SetSize(inodeRef->Length());
 
	status_t status = tree->InsertEntries(transaction, path, &entry,
		(void**)&inodeRef, 1);
	free(inodeRef);
	if (status != B_OK)
		return status;
 
	// insert dir_entry
	uint32 hash = calculate_crc((uint32)~1, (uint8*)name, nameLength);
	btrfs_dir_entry* directoryEntry =
		(btrfs_dir_entry*)malloc(sizeof(btrfs_dir_entry) + nameLength);
	if (directoryEntry == NULL)
		return B_NO_MEMORY;
	directoryEntry->location.SetObjectID(fID);
	directoryEntry->location.SetType(BTRFS_KEY_TYPE_INODE_ITEM);
	directoryEntry->location.SetOffset(0);
	directoryEntry->SetTransactionID(transaction.SystemID());
	// TODO: xattribute, 0 for standard directory
	directoryEntry->SetName(name, nameLength);
	directoryEntry->SetAttributeData(NULL, 0);
	directoryEntry->type = get_filetype(mode);
 
	entry.key.SetObjectID(parent->ID());
	entry.key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
	entry.key.SetOffset(hash);
	entry.SetSize(directoryEntry->Length());
 
	status = tree->InsertEntries(transaction, path, &entry,
		(void**)&directoryEntry, 1);
	if (status != B_OK) {
		free(directoryEntry);
		return status;
	}
 
	// insert dir_index (has same data with dir_entry)
	entry.key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
	entry.key.SetOffset(index);
 
	status = tree->InsertEntries(transaction, path, &entry,
		(void**)&directoryEntry, 1);
	if (status != B_OK) {
		free(directoryEntry);
		return status;
	}
 
	free(directoryEntry);
	return B_OK;
}
 
 
// Remove the "name" and unlink it with inode.
status_t
Inode::Dereference(Transaction& transaction, BTree::Path* path, ino_t parentID,
	const char* name)
{
	BTree* tree = path->Tree();
 
	// remove inode_ref item
	btrfs_key key;
	key.SetObjectID(fID);
	key.SetType(BTRFS_KEY_TYPE_INODE_REF);
	key.SetOffset(parentID);
	btrfs_inode_ref* inodeRef;
	status_t status = tree->RemoveEntries(transaction, path, key,
		(void**)&inodeRef, 1);
	if (status != B_OK)
		return status;
 
	// remove dir_item
	uint32 hash = calculate_crc((uint32)~1, (uint8*)name, strlen(name));
	key.SetObjectID(parentID);
	key.SetType(BTRFS_KEY_TYPE_DIR_ITEM);
	key.SetOffset(hash);
	status = tree->RemoveEntries(transaction, path, key, NULL, 1);
	if (status != B_OK)
		return status;
 
	// remove dir_index
	uint64 index = inodeRef->Index();
	free(inodeRef);
	key.SetType(BTRFS_KEY_TYPE_DIR_INDEX);
	key.SetOffset(index);
	status = tree->RemoveEntries(transaction, path, key, NULL, 1);
	if (status != B_OK)
		return status;
 
	return B_OK;
}

V781 The value of the 'bytesRead' variable is checked after it was used. Perhaps there is a mistake in program logic. Check lines: 303, 305.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fLock.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fNode.