⛏️ index : haiku.git

/*
 * Copyright 2009-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "kernel_interface.h"

#include <dirent.h>

#include <new>

#include <fs_info.h>
#include <fs_interface.h>
#include <KernelExport.h>
#include <io_requests.h>
#include <slab/Slab.h>

#include <AutoDeleter.h>

#include <package/hpkg/PackageFileHeapAccessorBase.h>
#include "util/TwoKeyAVLTree.h"

#include "AttributeCookie.h"
#include "AttributeDirectoryCookie.h"
#include "DebugSupport.h"
#include "Directory.h"
#include "Query.h"
#include "PackageFSRoot.h"
#include "StringConstants.h"
#include "StringPool.h"
#include "Utils.h"
#include "Volume.h"


template<> object_cache* TwoKeyAVLTreeNode<void*>::sNodeCache = NULL;

static const uint32 kOptimalIOSize = 64 * 1024;


// #pragma mark - helper functions


static bool
lock_directory_for_node(Volume* volume, Node* node, DirectoryReadLocker& locker)
{
	if (Directory* directory = dynamic_cast<Directory*>(node)) {
		locker.SetTo(directory, false, true);
		return locker.IsLocked();
	} else {
		BReference<Directory> parentRef = node->GetParent();
		if (!parentRef.IsSet())
			return false;
		locker.SetTo(parentRef.Get(), false, true);
		return locker.IsLocked() && node->GetParentUnchecked() == locker.Get();
	}
}


static status_t
check_access(Node* node, int mode)
{
	// write access requested?
	if (mode & W_OK)
		return B_READ_ONLY_DEVICE;

	return check_access_permissions(mode, node->Mode(), node->GroupID(),
		node->UserID());
}


//	#pragma mark - Volume


static status_t
packagefs_mount(fs_volume* fsVolume, const char* device, uint32 flags,
	const char* parameters, ino_t* _rootID)
{
	FUNCTION("fsVolume: %p, device: \"%s\", flags: %#" B_PRIx32 ", parameters: "
			"\"%s\"\n", fsVolume, device, flags, parameters);

	// create a Volume object
	Volume* volume = new(std::nothrow) Volume(fsVolume);
	if (volume == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	VolumeWriteLocker volumeWriteLocker(volume);

	// Initialize the fs_volume now already, so it is mostly usable in during
	// mounting.
	fsVolume->private_volume = volume;
	fsVolume->ops = &gPackageFSVolumeOps;

	status_t error = volume->Mount(parameters);
	if (error != B_OK) {
		volumeWriteLocker.Unlock();
		delete volume;
		return error;
	}

	// set return values
	*_rootID = volume->RootDirectory()->ID();

	return B_OK;
}


static status_t
packagefs_unmount(fs_volume* fsVolume)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p\n", volume);

	volume->WriteLock();
	volume->Unmount();
	delete volume;

	return B_OK;
}


static status_t
packagefs_read_fs_info(fs_volume* fsVolume, struct fs_info* info)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p, info: %p\n", volume, info);

	info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY | B_FS_HAS_MIME
		| B_FS_HAS_ATTR | B_FS_HAS_QUERY | B_FS_SUPPORTS_NODE_MONITORING;
	info->block_size = 4096;
	info->io_size = kOptimalIOSize;
	info->total_blocks = info->free_blocks = 0;
	strlcpy(info->volume_name, volume->RootDirectory()->Name(),
		sizeof(info->volume_name));
	return B_OK;
}


// #pragma mark - VNodes


static status_t
packagefs_lookup(fs_volume* fsVolume, fs_vnode* fsDir, const char* entryName,
	ino_t* _vnid)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsDir->private_node;

	FUNCTION("volume: %p, dir: %p (%" B_PRId64 "), entry: \"%s\"\n", volume,
		node, node->ID(), entryName);

	if (!S_ISDIR(node->Mode()))
		return B_NOT_A_DIRECTORY;

	Directory* dir = dynamic_cast<Directory*>(node);

	// resolve "."
	if (strcmp(entryName, ".") == 0) {
		Node* self;
		*_vnid = dir->ID();
		return volume->GetVNode(*_vnid, self);
	}

	// resolve ".."
	if (strcmp(entryName, "..") == 0) {
		BReference<Directory> parent = dir->GetParent();
		if (parent == NULL)
			return B_ENTRY_NOT_FOUND;

		Node* dummy;
		*_vnid = parent->ID();
		return volume->GetVNode(*_vnid, dummy);
	}

	// resolve normal entries -- look up the node
	DirectoryReadLocker dirLocker(dir);
	String entryNameString;
	Node* child = dir->FindChild(StringKey(entryName));
	if (child == NULL)
		return B_ENTRY_NOT_FOUND;
	BReference<Node> childReference(child);
	dirLocker.Unlock();

	// get the vnode reference
	*_vnid = child->ID();
	RETURN_ERROR(volume->GetVNode(*_vnid, child));
}


static status_t
packagefs_get_vnode_name(fs_volume* fsVolume, fs_vnode* fsNode, char* buffer,
	size_t bufferSize)
{
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRIdINO "), %p, %zu\n",
		fsVolume->private_volume, node, node->ID(), buffer, bufferSize);

	if (strlcpy(buffer, node->Name(), bufferSize) >= bufferSize)
		return B_BUFFER_OVERFLOW;

	return B_OK;
}


static status_t
packagefs_get_vnode(fs_volume* fsVolume, ino_t vnid, fs_vnode* fsNode,
	int* _type, uint32* _flags, bool reenter)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p, vnid: %" B_PRId64 "\n", volume, vnid);

	VolumeReadLocker volumeLocker(volume);
	Node* node = volume->FindNode(vnid);
	if (node == NULL)
		return B_ENTRY_NOT_FOUND;
	BReference<Node> nodeReference(node);

	DirectoryWriteLocker dirLocker;
	if (Directory* directory = dynamic_cast<Directory*>(node)) {
		dirLocker.SetTo(directory, false, true);
		if (!dirLocker.IsLocked())
			return B_NO_INIT;
	} else {
		dirLocker.SetTo(node->GetParentUnchecked(), false, true);
		if (!dirLocker.IsLocked())
			return B_NO_INIT;
	}
	volumeLocker.Unlock();

	status_t error = node->VFSInit(volume->ID());
	if (error != B_OK)
		RETURN_ERROR(error);
	dirLocker.Unlock();

	fsNode->private_node = nodeReference.Detach();
	fsNode->ops = &gPackageFSVnodeOps;
	*_type = node->Mode() & S_IFMT;
	*_flags = 0;

	return B_OK;
}


static status_t
packagefs_put_vnode(fs_volume* fsVolume, fs_vnode* fsNode, bool reenter)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p\n", volume, node);
	TOUCH(volume);

	VolumeReadLocker volumeLocker(volume);
	DirectoryWriteLocker dirLocker;
	if (Directory* directory = dynamic_cast<Directory*>(node)) {
		dirLocker.SetTo(directory, false, true);
		ASSERT(dirLocker.IsLocked());
	} else {
		dirLocker.SetTo(node->GetParentUnchecked(), false, true);
		if (dirLocker.Get() == NULL) {
			// This node does not have a parent. This should only happen during
			// removal, in which case we should either have the Volume write lock,
			// or the node should have only one reference remaining.
			ASSERT(volume->IsWriteLocked() || node->CountReferences() == 1);
		}
	}
	volumeLocker.Unlock();

	node->VFSUninit();
	dirLocker.Unlock();

	node->ReleaseReference();

	return B_OK;
}


// #pragma mark - Request I/O


static status_t
packagefs_io(fs_volume* fsVolume, fs_vnode* fsNode, void* cookie,
	io_request* request)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p, request: %p\n",
		volume, node, node->ID(), cookie, request);
	TOUCH(volume);

	if (io_request_is_write(request))
		RETURN_ERROR(B_READ_ONLY_DEVICE);

	status_t error = node->Read(request);
	notify_io_request(request, error);
	return error;
}


// #pragma mark - Nodes


status_t
packagefs_ioctl(fs_volume* fsVolume, fs_vnode* fsNode, void* cookie,
	uint32 operation, void* buffer, size_t size)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p, operation: %"
		B_PRIu32 ", buffer: %p, size: %zu\n", volume, node, node->ID(), cookie,
		operation, buffer, size);
	TOUCH(cookie);

	return volume->IOCtl(node, operation, buffer, size);
}


static status_t
packagefs_read_symlink(fs_volume* fsVolume, fs_vnode* fsNode, char* buffer,
	size_t* _bufferSize)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 ")\n", volume, node,
		node->ID());
	TOUCH(volume);

	DirectoryReadLocker dirLocker;
	if (!lock_directory_for_node(volume, node, dirLocker))
		return B_NO_INIT;

	if (!S_ISLNK(node->Mode()))
		return B_BAD_VALUE;

	return node->ReadSymlink(buffer, _bufferSize);
}


static status_t
packagefs_access(fs_volume* fsVolume, fs_vnode* fsNode, int mode)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 ")\n", volume, node,
		node->ID());
	TOUCH(volume);

	DirectoryReadLocker dirLocker;
	if (!lock_directory_for_node(volume, node, dirLocker))
		return B_NO_INIT;

	return check_access(node, mode);
}


static status_t
packagefs_read_stat(fs_volume* fsVolume, fs_vnode* fsNode, struct stat* st)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 ")\n", volume, node,
		node->ID());
	TOUCH(volume);

	DirectoryReadLocker dirLocker;
	if (!lock_directory_for_node(volume, node, dirLocker))
		return B_NO_INIT;

	st->st_mode = node->Mode();
	st->st_nlink = 1;
	st->st_uid = node->UserID();
	st->st_gid = node->GroupID();
	st->st_size = node->FileSize();
	st->st_blksize = kOptimalIOSize;
	st->st_mtim = node->ModifiedTime();
	st->st_atim = st->st_mtim;
	st->st_ctim = st->st_mtim;
		// TODO: Perhaps manage a changed time (particularly for directories)?
	st->st_crtim = st->st_mtim;
	st->st_blocks = (st->st_size + 511) / 512;

	return B_OK;
}


// #pragma mark - Files


struct FileCookie {
	int	openMode;

	FileCookie(int openMode)
		:
		openMode(openMode)
	{
	}
};


static status_t
packagefs_open(fs_volume* fsVolume, fs_vnode* fsNode, int openMode,
	void** _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), openMode %#x\n",
		volume, node, node->ID(), openMode);
	TOUCH(volume);

	DirectoryReadLocker dirLocker;
	if (!lock_directory_for_node(volume, node, dirLocker))
		return B_NO_INIT;

	// check the open mode and permissions
	if (S_ISDIR(node->Mode()) && (openMode & O_RWMASK) != O_RDONLY)
		return B_IS_A_DIRECTORY;
	if ((openMode & O_DIRECTORY) != 0 && !S_ISDIR(node->Mode()))
		return B_NOT_A_DIRECTORY;

	if ((openMode & O_RWMASK) != O_RDONLY)
		return B_NOT_ALLOWED;

	status_t error = check_access(node, R_OK);
	if (error != B_OK)
		return error;

	// allocate the cookie
	FileCookie* cookie = new(std::nothrow) FileCookie(openMode);
	if (cookie == NULL)
		RETURN_ERROR(B_NO_MEMORY);

	*_cookie = cookie;

	return B_OK;
}


static status_t
packagefs_close(fs_volume* fs, fs_vnode* _node, void* cookie)
{
	return B_OK;
}


static status_t
packagefs_free_cookie(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	FileCookie* cookie = (FileCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	delete cookie;

	return B_OK;
}


static status_t
packagefs_read(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie,
	off_t offset, void* buffer, size_t* bufferSize)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	FileCookie* cookie = (FileCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p, offset: %"
		B_PRId64 ", buffer: %p, size: %" B_PRIuSIZE "\n", volume, node,
		node->ID(), cookie, offset, buffer, *bufferSize);
	TOUCH(volume);

	if ((cookie->openMode & O_RWMASK) != O_RDONLY)
		return EBADF;

	return node->Read(offset, buffer, bufferSize);
}


// #pragma mark - Directories


struct DirectoryCookie : DirectoryIterator {
	Directory*	directory;
	int32		state;
	bool		registered;

	DirectoryCookie(Directory* directory)
		:
		directory(directory),
		state(0),
		registered(false)
	{
		Rewind();
	}

	~DirectoryCookie()
	{
		if (registered)
			directory->RemoveDirectoryIterator(this);
	}

	void Rewind()
	{
		if (registered)
			directory->RemoveDirectoryIterator(this);
		registered = false;

		state = 0;
		node = directory;
	}

	Node* Current(const char*& _name) const
	{
		if (node == NULL)
			return NULL;

		if (state == 0)
			_name = ".";
		else if (state == 1)
			_name = "..";
		else
			_name = node->Name();

		return node;
	}

	Node* Next()
	{
		if (state == 0) {
			state = 1;
			node = directory->GetParentUnchecked();
			if (node == NULL)
				node = directory;
			return node;
		}

		if (state == 1) {
			node = directory->FirstChild();
			state = 2;
		} else {
			if (node != NULL)
				node = directory->NextChild(node);
		}

		if (node == NULL) {
			if (registered) {
				directory->RemoveDirectoryIterator(this);
				registered = false;
			}

			return NULL;
		}

		if (!registered) {
			directory->AddDirectoryIterator(this);
			registered = true;
		}

		return node;
	}
};


static status_t
packagefs_open_dir(fs_volume* fsVolume, fs_vnode* fsNode, void** _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 ")\n", volume, node,
		node->ID());
	TOUCH(volume);

	if (!S_ISDIR(node->Mode()))
		return B_NOT_A_DIRECTORY;

	Directory* dir = dynamic_cast<Directory*>(node);

	status_t error = check_access(dir, R_OK);
	if (error != B_OK)
		return error;

	// create a cookie
	DirectoryWriteLocker dirLocker(dir);
	DirectoryCookie* cookie = new(std::nothrow) DirectoryCookie(dir);
	if (cookie == NULL)
		RETURN_ERROR(B_NO_MEMORY);

	*_cookie = cookie;
	return B_OK;
}


static status_t
packagefs_close_dir(fs_volume* fsVolume, fs_vnode* fsNode, void* cookie)
{
	return B_OK;
}


static status_t
packagefs_free_dir_cookie(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	DirectoryCookie* cookie = (DirectoryCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	DirectoryWriteLocker dirLocker(dynamic_cast<Directory*>(node));
	delete cookie;

	return B_OK;
}


static status_t
packagefs_read_dir(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie,
	struct dirent* buffer, size_t bufferSize, uint32* _count)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	DirectoryCookie* cookie = (DirectoryCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	DirectoryWriteLocker dirLocker(cookie->directory);

	uint32 maxCount = *_count;
	uint32 count = 0;

	dirent* previousEntry = NULL;

	const char* name;
	while (Node* child = cookie->Current(name)) {
		// don't read more entries than requested
		if (count >= maxCount)
			break;

		// align the buffer for subsequent entries
		if (count > 0) {
			addr_t offset = (addr_t)buffer % 8;
			if (offset > 0) {
				offset = 8 - offset;
				if (bufferSize <= offset)
					break;

				previousEntry->d_reclen += offset;
				buffer = (dirent*)((addr_t)buffer + offset);
				bufferSize -= offset;
			}
		}

		// fill in the entry name -- checks whether the entry fits into the
		// buffer
		if (!set_dirent_name(buffer, bufferSize, name)) {
			if (count == 0)
				RETURN_ERROR(B_BUFFER_OVERFLOW);
			break;
		}

		// fill in the other data
		buffer->d_dev = volume->ID();
		buffer->d_ino = child->ID();

		count++;
		previousEntry = buffer;
		bufferSize -= buffer->d_reclen;
		buffer = (dirent*)((addr_t)buffer + buffer->d_reclen);

		cookie->Next();
	}

	*_count = count;
	return B_OK;
}


static status_t
packagefs_rewind_dir(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	DirectoryCookie* cookie = (DirectoryCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	DirectoryWriteLocker dirLocker(dynamic_cast<Directory*>(node));
	cookie->Rewind();

	return B_OK;
}


// #pragma mark - Attribute Directories


status_t
packagefs_open_attr_dir(fs_volume* fsVolume, fs_vnode* fsNode, void** _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 ")\n", volume, node,
		node->ID());
	TOUCH(volume);

	status_t error = check_access(node, R_OK);
	if (error != B_OK)
		return error;

	// create a cookie
	DirectoryReadLocker dirLocker;
	if (!lock_directory_for_node(volume, node, dirLocker))
		return B_NO_INIT;

	AttributeDirectoryCookie* cookie;
	error = node->OpenAttributeDirectory(cookie);
	if (error != B_OK)
		RETURN_ERROR(error);

	*_cookie = cookie;
	return B_OK;
}


status_t
packagefs_close_attr_dir(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	AttributeDirectoryCookie* cookie = (AttributeDirectoryCookie*)_cookie;
	return cookie->Close();
}


status_t
packagefs_free_attr_dir_cookie(fs_volume* fsVolume, fs_vnode* fsNode,
	void* _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	AttributeDirectoryCookie* cookie = (AttributeDirectoryCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	delete cookie;

	return B_OK;
}


status_t
packagefs_read_attr_dir(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie,
	struct dirent* buffer, size_t bufferSize, uint32* _count)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	AttributeDirectoryCookie* cookie = (AttributeDirectoryCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	return cookie->Read(volume->ID(), node->ID(), buffer, bufferSize, _count);
}


status_t
packagefs_rewind_attr_dir(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	AttributeDirectoryCookie* cookie = (AttributeDirectoryCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	return cookie->Rewind();
}


// #pragma mark - Attribute Operations


status_t
packagefs_open_attr(fs_volume* fsVolume, fs_vnode* fsNode, const char* name,
	int openMode, void** _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), name: \"%s\", openMode "
			"%#x\n", volume, node, node->ID(), name, openMode);
	TOUCH(volume);

	DirectoryReadLocker dirLocker;
	if (!lock_directory_for_node(volume, node, dirLocker))
		return B_NO_INIT;

	// check the open mode and permissions
	if ((openMode & O_RWMASK) != O_RDONLY)
		return B_NOT_ALLOWED;

	status_t error = check_access(node, R_OK);
	if (error != B_OK)
		return error;

	AttributeCookie* cookie;
	error = node->OpenAttribute(StringKey(name), openMode, cookie);
	if (error != B_OK)
		return error;

	*_cookie = cookie;
	return B_OK;
}


status_t
packagefs_close_attr(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	AttributeCookie* cookie = (AttributeCookie*)_cookie;
	RETURN_ERROR(cookie->Close());
}


status_t
packagefs_free_attr_cookie(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	AttributeCookie* cookie = (AttributeCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	delete cookie;

	return B_OK;
}


status_t
packagefs_read_attr(fs_volume* fsVolume, fs_vnode* fsNode, void* _cookie,
	off_t offset, void* buffer, size_t* bufferSize)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	AttributeCookie* cookie = (AttributeCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	return cookie->ReadAttribute(offset, buffer, bufferSize);
}


status_t
packagefs_read_attr_stat(fs_volume* fsVolume, fs_vnode* fsNode,
	void* _cookie, struct stat* st)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Node* node = (Node*)fsNode->private_node;
	AttributeCookie* cookie = (AttributeCookie*)_cookie;

	FUNCTION("volume: %p, node: %p (%" B_PRId64 "), cookie: %p\n", volume, node,
		node->ID(), cookie);
	TOUCH(volume);
	TOUCH(node);

	return cookie->ReadAttributeStat(st);
}


// #pragma mark - index directory & index operations


// NOTE: We don't do any locking in the index dir hooks, since once mounted
// the index directory is immutable.


status_t
packagefs_open_index_dir(fs_volume* fsVolume, void** _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p\n", volume);

	IndexDirIterator* iterator = new(std::nothrow) IndexDirIterator(
		volume->GetIndexDirIterator());
	if (iterator == NULL)
		return B_NO_MEMORY;

	*_cookie = iterator;
	return B_OK;
}


status_t
packagefs_close_index_dir(fs_volume* fsVolume, void* cookie)
{
	return B_OK;
}


status_t
packagefs_free_index_dir_cookie(fs_volume* fsVolume, void* cookie)
{
	FUNCTION("volume: %p, cookie: %p\n", fsVolume->private_volume, cookie);

	delete (IndexDirIterator*)cookie;
	return B_OK;
}


status_t
packagefs_read_index_dir(fs_volume* fsVolume, void* cookie,
	struct dirent* buffer, size_t bufferSize, uint32* _num)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p, cookie: %p, buffer: %p, bufferSize: %zu, num: %"
		B_PRIu32 "\n", volume, cookie, buffer, bufferSize, *_num);

	IndexDirIterator* iterator = (IndexDirIterator*)cookie;

	if (*_num == 0)
		return B_BAD_VALUE;

	IndexDirIterator previousIterator = *iterator;

	// get the next index
	Index* index = iterator->Next();
	if (index == NULL) {
		*_num = 0;
		return B_OK;
	}

	// fill in the entry
	if (!set_dirent_name(buffer, bufferSize, index->Name())) {
		*iterator = previousIterator;
		return B_BUFFER_OVERFLOW;
	}

	buffer->d_dev = volume->ID();
	buffer->d_ino = 0;

	*_num = 1;
	return B_OK;
}


status_t
packagefs_rewind_index_dir(fs_volume* fsVolume, void* cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p, cookie: %p\n", volume, cookie);

	IndexDirIterator* iterator = (IndexDirIterator*)cookie;
	*iterator = volume->GetIndexDirIterator();

	return B_OK;
}


status_t
packagefs_create_index(fs_volume* fsVolume, const char* name, uint32 type,
	uint32 flags)
{
	return B_NOT_SUPPORTED;
}


status_t
packagefs_remove_index(fs_volume* fsVolume, const char* name)
{
	return B_NOT_SUPPORTED;
}


status_t
packagefs_read_index_stat(fs_volume* fsVolume, const char* name,
	struct stat* stat)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p, name: \"%s\", stat: %p\n", volume, name, stat);

	Index* index = volume->FindIndex(StringKey(name));
	if (index == NULL)
		return B_ENTRY_NOT_FOUND;

	VolumeReadLocker volumeReadLocker(volume);

	memset(stat, 0, sizeof(*stat));
		// TODO: st_mtime, st_crtime, st_uid, st_gid are made available to
		// userland, so we should make an attempt to fill in values that make
		// sense.

	stat->st_type = index->Type();
	stat->st_size = index->CountEntries();

	return B_OK;
}


// #pragma mark - query operations


status_t
packagefs_open_query(fs_volume* fsVolume, const char* queryString, uint32 flags,
	port_id port, uint32 token, void** _cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;

	FUNCTION("volume: %p, query: \"%s\", flags: %#" B_PRIx32 ", port: %"
		B_PRId32 ", token: %" B_PRIu32 "\n", volume, queryString, flags, port,
		token);

	VolumeWriteLocker volumeWriteLocker(volume);

	Query* query;
	status_t error = Query::Create(volume, queryString, flags, port, token,
		query);
	if (error != B_OK)
		return error;

	*_cookie = query;
	return B_OK;
}


status_t
packagefs_close_query(fs_volume* fsVolume, void* cookie)
{
	FUNCTION_START();
	return B_OK;
}


status_t
packagefs_free_query_cookie(fs_volume* fsVolume, void* cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Query* query = (Query*)cookie;

	FUNCTION("volume: %p, query: %p\n", volume, query);

	VolumeWriteLocker volumeWriteLocker(volume);

	delete query;

	return B_OK;
}


status_t
packagefs_read_query(fs_volume* fsVolume, void* cookie, struct dirent* buffer,
	size_t bufferSize, uint32* _num)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Query* query = (Query*)cookie;

	FUNCTION("volume: %p, query: %p\n", volume, query);

	VolumeWriteLocker volumeWriteLocker(volume);

	status_t error = query->GetNextEntry(buffer, bufferSize);
	if (error == B_OK)
		*_num = 1;
	else if (error == B_ENTRY_NOT_FOUND)
		*_num = 0;
	else
		return error;

	return B_OK;
}


status_t
packagefs_rewind_query(fs_volume* fsVolume, void* cookie)
{
	Volume* volume = (Volume*)fsVolume->private_volume;
	Query* query = (Query*)cookie;

	FUNCTION("volume: %p, query: %p\n", volume, query);

	VolumeWriteLocker volumeWriteLocker(volume);

	return query->Rewind();
}


// #pragma mark - Module Interface


static status_t
packagefs_std_ops(int32 op, ...)
{
	using BPackageKit::BHPKG::BPrivate::PackageFileHeapAccessorBase;

	switch (op) {
		case B_MODULE_INIT:
		{
			init_debugging();
			PRINT("package_std_ops(): B_MODULE_INIT\n");

			status_t error = StringPool::Init();
			if (error != B_OK) {
				ERROR("Failed to init StringPool\n");
				exit_debugging();
				return error;
			}

			if (!StringConstants::Init()) {
				ERROR("Failed to init string constants\n");
				StringPool::Cleanup();
				exit_debugging();
				return error;
			}

			object_cache* quadChunkCache;
			PackageFileHeapAccessorBase::sQuadChunkCache = quadChunkCache =
				create_object_cache("pkgfs heap buffers",
					PackageFileHeapAccessorBase::kChunkSize * 4,
					0);
			object_cache_set_minimum_reserve(quadChunkCache, 1);

			TwoKeyAVLTreeNode<void*>::sNodeCache =
				create_object_cache("pkgfs TKAVLTreeNodes",
					sizeof(TwoKeyAVLTreeNode<void*>), CACHE_NO_DEPOT);

			error = PackageFSRoot::GlobalInit();
			if (error != B_OK) {
				ERROR("Failed to init PackageFSRoot\n");
				StringConstants::Cleanup();
				StringPool::Cleanup();
				exit_debugging();
				return error;
			}

			return B_OK;
		}

		case B_MODULE_UNINIT:
		{
			PRINT("package_std_ops(): B_MODULE_UNINIT\n");
			PackageFSRoot::GlobalUninit();
			delete_object_cache(TwoKeyAVLTreeNode<void*>::sNodeCache);
			delete_object_cache((object_cache*)
				PackageFileHeapAccessorBase::sQuadChunkCache);
			StringConstants::Cleanup();
			StringPool::Cleanup();
			exit_debugging();
			return B_OK;
		}

		default:
			return B_ERROR;
	}
}


static file_system_module_info sPackageFSModuleInfo = {
	{
		"file_systems/packagefs" B_CURRENT_FS_API_VERSION,
		0,
		packagefs_std_ops,
	},

	"packagefs",				// short_name
	"Package File System",		// pretty_name
	0,							// DDM flags


	// scanning
	NULL,	// identify_partition,
	NULL,	// scan_partition,
	NULL,	// free_identify_partition_cookie,
	NULL,	// free_partition_content_cookie()

	&packagefs_mount
};


fs_volume_ops gPackageFSVolumeOps = {
	&packagefs_unmount,
	&packagefs_read_fs_info,
	NULL,	// write_fs_info,
	NULL,	// sync,

	&packagefs_get_vnode,

	// index directory
	&packagefs_open_index_dir,
	&packagefs_close_index_dir,
	&packagefs_free_index_dir_cookie,
	&packagefs_read_index_dir,
	&packagefs_rewind_index_dir,

	&packagefs_create_index,
	&packagefs_remove_index,
	&packagefs_read_index_stat,

	// query operations
	&packagefs_open_query,
	&packagefs_close_query,
	&packagefs_free_query_cookie,
	&packagefs_read_query,
	&packagefs_rewind_query,

	// TODO: FS layer operations
};


fs_vnode_ops gPackageFSVnodeOps = {
	// vnode operations
	&packagefs_lookup,
	&packagefs_get_vnode_name,
	&packagefs_put_vnode,
	&packagefs_put_vnode,	// remove_vnode -- same as put_vnode

	// VM file access
	NULL,	// can_page,
	NULL,	// read_pages,
	NULL,	// write_pages,

	&packagefs_io,
	NULL,	// cancel_io()

	NULL,	// get_file_map,

	&packagefs_ioctl,
	NULL,	// set_flags,
	NULL,	// select,
	NULL,	// deselect,
	NULL,	// fsync,

	&packagefs_read_symlink,
	NULL,	// create_symlink,

	NULL,	// link,
	NULL,	// unlink,
	NULL,	// rename,

	&packagefs_access,
	&packagefs_read_stat,
	NULL,	// write_stat,
	NULL,	// preallocate,

	// file operations
	NULL,	// create,
	&packagefs_open,
	&packagefs_close,
	&packagefs_free_cookie,
	&packagefs_read,
	NULL,	// write,

	// directory operations
	NULL,	// create_dir,
	NULL,	// remove_dir,
	&packagefs_open_dir,
	&packagefs_close_dir,
	&packagefs_free_dir_cookie,
	&packagefs_read_dir,
	&packagefs_rewind_dir,

	// attribute directory operations
	&packagefs_open_attr_dir,
	&packagefs_close_attr_dir,
	&packagefs_free_attr_dir_cookie,
	&packagefs_read_attr_dir,
	&packagefs_rewind_attr_dir,

	// attribute operations
	NULL,	// create_attr,
	&packagefs_open_attr,
	&packagefs_close_attr,
	&packagefs_free_attr_cookie,
	&packagefs_read_attr,
	NULL,	// write_attr,

	&packagefs_read_attr_stat,
	NULL,	// write_attr_stat,
	NULL,	// rename_attr,
	NULL	// remove_attr,

	// TODO: FS layer operations
};


module_info *modules[] = {
	(module_info *)&sPackageFSModuleInfo,
	NULL,
};