root/src/add-ons/kernel/file_systems/packagefs/kernel_interface.cpp
/*
 * 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 + DEV_BSIZE - 1) / DEV_BSIZE;

        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_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);
                        PackageFileHeapAccessorBase::sQuadChunkFallbackBuffer =
                                object_cache_alloc(quadChunkCache, 0);

                        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);
                        object_cache_free((object_cache*)
                                PackageFileHeapAccessorBase::sQuadChunkCache,
                                PackageFileHeapAccessorBase::sQuadChunkFallbackBuffer, 0);
                        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,
};