root/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp
/*
 * Copyright 2001-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "FUSEVolume.h"

#include <dirent.h>
#include <file_systems/mime_ext_table.h>

#include <algorithm>

#include <fs_info.h>
#include <NodeMonitor.h>

#include <AutoDeleter.h>
#include <StackOrHeapArray.h>

#include "FUSELowLevel.h"

#include "../IORequestInfo.h"
#include "../kernel_emu.h"
#include "../RequestThread.h"

#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))


// TODO: For remote/shared file systems (sshfs, nfs, etc.) we need to notice
// that entries have been added/removed, so that we can (1) update our
// FUSEEntry/FUSENode objects and (2) send out node monitoring messages.


// The maximal node tree hierarchy levels we support.
static const uint32 kMaxNodeTreeDepth = 1024;


struct FUSEVolume::DirEntryCache {
        DirEntryCache()
                :
                fEntries(NULL),
                fNames(NULL),
                fEntryCount(0),
                fEntryCapacity(0),
                fNamesSize(0),
                fNamesCapacity(0)
        {
        }

        ~DirEntryCache()
        {
                free(fEntries);
                free(fNames);
        }

        status_t AddEntry(ino_t nodeID, const char* name)
        {
                // resize entries array, if full
                if (fEntryCount == fEntryCapacity) {
                        // entries array full -- resize
                        uint32 newCapacity = std::max(fEntryCapacity * 2, (uint32)8);
                        Entry* newEntries = (Entry*)realloc(fEntries,
                                newCapacity * sizeof(Entry));
                        if (newEntries == NULL)
                                return B_NO_MEMORY;

                        fEntries = newEntries;
                        fEntryCapacity = newCapacity;
                }

                // resize names buffer, if full
                size_t nameSize = strlen(name) + 1;
                if (fNamesSize + nameSize > fNamesCapacity) {
                        size_t newCapacity = std::max(fNamesCapacity * 2, (size_t)256);
                        while (newCapacity < fNamesSize + nameSize)
                                newCapacity *= 2;

                        char* names = (char*)realloc(fNames, newCapacity);
                        if (names == NULL)
                                return B_NO_MEMORY;

                        fNames = names;
                        fNamesCapacity = newCapacity;
                }

                // add the entry
                fEntries[fEntryCount].nodeID = nodeID;
                fEntries[fEntryCount].nameOffset = fNamesSize;
                fEntries[fEntryCount].nameSize = nameSize;
                fEntryCount++;

                memcpy(fNames + fNamesSize, name, nameSize);
                fNamesSize += nameSize;

                return B_OK;
        }

        uint32 CountEntries() const
        {
                return fEntryCount;
        }

        bool ReadDirent(uint32 index, dev_t volumeID, bool align, dirent* buffer,
                size_t bufferSize) const
        {
                if (index >= fEntryCount)
                        return false;

                const Entry& entry = fEntries[index];

                // get and check the size
                size_t size = offsetof(struct dirent, d_name) + entry.nameSize;
                if (size > bufferSize)
                        return false;

                // align the size, if requested
                if (align)
                        size = std::min(bufferSize, ROUNDUP(size, 8));

                // fill in the dirent
                buffer->d_dev = volumeID;
                buffer->d_ino = entry.nodeID;
                memcpy(buffer->d_name, fNames + entry.nameOffset, entry.nameSize);
                buffer->d_reclen = size;

                return true;
        }

private:
        struct Entry {
                ino_t   nodeID;
                uint32  nameOffset;
                uint32  nameSize;
        };

private:
        Entry*  fEntries;
        char*   fNames;
        uint32  fEntryCount;
        uint32  fEntryCapacity;
        size_t  fNamesSize;
        size_t  fNamesCapacity;
};


struct FUSEVolume::DirCookie : fuse_file_info, RWLockable {
        union {
                off_t           currentEntryOffset;
                uint32          currentEntryIndex;
        };
        DirEntryCache*  entryCache;
        bool                    getdirInterface;

        DirCookie()
                :
                currentEntryOffset(0),
                entryCache(NULL),
                getdirInterface(false)
        {
                flags = 0;
                fh_old = 0;
                writepage = 0;
                direct_io = 0;
                keep_cache = 0;
                flush = 0;
                fh = 0;
                lock_owner = 0;
        }

        ~DirCookie()
        {
                delete entryCache;
        }
};


struct FUSEVolume::FileCookie : fuse_file_info, RWLockable {
        FileCookie(int openMode)
        {
                flags = openMode;
                fh_old = 0;
                writepage = 0;
                direct_io = 0;
                keep_cache = 0;
                flush = 0;
                fh = 0;
                lock_owner = 0;
        }
};


struct FUSEVolume::AttrDirCookie : RWLockable {
        AttrDirCookie()
                :
                fAttributes(NULL),
                fAttributesSize(0),
                fCurrentOffset(0),
                fValid(false)
        {
        }

        ~AttrDirCookie()
        {
                Clear();
        }

        void Clear()
        {
                free(fAttributes);
                fAttributes = NULL;
                fAttributesSize = 0;
                fCurrentOffset = 0;
                fValid = false;
        }

        status_t Allocate(size_t size)
        {
                Clear();

                if (size == 0)
                        return B_OK;

                fAttributes = (char*)malloc(size);
                if (fAttributes == NULL)
                        return B_NO_MEMORY;

                fAttributesSize = size;
                return B_OK;
        }

        bool IsValid() const
        {
                return fValid;
        }

        void SetValid(bool valid)
        {
                fValid = valid;
        }

        char* AttributesBuffer() const
        {
                return fAttributes;
        }

        bool ReadNextEntry(dev_t volumeID, ino_t nodeID, bool align,
                dirent* buffer, size_t bufferSize)
        {
                if (fCurrentOffset >= fAttributesSize)
                        return false;

                const char* name = fAttributes + fCurrentOffset;
                size_t nameLen = strlen(name);

                // get and check the size
                size_t size = offsetof(struct dirent, d_name) + nameLen + 1;
                if (size > bufferSize)
                        return false;

                // align the size, if requested
                if (align)
                        size = std::min(bufferSize, ROUNDUP(size, 8));

                // fill in the dirent
                buffer->d_dev = volumeID;
                buffer->d_ino = nodeID;
                memcpy(buffer->d_name, name, nameLen + 1);
                buffer->d_reclen = size;

                fCurrentOffset += nameLen + 1;

                return true;
        }

private:
        char*   fAttributes;
        size_t  fAttributesSize;
        size_t  fCurrentOffset;
        bool    fValid;
};


struct FUSEVolume::AttrCookie : RWLockable {
public:
        AttrCookie(const char* name)
                :
                fValue(NULL),
                fSize(0),
                fType(0)
        {
                _SetType(name);
        }

        AttrCookie(const char* name, const char* value)
                :
                fValue(strdup(value)),
                fSize(strlen(value) + 1),
                fType(0)
        {
                _SetType(name);
        }

        ~AttrCookie()
        {
                free(fValue);
        }

        bool IsValid() const
        {
                return fValue != NULL;
        }

        uint32 Type() const
        {
                return fType;
        }

        status_t Allocate(size_t size)
        {
                fValue = (char*)malloc(size);
                if (fValue == NULL) {
                        fSize = 0;
                        return B_NO_MEMORY;
                }
                fSize = size;
                return B_OK;
        }

        void Read(void* buffer, size_t bufferSize, off_t pos,
                size_t* bytesRead) const
        {
                if (pos < 0 || (uint64)pos > SIZE_MAX || (size_t)pos > fSize - 1) {
                        *bytesRead = 0;
                        return;
                }
                size_t copySize = fSize - pos;
                if (copySize > bufferSize)
                        copySize = bufferSize;
                strlcpy((char*)buffer, &fValue[pos], bufferSize);
                *bytesRead = copySize;
        }

        char* Buffer()
        {
                return fValue;
        }

        size_t Size() const
        {
                return fSize;
        }

private:
        void _SetType(const char* name)
        {
                if (strcmp(name, kAttrMimeTypeName) == 0)
                        fType = B_MIME_STRING_TYPE;
                else
                        fType = B_RAW_TYPE;
        }

private:
        char*   fValue;
        size_t  fSize;
        uint32  fType;
};


struct FUSEVolume::ReadDirBuffer {
        FUSEVolume*     volume;
        FUSENode*       directory;
        DirCookie*      cookie;
        void*           buffer;
        size_t          bufferSize;
        size_t          usedSize;
        uint32          entriesRead;
        uint32          maxEntries;
        status_t        error;

        ReadDirBuffer(FUSEVolume* volume, FUSENode* directory, DirCookie* cookie,
                void* buffer, size_t bufferSize, uint32 maxEntries)
                :
                volume(volume),
                directory(directory),
                cookie(cookie),
                buffer(buffer),
                bufferSize(bufferSize),
                usedSize(0),
                entriesRead(0),
                maxEntries(maxEntries),
                error(B_OK)
        {
        }
};


struct FUSEVolume::LockIterator {
        FUSEVolume*     volume;
        FUSENode*       firstNode;
        FUSENode*       lastLockedNode;
        FUSENode*       nextNode;
        FUSENode*       stopBeforeNode;
        bool            writeLock;

        LockIterator(FUSEVolume* volume, FUSENode* node, bool writeLock,
                FUSENode* stopBeforeNode)
                :
                volume(volume),
                firstNode(node),
                lastLockedNode(NULL),
                nextNode(node),
                stopBeforeNode(stopBeforeNode),
                writeLock(writeLock)
        {
        }

        ~LockIterator()
        {
                Unlock();
        }

        void SetTo(FUSEVolume* volume, FUSENode* node, bool writeLock,
                FUSENode* stopBeforeNode)
        {
                Unlock();

                this->volume = volume;
                this->firstNode = node;
                this->lastLockedNode = NULL;
                this->nextNode = node;
                this->stopBeforeNode = stopBeforeNode;
                this->writeLock = writeLock;
        }

        status_t LockNext(bool* _done, bool* _volumeUnlocked)
        {
                // increment the ref count first
                nextNode->refCount++;

                if (volume->fLockManager.TryGenericLock(
                                nextNode == firstNode && writeLock, nextNode)) {
                        // got the lock
                        *_volumeUnlocked = false;
                } else {
                        // node is locked -- we need to unlock the volume and wait for
                        // the lock
                        volume->fLock.Unlock();
                        status_t error = volume->fLockManager.GenericLock(
                                nextNode == firstNode && writeLock, nextNode);
                        volume->fLock.Lock();

                        *_volumeUnlocked = false;

                        if (error != B_OK) {
                                volume->_PutNode(nextNode);
                                return error;
                        }
                }

                lastLockedNode = nextNode;

                // get the parent node
                FUSENode* parent = nextNode->Parent();
                if (parent == stopBeforeNode || parent == nextNode) {
                        if (parent == nextNode)
                                parent = NULL;
                        *_done = true;
                } else
                        *_done = false;

                nextNode = parent;

                return B_OK;
        }

        void Unlock()
        {
                if (lastLockedNode == NULL)
                        return;

                volume->_UnlockNodeChainInternal(firstNode, writeLock, lastLockedNode,
                        NULL);

                lastLockedNode = NULL;
                nextNode = firstNode;
        }

        void SetStopBeforeNode(FUSENode* stopBeforeNode)
        {
                this->stopBeforeNode = stopBeforeNode;
        }

        void Detach()
        {
                lastLockedNode = NULL;
                nextNode = firstNode;
        }
};


struct FUSEVolume::RWLockableReadLocking {
        RWLockableReadLocking(FUSEVolume* volume)
                :
                fLockManager(volume != NULL ? &volume->fLockManager : NULL)
        {
        }

        RWLockableReadLocking(RWLockManager* lockManager)
                :
                fLockManager(lockManager)
        {
        }

        RWLockableReadLocking(const RWLockableReadLocking& other)
                :
                fLockManager(other.fLockManager)
        {
        }

        inline bool Lock(RWLockable* lockable)
        {
                return fLockManager != NULL && fLockManager->ReadLock(lockable);
        }

        inline void Unlock(RWLockable* lockable)
        {
                if (fLockManager != NULL)
                        fLockManager->ReadUnlock(lockable);
        }

private:
        RWLockManager*  fLockManager;
};


struct FUSEVolume::RWLockableWriteLocking {
        RWLockableWriteLocking(FUSEVolume* volume)
                :
                fLockManager(volume != NULL ? &volume->fLockManager : NULL)
        {
        }

        RWLockableWriteLocking(RWLockManager* lockManager)
                :
                fLockManager(lockManager)
        {
        }

        RWLockableWriteLocking(const RWLockableWriteLocking& other)
                :
                fLockManager(other.fLockManager)
        {
        }

        inline bool Lock(RWLockable* lockable)
        {
                return fLockManager != NULL && fLockManager->WriteLock(lockable);
        }

        inline void Unlock(RWLockable* lockable)
        {
                if (fLockManager != NULL)
                        fLockManager->WriteUnlock(lockable);
        }

private:
        RWLockManager*  fLockManager;
};


struct FUSEVolume::RWLockableReadLocker
        : public AutoLocker<RWLockable, RWLockableReadLocking> {

        RWLockableReadLocker(FUSEVolume* volume, RWLockable* lockable)
                :
                AutoLocker<RWLockable, RWLockableReadLocking>(
                        RWLockableReadLocking(volume))
        {
                SetTo(lockable, false);
        }
};


struct FUSEVolume::RWLockableWriteLocker
        : public AutoLocker<RWLockable, RWLockableWriteLocking> {

        RWLockableWriteLocker(FUSEVolume* volume, RWLockable* lockable)
                :
                AutoLocker<RWLockable, RWLockableWriteLocking>(
                        RWLockableWriteLocking(volume))
        {
                SetTo(lockable, false);
        }
};


struct FUSEVolume::NodeLocker {
        NodeLocker(FUSEVolume* volume, FUSENode* node, bool parent, bool writeLock)
                :
                fVolume(volume),
                fNode(NULL),
                fParent(parent),
                fWriteLock(writeLock)
        {
                fStatus = volume->_LockNodeChain(node, parent, writeLock);
                if (fStatus == B_OK)
                        fNode = node;
        }

        ~NodeLocker()
        {
                if (fNode != NULL)
                        fVolume->_UnlockNodeChain(fNode, fParent, fWriteLock);
        }

        status_t Status() const
        {
                return fStatus;
        }

private:
        FUSEVolume*     fVolume;
        FUSENode*       fNode;
        status_t        fStatus;
        bool            fParent;
        bool            fWriteLock;
};


struct FUSEVolume::NodeReadLocker : NodeLocker {
        NodeReadLocker(FUSEVolume* volume, FUSENode* node, bool parent)
                :
                NodeLocker(volume, node, parent, false)
        {
        }
};


struct FUSEVolume::NodeWriteLocker : NodeLocker {
        NodeWriteLocker(FUSEVolume* volume, FUSENode* node, bool parent)
                :
                NodeLocker(volume, node, parent, true)
        {
        }
};


struct FUSEVolume::MultiNodeLocker {
        MultiNodeLocker(FUSEVolume* volume, FUSENode* node1, bool lockParent1,
                bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2)
                :
                fVolume(volume),
                fNode1(NULL),
                fNode2(NULL),
                fLockParent1(lockParent1),
                fWriteLock1(writeLock1),
                fLockParent2(lockParent2),
                fWriteLock2(writeLock2)
        {
                fStatus = volume->_LockNodeChains(node1, lockParent1, writeLock1, node2,
                        lockParent2, writeLock2);
                if (fStatus == B_OK) {
                        fNode1 = node1;
                        fNode2 = node2;
                }
        }

        ~MultiNodeLocker()
        {
                if (fNode1 != NULL) {
                        fVolume->_UnlockNodeChains(fNode1, fLockParent1, fWriteLock1,
                                fNode2, fLockParent2, fWriteLock2);
                }
        }

        status_t Status() const
        {
                return fStatus;
        }

private:
        FUSEVolume*     fVolume;
        FUSENode*       fNode1;
        FUSENode*       fNode2;
        bool            fLockParent1;
        bool            fWriteLock1;
        bool            fLockParent2;
        bool            fWriteLock2;
        status_t        fStatus;
};


// #pragma mark -


FUSEVolume::FUSEVolume(FUSEFileSystem* fileSystem, dev_t id)
        :
        Volume(fileSystem, id),
        fFS(NULL),
        fRootNode(NULL),
        fNextNodeID(FUSE_ROOT_ID + 1),
        fUseNodeIDs(false)
{
}


FUSEVolume::~FUSEVolume()
{
}


status_t
FUSEVolume::Init()
{
        // init lock manager
        status_t error = fLockManager.Init();
        if (error != B_OK)
                return error;

        // init entry and node tables
        error = fEntries.Init();
        if (error != B_OK)
                return error;

        error = fNodes.Init();
        if (error != B_OK)
                return error;

        // check lock
        error = fLock.InitCheck();
        if (error != B_OK)
                return error;

        return B_OK;
}


// #pragma mark - FS


status_t
FUSEVolume::Mount(const char* device, uint32 flags, const char* parameters,
        ino_t* rootID)
{
printf("FUSEVolume::Mount()\n");
        status_t error = _FileSystem()->InitClientFS(parameters);
        if (error != B_OK)
                RETURN_ERROR(error);

        fOps = _FileSystem()->GetLowlevelOps();
        if (fOps == NULL)
                fFS = _FileSystem()->GetFS();
        _FileSystem()->GetVolumeCapabilities(fCapabilities);

        const fuse_config& config = _FileSystem()->GetFUSEConfig();
        fUseNodeIDs = config.use_ino;

        // update the fuse_context::private_data field before calling into the FS
        if (fFS != NULL) {
                fuse_context* context = (fuse_context*)RequestThread::GetCurrentThread()
                        ->GetContext()->GetFSData();

                context->private_data = fFS->userData;
        }

        // get the root node
        struct stat st;

        int fuseError;
        if (fOps != NULL)
                fuseError = fuse_ll_getattr(fOps, FUSE_ROOT_ID, &st);
        else
                fuseError = fuse_fs_getattr(fFS, "/", &st);

        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        if (!fUseNodeIDs)
                st.st_ino = FUSE_ROOT_ID;

        // create a node and an entry object for the root node
        AutoLocker<Locker> _(fLock);
        FUSENode* node = new(std::nothrow) FUSENode(st.st_ino, st.st_mode & S_IFMT);
        FUSEEntry* entry = node != NULL ? FUSEEntry::Create(node, "/", node) : NULL;
        if (node == NULL || entry == NULL) {
                delete node;
                delete entry;
                _FileSystem()->ExitClientFS(B_NO_MEMORY);
                RETURN_ERROR(B_NO_MEMORY);
        }

        node->refCount++;       // for the entry
        node->entries.Add(entry);
        fRootNode = node;

        // insert the node and the entry
        fNodes.Insert(node);
        fEntries.Insert(entry);

        // init the volume name
        snprintf(fName, sizeof(fName), "%s Volume", _FileSystem()->GetName());

        // publish the root node
        error = UserlandFS::KernelEmu::publish_vnode(fID, node->id, node,
                node->type, 0, _FileSystem()->GetNodeCapabilities());
        if (error != B_OK) {
                _FileSystem()->ExitClientFS(B_NO_MEMORY);
                RETURN_ERROR(error);
        }

        *rootID = node->id;

        return B_OK;
}


status_t
FUSEVolume::Unmount()
{
printf("FUSEVolume::Unmount()\n");
        UserlandFS::KernelEmu::put_vnode(fID, fRootNode->id);
        _FileSystem()->ExitClientFS(B_OK);
        return B_OK;
}


status_t
FUSEVolume::Sync()
{
        PRINT(("FUSEVolume::Sync()\n"));

        // There's no FUSE hook for sync'ing the whole FS. We need to individually
        // fsync all nodes that have been marked dirty. To keep things simple, we
        // hold the volume lock the whole time. That's a concurrency killer, but
        // usually sync isn't invoked that often.

        AutoLocker<Locker> _(fLock);

        // iterate through all nodes
        FUSENodeTable::Iterator it = fNodes.GetIterator();
        while (FUSENode* node = it.Next()) {
                if (!node->dirty)
                        continue;

                // node is dirty -- we have to sync it

                int fuseError;
                if (fOps != NULL) {
                        fuse_file_info cookie;
                        fuseError = fuse_ll_open(fOps, node->id, &cookie);
                        if (fuseError == 0) {
                                fuse_ll_fsync(fOps, node->id, 0, &cookie);
                                        // full sync, not only data
                                fuse_ll_flush(fOps, node->id, &cookie);
                                fuse_ll_release(fOps, node->id, &cookie);
                        }
                } else {
                        // get a path for the node
                        char path[B_PATH_NAME_LENGTH];
                        size_t pathLen;
                        status_t error = _BuildPath(node, path, pathLen);
                        if (error != B_OK)
                                continue;

                        // open, sync, and close the node
                        FileCookie cookie(O_RDONLY);
                        fuseError = fuse_fs_open(fFS, path, &cookie);
                        if (fuseError == 0) {
                                fuseError = fuse_fs_fsync(fFS, path, 0, &cookie);
                                        // full sync, not only data
                                fuse_fs_flush(fFS, path, &cookie);
                                fuse_fs_release(fFS, path, &cookie);
                        }
                }

                if (fuseError == 0) {
                        // sync'ing successful -- mark the node not dirty
                        node->dirty = false;
                }
        }

        return B_OK;
}


status_t
FUSEVolume::ReadFSInfo(fs_info* info)
{
        if (_FileSystem()->HasHaikuFuseExtensions() && fFS->ops.ioctl != NULL) {
                int fuseError = fuse_fs_ioctl(fFS, "/", FUSE_HAIKU_GET_DRIVE_INFO, info, NULL,
                        sizeof(fs_info), NULL);
                if (fuseError != 0)
                        return fuseError;
                return B_OK;
        }

        // No Haiku FUSE extensions, so our knowledge is limited: use some values
        // from statfs and make reasonable guesses for the rest of them.
        struct statvfs st;
        int fuseError;

        if (fOps != NULL) {
                fuseError = fuse_ll_statfs(fOps, FUSE_ROOT_ID, &st);
        } else {
                if (fFS->ops.statfs == NULL)
                        return B_UNSUPPORTED;

                fuseError = fuse_fs_statfs(fFS, "/", &st);
        }

        if (fuseError != 0)
                return fuseError;

        memset(info, 0, sizeof(*info));
        info->flags = B_FS_IS_PERSISTENT;       // assume the FS is persistent
        if ((st.f_flag & ST_RDONLY) != 0)
                info->flags |= B_FS_IS_READONLY;
        info->block_size = st.f_bsize;
        info->io_size = 64 * 1024;                      // some value
        info->total_blocks = st.f_blocks;
        info->free_blocks = st.f_bfree;
        info->total_nodes = st.f_files;
        info->free_nodes = 100;                         // st.f_favail is ignored by statfs()
        strlcpy(info->volume_name, fName, sizeof(info->volume_name));
                // no way to get the real name (if any)

        return B_OK;
}


// #pragma mark - vnodes


status_t
FUSEVolume::Lookup(void* _dir, const char* entryName, ino_t* vnid)
{
        FUSENode* dir = (FUSENode*)_dir;

        // lock the directory
        NodeReadLocker nodeLocker(this, dir, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        // look the node up
        FUSENode* node;
        status_t error = _GetNode(dir, entryName, &node);
        if (error != B_OK)
                return error;

        *vnid = node->id;

        return B_OK;
}


status_t
FUSEVolume::GetVNodeName(void* _node, char* buffer, size_t bufferSize)
{
        FUSENode* node = (FUSENode*)_node;

        AutoLocker<Locker> _(fLock);

        // get one of the node's entries and return its name
        FUSEEntry* entry = node->entries.Head();
        if (entry == NULL)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        if (entry->name == NULL || entry->name[0] == '\0')
                RETURN_ERROR(B_BAD_DATA);

        if (strlcpy(buffer, entry->name, bufferSize) >= bufferSize)
                RETURN_ERROR(B_NAME_TOO_LONG);

        return B_OK;
}


status_t
FUSEVolume::ReadVNode(ino_t vnid, bool reenter, void** _node, int* type,
        uint32* flags, FSVNodeCapabilities* _capabilities)
{
        AutoLocker<Locker> _(fLock);

        FUSENode* node = fNodes.Lookup(vnid);
        if (node == NULL)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        node->refCount++;

        *_node = node;
        *type = node->type;
        *flags = 0;
        *_capabilities = _FileSystem()->GetNodeCapabilities();

        return B_OK;
}


status_t
FUSEVolume::WriteVNode(void* _node, bool reenter)
{
        FUSENode* node = (FUSENode*)_node;

        AutoLocker<Locker> _(fLock);

        _PutNode(node);

        return B_OK;
}


status_t
FUSEVolume::RemoveVNode(void* node, bool reenter)
{
        // TODO: Implement for real!
        return WriteVNode(node, reenter);
}


// #pragma mark - asynchronous I/O


status_t
FUSEVolume::DoIO(void* _node, void* _cookie, const IORequestInfo& requestInfo)
{
        FUSENode* node = (FUSENode*)_node;
        FileCookie* cookie = (FileCookie*)_cookie;

        if (requestInfo.length == 0)
                return B_OK;

        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        if (!S_ISREG(node->type))
                RETURN_ERROR(B_BAD_VALUE);

        BStackOrHeapArray<char, B_PAGE_SIZE> buffer(requestInfo.length);

        char path[B_PATH_NAME_LENGTH];
        size_t pathLen;

        int fuseError = 0;
        status_t error = B_OK;

        FileCookie alternativeCookie(requestInfo.isWrite ? O_WRONLY : O_RDONLY);
        if (cookie == NULL) {
                cookie = &alternativeCookie;

                if (fOps != NULL) {
                        fuseError = fuse_ll_open(fOps, node->id, cookie);
                } else {
                        AutoLocker<Locker> locker(fLock);

                        error = _BuildPath(node, path, pathLen);
                        if (error != B_OK)
                                RETURN_ERROR(error);

                        locker.Unlock();

                        fuseError = fuse_fs_open(fFS, path, cookie);
                }
        }

        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        RWLockableReadLocker cookieLocker(this, cookie);

        if (requestInfo.isWrite) {
                error = UserlandFS::KernelEmu::read_from_io_request(GetID(), requestInfo.id, buffer,
                        requestInfo.length);
        }

        size_t bytes = 0;
        if (error == B_OK) {
                bytes = requestInfo.length;
                error = _InternalIO(node, cookie, path, requestInfo.offset,
                        buffer, bytes, requestInfo.isWrite);
        }

        if (error == B_OK && !requestInfo.isWrite) {
                error = UserlandFS::KernelEmu::write_to_io_request(GetID(), requestInfo.id,
                        buffer, bytes);
        }

        UserlandFS::KernelEmu::notify_io_request(GetID(), requestInfo.id, error);

        if (cookie == &alternativeCookie) {
                if (fOps != NULL)
                        fuse_ll_release(fOps, node->id, cookie);
                else
                        fuse_fs_release(fFS, path, cookie);
        }

        if (error != B_OK)
                RETURN_ERROR(error);

        return B_OK;
}


// #pragma mark - nodes


status_t
FUSEVolume::SetFlags(void* _node, void* _cookie, int flags)
{
        FileCookie* cookie = (FileCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        const int settableFlags = O_APPEND | O_NONBLOCK | O_SYNC | O_RSYNC
                | O_DSYNC | O_DIRECT;

        cookie->flags = (cookie->flags & ~settableFlags) | (flags & settableFlags);

        return B_OK;
}


status_t
FUSEVolume::FSync(void* _node)
{
        FUSENode* node = (FUSENode*)_node;

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        bool dirty;
        AutoLocker<Locker> locker(fLock);
        if (fOps != NULL) {
                // mark the node not dirty
                dirty = node->dirty;
                node->dirty = false;

                locker.Unlock();

                fuse_file_info cookie;
                fuseError = fuse_ll_open(fOps, node->id, &cookie);
                if (fuseError == 0) {
                        fuseError = fuse_ll_fsync(fOps, node->id, 0, &cookie);
                                // full sync, not only data
                        fuse_ll_flush(fOps, node->id, &cookie);
                        fuse_ll_release(fOps, node->id, &cookie);
                }
        } else {
                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                // mark the node not dirty
                dirty = node->dirty;
                node->dirty = false;

                locker.Unlock();

                // open, sync, and close the node
                FileCookie cookie(O_RDONLY);
                fuseError = fuse_fs_open(fFS, path, &cookie);
                if (fuseError == 0) {
                        fuseError = fuse_fs_fsync(fFS, path, 0, &cookie);
                                // full sync, not only data
                        fuse_fs_flush(fFS, path, &cookie);
                        fuse_fs_release(fFS, path, &cookie);
                }
        }

        if (fuseError != 0) {
                // sync'ing failed -- mark the node dirty again
                locker.Lock();
                node->dirty |= dirty;
                RETURN_ERROR(fuseError);
        }

        return B_OK;
}


status_t
FUSEVolume::ReadSymlink(void* _node, char* buffer, size_t bufferSize,
        size_t* _bytesRead)
{
        FUSENode* node = (FUSENode*)_node;

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_readlink(fOps, node->id, buffer, bufferSize);
                if (fuseError != 0) {
                        *_bytesRead = 0;
                        return fuseError;
                }
                // fuse_ll_readlink returns the actual size (even if the data didn't fit the buffer)
                *_bytesRead = fuseError;
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // read the symlink
                int fuseError = fuse_fs_readlink(fFS, path, buffer, bufferSize);
                if (fuseError != 0) {
                        *_bytesRead = 0;
                        return fuseError;
                }

                // fuse_fs_readlink() is supposed to return a NULL-terminated string, which
                // the Haiku interface doesn't require. We have to return the string length,
                // though.
                *_bytesRead = strnlen(buffer, bufferSize);
        }

        return B_OK;
}


status_t
FUSEVolume::CreateSymlink(void* _dir, const char* name, const char* target,
        int mode)
{
        FUSENode* dir = (FUSENode*)_dir;
        PRINT(("FUSEVolume::CreateSymlink(%p (%" B_PRId64 "), \"%s\" -> \"%s\", "
                "%#x)\n", dir, dir->id, name, target, mode));

        // lock the directory
        NodeWriteLocker nodeLocker(this, dir, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_symlink(fOps, target, dir->id, name);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the entry
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(dir, name, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // create the symlink
                fuseError = fuse_fs_symlink(fFS, target, path);
        }

        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // TODO: Set the mode?!

        // mark the dir dirty
        AutoLocker<Locker> locker(fLock);
        dir->dirty = true;
        locker.Unlock();

        // send node monitoring message
        ino_t nodeID;
        if (_GetNodeID(dir, name, &nodeID)) {
                UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0,
                        dir->id, nodeID, NULL, name);
        }

        return B_OK;
}


status_t
FUSEVolume::Link(void* _dir, const char* name, void* _node)
{
        FUSENode* dir = (FUSENode*)_dir;
        FUSENode* node = (FUSENode*)_node;
        PRINT(("FUSEVolume::Link(%p (%" B_PRId64 "), \"%s\" -> %p (%" B_PRId64
                "))\n", dir, dir->id, name, node, node->id));

        // lock the directories -- the target directory for writing, the node's
        // parent for reading
        MultiNodeLocker nodeLocker(this, dir, false, true, node, true, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_link(fOps, node->id, dir->id, name);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the entries
                char oldPath[B_PATH_NAME_LENGTH];
                size_t oldPathLen;
                status_t error = _BuildPath(node, oldPath, oldPathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                char newPath[B_PATH_NAME_LENGTH];
                size_t newPathLen;
                error = _BuildPath(dir, name, newPath, newPathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // link
                fuseError = fuse_fs_link(fFS, oldPath, newPath);
        }
        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // mark the dir and the node dirty
        AutoLocker<Locker> locker(fLock);
        dir->dirty = true;
        node->dirty = true;
        locker.Unlock();

        // send node monitoring message
        UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0, dir->id,
                node->id, NULL, name);

        return B_OK;
}


status_t
FUSEVolume::Unlink(void* _dir, const char* name)
{
        FUSENode* dir = (FUSENode*)_dir;
        PRINT(("FUSEVolume::Unlink(%p (%" B_PRId64 "), \"%s\")\n", dir, dir->id,
                name));

        // lock the directory
        NodeWriteLocker nodeLocker(this, dir, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        // get the node ID (for the node monitoring message)
        ino_t nodeID;
        bool doNodeMonitoring = _GetNodeID(dir, name, &nodeID);

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_unlink(fOps, dir->id, name);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the entry
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(dir, name, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // unlink
                fuseError = fuse_fs_unlink(fFS, path);
        }
        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // remove the entry
        AutoLocker<Locker> locker(fLock);
        _RemoveEntry(dir, name);

        // mark the dir dirty
        dir->dirty = true;
        locker.Unlock();

        // send node monitoring message
        if (doNodeMonitoring) {
                UserlandFS::KernelEmu::notify_listener(B_ENTRY_REMOVED, 0, fID, 0,
                        dir->id, nodeID, NULL, name);
        }

        return B_OK;
}


status_t
FUSEVolume::Rename(void* _oldDir, const char* oldName, void* _newDir,
        const char* newName)
{
        FUSENode* oldDir = (FUSENode*)_oldDir;
        FUSENode* newDir = (FUSENode*)_newDir;
        PRINT(("FUSEVolume::Rename(%p (%" B_PRId64 "), \"%s\", %p (%" B_PRId64
                "), \"%s\")\n", oldDir, oldDir->id, oldName, newDir, newDir->id,
                newName));

        // lock the directories
        MultiNodeLocker nodeLocker(this, oldDir, false, true, newDir, false, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_rename(fOps, oldDir->id, oldName, newDir->id, newName);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the entries
                char oldPath[B_PATH_NAME_LENGTH];
                size_t oldPathLen;
                status_t error = _BuildPath(oldDir, oldName, oldPath, oldPathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                char newPath[B_PATH_NAME_LENGTH];
                size_t newPathLen;
                error = _BuildPath(newDir, newName, newPath, newPathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // rename
                fuseError = fuse_fs_rename(fFS, oldPath, newPath);
        }
        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // rename the entry
        AutoLocker<Locker> locker(fLock);
        _RenameEntry(oldDir, oldName, newDir, newName);

        // mark the dirs dirty
        oldDir->dirty = true;
        newDir->dirty = true;

        // send node monitoring message
        ino_t nodeID;
        if (_GetNodeID(newDir, newName, &nodeID)) {
                UserlandFS::KernelEmu::notify_listener(B_ENTRY_MOVED, 0, fID,
                        oldDir->id, newDir->id, nodeID, oldName, newName);
        }

        return B_OK;
}


status_t
FUSEVolume::Access(void* _node, int mode)
{
        FUSENode* node = (FUSENode*)_node;

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_access(fOps, node->id, mode);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // call the access hook on the path
                fuseError = fuse_fs_access(fFS, path, mode);
        }

        if (fuseError != 0)
                return fuseError;

        return B_OK;
}


status_t
FUSEVolume::ReadStat(void* _node, struct stat* st)
{
        FUSENode* node = (FUSENode*)_node;
        PRINT(("FUSEVolume::ReadStat(%p (%" B_PRId64 "), %p)\n", node, node->id,
                st));

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        st->st_dev = GetID();
        st->st_ino = node->id;
        st->st_blksize = 2048;
        st->st_type = 0;

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_getattr(fOps, node->id, st);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // stat the path
                fuseError = fuse_fs_getattr(fFS, path, st);
        }
        if (fuseError != 0)
                return fuseError;

        return B_OK;
}


status_t
FUSEVolume::WriteStat(void* _node, const struct stat* st, uint32 mask)
{
        FUSENode* node = (FUSENode*)_node;
        PRINT(("FUSEVolume::WriteStat(%p (%" B_PRId64 "), %p, %#" B_PRIx32 ")\n",
                node, node->id, st, mask));

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        if (fOps != NULL) {
                int fuseError = fuse_ll_setattr(fOps, node->id, st, mask);
                if (fuseError != 0)
                        RETURN_ERROR(fuseError);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // permissions
                if ((mask & B_STAT_MODE) != 0) {
                        int fuseError = fuse_fs_chmod(fFS, path, st->st_mode);
                        if (fuseError != 0)
                                RETURN_ERROR(fuseError);
                }

                // owner
                if ((mask & (B_STAT_UID | B_STAT_GID)) != 0) {
                        uid_t uid = (mask & B_STAT_UID) != 0 ? st->st_uid : (uid_t)-1;
                        gid_t gid = (mask & B_STAT_GID) != 0 ? st->st_gid : (gid_t)-1;
                        int fuseError = fuse_fs_chown(fFS, path, uid, gid);
                        if (fuseError != 0)
                                RETURN_ERROR(fuseError);
                }

                // size
                if ((mask & B_STAT_SIZE) != 0) {
                        // truncate
                        int fuseError = fuse_fs_truncate(fFS, path, st->st_size);
                        if (fuseError != 0)
                                RETURN_ERROR(fuseError);
                }

                // access/modification time
                if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) != 0) {
                        timespec tv[2] = {
                                {st->st_atime, 0},
                                {st->st_mtime, 0}
                        };

                        // If either time is not specified, we need to stat the file to get the
                        // current value.
                        if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME))
                                        != (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) {
                                struct stat currentStat;
                                int fuseError = fuse_fs_getattr(fFS, path, &currentStat);
                                if (fuseError != 0)
                                        RETURN_ERROR(fuseError);

                                if ((mask & B_STAT_ACCESS_TIME) == 0)
                                        tv[0].tv_sec = currentStat.st_atime;
                                else
                                tv[1].tv_sec = currentStat.st_mtime;
                        }

                        int fuseError = fuse_fs_utimens(fFS, path, tv);
                        if (fuseError != 0)
                                RETURN_ERROR(fuseError);
                }
        }

        // mark the node dirty
        AutoLocker<Locker> locker(fLock);
        node->dirty = true;

        // send node monitoring message
        uint32 changedFields = mask &
                (B_STAT_MODE | B_STAT_UID | B_STAT_GID | B_STAT_SIZE
                | B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME);

        if (changedFields != 0) {
                UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED, changedFields,
                        fID, 0, 0, node->id, NULL, NULL);
        }

        return B_OK;
}


// #pragma mark - files


status_t
FUSEVolume::Create(void* _dir, const char* name, int openMode, int mode,
        void** _cookie, ino_t* _vnid)
{
        FUSENode* dir = (FUSENode*)_dir;
        PRINT(("FUSEVolume::Create(%p (%" B_PRId64 "), \"%s\", %#x, %#x)\n", dir,
                dir->id, name, openMode, mode));

        // lock the directory
        NodeWriteLocker nodeLocker(this, dir, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        // allocate a file cookie
        FileCookie* cookie = new(std::nothrow) FileCookie(openMode);
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);
        ObjectDeleter<FileCookie> cookieDeleter(cookie);

        FUSENode* node;
        int fuseError;
        if (fOps) {
                fuse_file_info cookie;
                fuse_ino_t ino;
                fuseError = fuse_ll_create(fOps, dir->id, name, mode, &cookie, ino);
                if (fuseError != 0)
                        RETURN_ERROR(fuseError);

                // get the node
                // TODO do we really need it?
                status_t error = _GetNode(dir, name, &node);
                if (error != B_OK) {
                        // This is bad. We've create the file successfully, but couldn't get
                        // the node. Delete the entry.
                        // We can't close the file because we don't know its inode.
                        fuse_ll_flush(fOps, ino, &cookie);
                        fuse_ll_release(fOps, ino, &cookie);
                        fuse_ll_unlink(fOps, dir->id, name);
                        RETURN_ERROR(error);
                }
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(dir, name, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // create the file
                fuseError = fuse_fs_create(fFS, path, mode, cookie);
                if (fuseError != 0)
                        RETURN_ERROR(fuseError);

                // get the node
                error = _GetNode(dir, name, &node);
                if (error != B_OK) {
                        // This is bad. We've create the file successfully, but couldn't get
                        // the node. Close the file and delete the entry.
                        fuse_fs_flush(fFS, path, cookie);
                        fuse_fs_release(fFS, path, cookie);
                        fuse_fs_unlink(fFS, path);
                        RETURN_ERROR(error);
                }
        }

        // mark the dir and the node dirty
        AutoLocker<Locker> locker(fLock);
        dir->dirty = true;
        node->dirty = true;
        locker.Unlock();

        // send node monitoring message
        UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0, dir->id,
                node->id, NULL, name);

        cookieDeleter.Detach();
        *_cookie = cookie;
        *_vnid = node->id;

        return B_OK;
}


status_t
FUSEVolume::Open(void* _node, int openMode, void** _cookie)
{
        FUSENode* node = (FUSENode*)_node;
        PRINT(("FUSEVolume::Open(%p (%" B_PRId64 "), %#x)\n", node, node->id,
                openMode));

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        bool truncate = (openMode & O_TRUNC) != 0;
        openMode &= ~O_TRUNC;

        // allocate a file cookie
        FileCookie* cookie = new(std::nothrow) FileCookie(openMode);
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);
        ObjectDeleter<FileCookie> cookieDeleter(cookie);

        char path[B_PATH_NAME_LENGTH];
        size_t pathLen;

        int fuseError;
        struct fuse_file_info llCookie = { 0 };
                // FIXME store this in the FileCookie for lowlevel streams, we'll need it in read, write...
        if (fOps != NULL) {
                llCookie.flags = openMode;
                if (S_ISDIR(node->type))
                        fuseError = fuse_ll_opendir(fOps, node->id, &llCookie);
                else
                        fuseError = fuse_ll_open(fOps, node->id, &llCookie);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // open the file
                fuseError = fuse_fs_open(fFS, path, cookie);
        }

        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // truncate the file, if requested
        if (truncate) {
                if (fOps != NULL) {
                        struct stat st;
                        st.st_size = 0;
                        fuseError = fuse_ll_setattr(fOps, node->id, &st, FUSE_SET_ATTR_SIZE);

                        if (fuseError != 0) {
                                fuse_ll_flush(fOps, node->id, &llCookie);
                                fuse_ll_release(fOps, node->id, &llCookie);
                                RETURN_ERROR(fuseError);
                        }
                } else {
                        fuseError = fuse_fs_ftruncate(fFS, path, 0, cookie);
                        if (fuseError == ENOSYS) {
                                // Fallback to truncate if ftruncate is not implemented
                                fuseError = fuse_fs_truncate(fFS, path, 0);
                        }
                        if (fuseError != 0) {
                                fuse_fs_flush(fFS, path, cookie);
                                fuse_fs_release(fFS, path, cookie);
                                RETURN_ERROR(fuseError);
                        }
                }

                // mark the node dirty
                AutoLocker<Locker> locker(fLock);
                node->dirty = true;

                // send node monitoring message
                UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED,
                        B_STAT_SIZE | B_STAT_MODIFICATION_TIME, fID, 0, 0, node->id, NULL,
                        NULL);
        }

        if (S_ISREG(node->type)) {
                // The caching logic does not seem to work quite right with many
                // filesystems (e.g. sshfs is one such): read past the end of a file
                // returns errors instead of no data, for instance.
#if 0
                if (cookie->direct_io || llCookie.direct_io) {
                        if (node->cacheCount > 0) {
                                // In some very rare cases, for the same node, the first `open`
                                // indicates that caching is allowed (by not setting `direct_io`),
                                // but a subsequent `open` on the same node indicates that it is
                                // NOT allowed (by setting `direct_io` to 1).
                                debugger("FUSEVolume::Open(): inconsistent direct_io flags!");
                                UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id);
                                node->cacheCount = 0;
                        }
                } else {
                        if (node->cacheCount == 0) {
                                struct stat st;
                                if (fOps != NULL) {
                                        fuseError = fuse_ll_getattr(fOps, node->id, &st);
                                } else {
                                        fuseError = fuse_fs_getattr(fFS, path, &st);
                                }
                                if (fuseError != 0) {
                                        RETURN_ERROR(fuseError);
                                }
                                status_t error = UserlandFS::KernelEmu::file_cache_create(GetID(), node->id, st.st_size);
                                if (error != B_OK) {
                                        RETURN_ERROR(error);
                                }
                        }
                        // Increment cacheCount by extra 1 if the cache is kept to prevent
                        // the cache from being deleted at close().
                        node->cacheCount += 1 + cookie->keep_cache + llCookie.keep_cache;
                }
#endif
        }

        cookieDeleter.Detach();
        *_cookie = cookie;

        return B_OK;
}


status_t
FUSEVolume::Close(void* _node, void* _cookie)
{
        FUSENode* node = (FUSENode*)_node;
        FileCookie* cookie = (FileCookie*)_cookie;

        RWLockableReadLocker cookieLocker(this, cookie);

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;

        if (fOps != NULL) {
                fuseError = fuse_ll_flush(fOps, node->id, cookie);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // flush the file
                fuseError = fuse_fs_flush(fFS, path, cookie);
        }
        if (fuseError != 0)
                return fuseError;

        if (S_ISREG(node->type) && node->cacheCount > 0) {
                --node->cacheCount;
                if (node->cacheCount == 0) {
                        UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id);
                }
        }

        return B_OK;
}


status_t
FUSEVolume::FreeCookie(void* _node, void* _cookie)
{
        FUSENode* node = (FUSENode*)_node;
        FileCookie* cookie = (FileCookie*)_cookie;

        // no need to lock the cookie here, as no-one else uses it anymore

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        ObjectDeleter<FileCookie> cookieDeleter(cookie);

        int fuseError;
        if (fOps) {
                fuseError = fuse_ll_release(fOps, node->id, cookie);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // release the file
                fuseError = fuse_fs_release(fFS, path, cookie);
        }

        if (fuseError != 0)
                return fuseError;

        return B_OK;
}


status_t
FUSEVolume::Read(void* _node, void* _cookie, off_t pos, void* buffer,
        size_t bufferSize, size_t* _bytesRead)
{
        FUSENode* node = (FUSENode*)_node;
        FileCookie* cookie = (FileCookie*)_cookie;

        RWLockableReadLocker cookieLocker(this, cookie);

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        *_bytesRead = bufferSize;
        status_t error = B_OK;

        if (S_ISREG(node->type) && node->cacheCount > 0) {
                error = UserlandFS::KernelEmu::file_cache_read(GetID(), node->id, cookie, pos,
                        buffer, _bytesRead);
        } else
                error = _InternalIO(node, cookie, NULL, pos, (char *)buffer, *_bytesRead, false);

        if (error != B_OK)
                RETURN_ERROR(error);

        return B_OK;
}


status_t
FUSEVolume::Write(void* _node, void* _cookie, off_t pos, const void* buffer,
        size_t bufferSize, size_t* _bytesWritten)
{
        FUSENode* node = (FUSENode*)_node;
        FileCookie* cookie = (FileCookie*)_cookie;

        RWLockableReadLocker cookieLocker(this, cookie);

        *_bytesWritten = 0;

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        *_bytesWritten = bufferSize;
        status_t error = B_OK;

        if (S_ISREG(node->type) && node->cacheCount > 0) {
                error = UserlandFS::KernelEmu::file_cache_write(GetID(), node->id, cookie, pos,
                        buffer, _bytesWritten);
        } else
                error = _InternalIO(node, cookie, NULL, pos, (char *)buffer, *_bytesWritten, true);

        if (error != B_OK)
                RETURN_ERROR(error);

        // mark the node dirty
        AutoLocker<Locker> locker(fLock);
        node->dirty = true;

        // send node monitoring message
        UserlandFS::KernelEmu::notify_listener(B_STAT_CHANGED,
                B_STAT_SIZE | B_STAT_MODIFICATION_TIME, fID, 0, 0, node->id, NULL,
                NULL);
                // TODO: The size possibly doesn't change.
                // TODO: Avoid message flooding -- use a timeout and set the
                // B_STAT_INTERIM_UPDATE flag.

        return B_OK;
}


// #pragma mark - directories


status_t
FUSEVolume::CreateDir(void* _dir, const char* name, int mode)
{
        FUSENode* dir = (FUSENode*)_dir;
        PRINT(("FUSEVolume::CreateDir(%p (%" B_PRId64 "), \"%s\", %#x)\n", dir,
                dir->id, name, mode));

        // lock the directory
        NodeWriteLocker nodeLocker(this, dir, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_mkdir(fOps, dir->id, name, mode);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the entry
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(dir, name, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // create the dir
                fuseError = fuse_fs_mkdir(fFS, path, mode);
        }
        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // mark the dir dirty
        AutoLocker<Locker> locker(fLock);
        dir->dirty = true;

        // send node monitoring message
        ino_t nodeID;
        if (_GetNodeID(dir, name, &nodeID)) {
                UserlandFS::KernelEmu::notify_listener(B_ENTRY_CREATED, 0, fID, 0,
                        dir->id, nodeID, NULL, name);
        }

        return B_OK;
}


status_t
FUSEVolume::RemoveDir(void* _dir, const char* name)
{
        FUSENode* dir = (FUSENode*)_dir;
        PRINT(("FUSEVolume::RemoveDir(%p (%" B_PRId64 "), \"%s\")\n", dir, dir->id,
                name));

        // lock the directory
        NodeWriteLocker nodeLocker(this, dir, false);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        // get the node ID (for the node monitoring message)
        ino_t nodeID;
        bool doNodeMonitoring = _GetNodeID(dir, name, &nodeID);

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_rmdir(fOps, dir->id, name);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the entry
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(dir, name, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // remove the dir
                fuseError = fuse_fs_rmdir(fFS, path);
        }
        if (fuseError != 0)
                RETURN_ERROR(fuseError);

        // remove the entry
        AutoLocker<Locker> locker(fLock);
        _RemoveEntry(dir, name);

        // mark the parent dir dirty
        dir->dirty = true;

        // send node monitoring message
        if (doNodeMonitoring) {
                UserlandFS::KernelEmu::notify_listener(B_ENTRY_REMOVED, 0, fID, 0,
                        dir->id, nodeID, NULL, name);
        }

        return B_OK;
}


status_t
FUSEVolume::OpenDir(void* _node, void** _cookie)
{
        FUSENode* node = (FUSENode*)_node;
        PRINT(("FUSEVolume::OpenDir(%p (%" B_PRId64 "), %p)\n", node, node->id,
                _cookie));

        // lock the parent directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        // allocate a dir cookie
        DirCookie* cookie = new(std::nothrow) DirCookie;
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);
        ObjectDeleter<DirCookie> cookieDeleter(cookie);

        if (fOps) {
                int fuseError = fuse_ll_opendir(fOps, node->id, cookie);
                if (fuseError != 0)
                        return fuseError;
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                if (fFS->ops.readdir == NULL && fFS->ops.getdir != NULL) {
                        // no open call -- the FS only supports the deprecated getdir()
                        // interface
                        cookie->getdirInterface = true;
                } else {
                        // open the dir
                        int fuseError = fuse_fs_opendir(fFS, path, cookie);
                        if (fuseError != 0)
                                return fuseError;
                }
        }

        cookieDeleter.Detach();
        *_cookie = cookie;

        return B_OK;
}


status_t
FUSEVolume::CloseDir(void* node, void* _cookie)
{
        return B_OK;
}


status_t
FUSEVolume::FreeDirCookie(void* _node, void* _cookie)
{
        FUSENode* node = (FUSENode*)_node;
        DirCookie* cookie = (DirCookie*)_cookie;

        // lock the parent directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        ObjectDeleter<DirCookie> cookieDeleter(cookie);

        int fuseError;
        if (fOps != NULL) {
                fuseError = fuse_ll_releasedir(fOps, node->id, cookie);
        } else {
                if (cookie->getdirInterface)
                        return B_OK;

                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen;
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                // release the dir
                fuseError = fuse_fs_releasedir(fFS, path, cookie);
        }

        if (fuseError != 0)
                return fuseError;

        return B_OK;
}


status_t
FUSEVolume::ReadDir(void* _node, void* _cookie, void* buffer, size_t bufferSize,
        uint32 count, uint32* _countRead)
{
        PRINT(("FUSEVolume::ReadDir(%p, %p, %p, %" B_PRIuSIZE ", %" B_PRId32 ")\n",
                _node, _cookie, buffer, bufferSize, count));
        *_countRead = 0;

        FUSENode* node = (FUSENode*)_node;
        DirCookie* cookie = (DirCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        uint32 countRead = 0;
        status_t readDirError = B_OK;

        AutoLocker<Locker> locker(fLock);

        if (cookie->entryCache == NULL) {
                // We don't have an entry cache (yet), so we need to ask the client
                // file system to read the directory.

                locker.Unlock();

                // lock the directory
                NodeReadLocker nodeLocker(this, node, false);
                if (nodeLocker.Status() != B_OK)
                        RETURN_ERROR(nodeLocker.Status());

                locker.Lock();

                ReadDirBuffer readDirBuffer(this, node, cookie, buffer, bufferSize, count);
                off_t offset = cookie->currentEntryOffset;

                // read the dir
                int fuseError;
                if (fOps != NULL) {
                        locker.Unlock();

                        // TODO pass the cookie from opendir here instead of NULL
                        fuseError = fuse_ll_readdir(fOps, node->id, &readDirBuffer, (char*)buffer, bufferSize,
                                &_AddReadDirEntryLowLevel, offset, NULL);

                        // The request filler may or may not be used. If the filesystem decides that it has
                        // already cached the directory, it can reply with an already filled buffer from a
                        // previous run. So, we can't rely on any updates done to the cookie by that function.
                        // So we need to check the number of entries in the buffer, and advance the
                        // currentEntryOffset.
                        if (fuseError > 0) {
                                struct dirent* dirent = (struct dirent*)buffer;
                                while (countRead < count
                                        && (char*)dirent + dirent->d_reclen <= (char*)buffer + fuseError) {
                                        countRead++;
                                        dirent = (struct dirent*)(((char*)dirent) + dirent->d_reclen);
                                        if (dirent->d_reclen == 0)
                                                break;
                                }
                                cookie->currentEntryOffset += (char*)dirent - (char*)buffer;

                                fuseError = 0;
                        }
                        readDirError = 0;
                } else {
                        // get a path for the node
                        char path[B_PATH_NAME_LENGTH];
                        size_t pathLen;
                        status_t error = _BuildPath(node, path, pathLen);
                        if (error != B_OK)
                                RETURN_ERROR(error);

                        locker.Unlock();

                        if (cookie->getdirInterface) {
PRINT(("  using getdir() interface\n"));
                                fuseError = fFS->ops.getdir(path, (fuse_dirh_t)&readDirBuffer,
                                        &_AddReadDirEntryGetDir);
                        } else {
PRINT(("  using readdir() interface\n"));
                                fuseError = fuse_fs_readdir(fFS, path, &readDirBuffer,
                                        &_AddReadDirEntry, offset, cookie);
                        }

                        countRead = readDirBuffer.entriesRead;
                        readDirError = readDirBuffer.error;
                }

                if (fuseError != 0)
                        return fuseError;

                locker.Lock();

        }

        if (cookie->entryCache != NULL) {
                // we're using an entry cache -- read into the buffer what we can
                dirent* entryBuffer = (dirent*)buffer;
                while (countRead < count
                        && cookie->entryCache->ReadDirent(cookie->currentEntryIndex, fID,
                                countRead + 1 < count, entryBuffer, bufferSize)) {
                        countRead++;
                        cookie->currentEntryIndex++;
                        bufferSize -= entryBuffer->d_reclen;
                        entryBuffer
                                = (dirent*)((uint8*)entryBuffer + entryBuffer->d_reclen);
                }
        }

        *_countRead = countRead;
        return countRead > 0 ? B_OK : readDirError;
}


status_t
FUSEVolume::RewindDir(void* _node, void* _cookie)
{
PRINT(("FUSEVolume::RewindDir(%p, %p)\n", _node, _cookie));
        DirCookie* cookie = (DirCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        if (cookie->getdirInterface) {
                delete cookie->entryCache;
                cookie->entryCache = NULL;
                cookie->currentEntryIndex = 0;
        } else {
                cookie->currentEntryOffset = 0;
        }

        return B_OK;
}


// #pragma mark - attribute directories


// OpenAttrDir
status_t
FUSEVolume::OpenAttrDir(void* _node, void** _cookie)
{
        // allocate an attribute directory cookie
        AttrDirCookie* cookie = new(std::nothrow) AttrDirCookie;
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);

        *_cookie = cookie;

        return B_OK;
}


// CloseAttrDir
status_t
FUSEVolume::CloseAttrDir(void* node, void* cookie)
{
        return B_OK;
}


// FreeAttrDirCookie
status_t
FUSEVolume::FreeAttrDirCookie(void* _node, void* _cookie)
{
        delete (AttrDirCookie*)_cookie;
        return B_OK;
}


// ReadAttrDir
status_t
FUSEVolume::ReadAttrDir(void* _node, void* _cookie, void* buffer,
        size_t bufferSize, uint32 count, uint32* _countRead)
{
        FUSENode* node = (FUSENode*)_node;
        AttrDirCookie* cookie = (AttrDirCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        *_countRead = 0;

        // lock the directory
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        char path[B_PATH_NAME_LENGTH];
        size_t pathLen;
        if (fOps == NULL) {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                status_t error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();
        }

        if (!cookie->IsValid()) {
                // cookie not yet valid -- get the length of the list
                int listSize;
                if (fOps != NULL)
                        listSize = fuse_ll_listxattr(fOps, node->id, NULL, 0);
                else
                        listSize = fuse_fs_listxattr(fFS, path, NULL, 0);

                if (listSize < 0)
                        RETURN_ERROR(listSize);

                while (true) {
                        // allocate space for the listing
                        status_t error = cookie->Allocate(listSize);
                        if (error != B_OK)
                                RETURN_ERROR(error);

                        // read the listing
                        int bytesRead;
                        if (fOps != NULL) {
                                bytesRead = fuse_ll_listxattr(fOps, node->id, cookie->AttributesBuffer(),
                                        listSize);
                        } else
                                bytesRead = fuse_fs_listxattr(fFS, path, cookie->AttributesBuffer(), listSize);
                        if (bytesRead < 0)
                                RETURN_ERROR(bytesRead);

                        if (bytesRead == listSize)
                                break;

                        // attributes listing changed -- reread it
                        listSize = bytesRead;
                }

                cookie->SetValid(true);
        }

        // we have a valid cookie now -- get the next entries from the cookie
        uint32 countRead = 0;
        dirent* entryBuffer = (dirent*)buffer;
        while (countRead < count
                && cookie->ReadNextEntry(fID, node->id, countRead + 1 < count,
                        entryBuffer, bufferSize)) {
                countRead++;
                bufferSize -= entryBuffer->d_reclen;
                entryBuffer = (dirent*)((uint8*)entryBuffer + entryBuffer->d_reclen);
        }

        *_countRead = countRead;
        return B_OK;
}


// RewindAttrDir
status_t
FUSEVolume::RewindAttrDir(void* _node, void* _cookie)
{
        AttrDirCookie* cookie = (AttrDirCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        cookie->Clear();

        return B_OK;
}


// #pragma mark - attributes


status_t
FUSEVolume::OpenAttr(void* _node, const char* name, int openMode,
        void** _cookie)
{
        FUSENode* node = (FUSENode*)_node;

        // lock the node
        NodeReadLocker nodeLocker(this, node, true);
        if (nodeLocker.Status() != B_OK)
                RETURN_ERROR(nodeLocker.Status());

        if (openMode != O_RDONLY) {
                // Write support currently not implemented
                RETURN_ERROR(B_UNSUPPORTED);
        }

        char path[B_PATH_NAME_LENGTH];
        size_t pathLen;
        status_t error;
        int attrSize;
        if (fOps != NULL) {
                attrSize = fuse_ll_getxattr(fOps, node->id, name, NULL, 0);
        } else {
                AutoLocker<Locker> locker(fLock);

                // get a path for the node
                error = _BuildPath(node, path, pathLen);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Unlock();

                attrSize = fuse_fs_getxattr(fFS, path, name, NULL, 0);
        }

        if (attrSize < 0) {
                if (strcmp(name, kAttrMimeTypeName) == 0) {
                        // Return a fake MIME type attribute based on the file extension
                        const char* mimeType = NULL;
                        error = set_mime(&mimeType, S_ISDIR(node->type) ? NULL : &path[0]);
                        if (error != B_OK)
                                return error;
                        *_cookie = new(std::nothrow)AttrCookie(name, mimeType);
                        return B_OK;
                }

                // Reading attribute failed
                return attrSize;
        }

        AttrCookie* cookie = new(std::nothrow)AttrCookie(name);
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);
        error = cookie->Allocate(attrSize);
        if (error != B_OK) {
                delete cookie;
                RETURN_ERROR(error);
        }

        int bytesRead;
        if (fOps != NULL)
                bytesRead = fuse_ll_getxattr(fOps, node->id, name, cookie->Buffer(), attrSize);
        else
                bytesRead = fuse_fs_getxattr(fFS, path, name, cookie->Buffer(), attrSize);

        if (bytesRead < 0) {
                delete cookie;
                return bytesRead;
        }

        *_cookie = cookie;

        return B_OK;
}


status_t
FUSEVolume::CloseAttr(void* _node, void* _cookie)
{
        return B_OK;
}


status_t
FUSEVolume::FreeAttrCookie(void* _node, void* _cookie)
{
        delete (AttrCookie*)_cookie;
        return B_OK;
}


status_t
FUSEVolume::ReadAttr(void* _node, void* _cookie, off_t pos, void* buffer,
        size_t bufferSize, size_t* bytesRead)
{
        AttrCookie* cookie = (AttrCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        if (!cookie->IsValid())
                RETURN_ERROR(B_BAD_VALUE);

        cookie->Read(buffer, bufferSize, pos, bytesRead);

        return B_OK;
}


status_t
FUSEVolume::ReadAttrStat(void* _node, void* _cookie, struct stat* st)
{
        AttrCookie* cookie = (AttrCookie*)_cookie;

        RWLockableWriteLocker cookieLocker(this, cookie);

        if (!cookie->IsValid())
                RETURN_ERROR(B_BAD_VALUE);

        st->st_size = cookie->Size();
        st->st_type = cookie->Type();

        return B_OK;
}


// #pragma mark -


ino_t
FUSEVolume::_GenerateNodeID()
{
        ino_t id;
        do {
                id = fNextNodeID++;
        } while (fNodes.Lookup(id) != NULL);

        return id;
}


/*!     Gets the ID of the node the entry specified by \a dir and \a entryName
        refers to. The ID is returned via \a _nodeID. The caller doesn't get a
        reference to the node.
*/
bool
FUSEVolume::_GetNodeID(FUSENode* dir, const char* entryName, ino_t* _nodeID)
{
        while (true) {
                AutoLocker<Locker> locker(fLock);

                FUSENode* node;
                status_t error = _InternalGetNode(dir, entryName, &node, locker);
                if (error != B_OK)
                        return false;

                if (node == NULL)
                        continue;

                *_nodeID = node->id;
                _PutNode(node);

                return true;
        }
}


/*!     Gets the node the entry specified by \a dir and \a entryName refers to. The
        found node is returned via \a _node. The caller gets a reference to the node
        as well as a vnode reference.
*/
status_t
FUSEVolume::_GetNode(FUSENode* dir, const char* entryName, FUSENode** _node)
{
        while (true) {
                AutoLocker<Locker> locker(fLock);

                FUSENode* node;
                status_t error = _InternalGetNode(dir, entryName, &node, locker);
                if (error != B_OK)
                        return error;

                if (node == NULL)
                        continue;

                ino_t nodeID = node->id;

                locker.Unlock();

                // get a reference for the caller
                void* privateNode;
                error = UserlandFS::KernelEmu::get_vnode(fID, nodeID, &privateNode);
                if (error != B_OK)
                        RETURN_ERROR(error);

                locker.Lock();

                if (privateNode != node) {
                        // weird, the node changed!
                        ERROR(("FUSEVolume::_GetNode(): cookie for node %" B_PRId64
                                " changed: expected: %p, got: %p\n", nodeID, node,
                                privateNode));
                        UserlandFS::KernelEmu::put_vnode(fID, nodeID);
                        _PutNode(node);
                        continue;
                }

                // Put the node reference we got from _InternalGetNode. We've now got
                // a reference from get_vnode().
                _PutNode(node);

                *_node = node;
                return B_OK;
        }
}


status_t
FUSEVolume::_InternalGetNode(FUSENode* dir, const char* entryName,
        FUSENode** _node, AutoLocker<Locker>& locker)
{
        // handle special cases
        if (strcmp(entryName, ".") == 0) {
                // same directory
                if (!S_ISDIR(dir->type))
                        RETURN_ERROR(B_NOT_A_DIRECTORY);

                dir->refCount++;
                *_node = dir;
                return B_OK;
        }

        if (strcmp(entryName, "..") == 0) {
                // parent directory
                if (!S_ISDIR(dir->type))
                        RETURN_ERROR(B_NOT_A_DIRECTORY);

                FUSEEntry* entry = dir->entries.Head();
                if (entry == NULL)
                        RETURN_ERROR(B_ENTRY_NOT_FOUND);

                entry->parent->refCount++;
                *_node = entry->parent;
                return B_OK;
        }

        // lookup the entry in the table
        FUSEEntry* entry = fEntries.Lookup(FUSEEntryRef(dir->id, entryName));
        if (entry != NULL) {
                entry->node->refCount++;
                *_node = entry->node;
                return B_OK;
        }

        int fuseError;
        struct stat st;
        if (fOps != NULL) {
                fuseError = fuse_ll_lookup(fOps, dir->id, entryName, &st);
        } else {
                // construct a path for the entry
                char path[B_PATH_NAME_LENGTH];
                size_t pathLen = 0;
                status_t error = _BuildPath(dir, entryName, path, pathLen);
                if (error != B_OK)
                        return error;

                locker.Unlock();

                // stat the path
                fuseError = fuse_fs_getattr(fFS, path, &st);
        }
        if (fuseError != 0)
                return fuseError;

        // lookup the entry in the table again
        entry = fEntries.Lookup(FUSEEntryRef(dir->id, entryName));
        if (entry != NULL) {
                // check whether the node still matches
                if (entry->node->id == st.st_ino) {
                        entry->node->refCount++;
                        *_node = entry->node;
                } else {
                        // nope, something changed -- return a NULL node and let the caller
                        // call us again
                        *_node = NULL;
                }

                return B_OK;
        }

        // lookup the node in the table
        FUSENode* node = NULL;
        if (fUseNodeIDs)
                node = fNodes.Lookup(st.st_ino);
        else
                st.st_ino = _GenerateNodeID();

        if (node == NULL) {
                // no node yet -- create one
                node = new(std::nothrow) FUSENode(st.st_ino, st.st_mode & S_IFMT);
                if (node == NULL)
                        RETURN_ERROR(B_NO_MEMORY);

                fNodes.Insert(node);
        } else {
                // get a node reference for the entry
                node->refCount++;
        }

        // create the entry
        entry = FUSEEntry::Create(dir, entryName, node);
        if (entry == NULL) {
                _PutNode(node);
                RETURN_ERROR(B_NO_MEMORY);
        }

        dir->refCount++;
                // dir reference for the entry

        fEntries.Insert(entry);
        node->entries.Add(entry);

        locker.Unlock();

        // get a reference for the caller
        node->refCount++;

        *_node = node;
        return B_OK;
}


void
FUSEVolume::_PutNode(FUSENode* node)
{
        if (--node->refCount == 0) {
                fNodes.Remove(node);
                if (node->cacheCount != 0)
                        UserlandFS::KernelEmu::file_cache_delete(GetID(), node->id);
                delete node;
        }
}


void
FUSEVolume::_PutNodes(FUSENode* const* nodes, int32 count)
{
        for (int32 i = 0; i < count; i++)
                _PutNode(nodes[i]);
}


/*!     Volume must be locked. The entry's directory must be write locked.
 */
void
FUSEVolume::_RemoveEntry(FUSEEntry* entry)
{
        fEntries.Remove(entry);
        entry->node->entries.Remove(entry);
        _PutNode(entry->node);
        _PutNode(entry->parent);
        delete entry;
}


/*!     Volume must be locked. The directory must be write locked.
 */
status_t
FUSEVolume::_RemoveEntry(FUSENode* dir, const char* name)
{
        FUSEEntry* entry = fEntries.Lookup(FUSEEntryRef(dir->id, name));
        if (entry == NULL)
                return B_ENTRY_NOT_FOUND;

        _RemoveEntry(entry);
        return B_OK;
}


/*!     Volume must be locked. The directories must be write locked.
 */
status_t
FUSEVolume::_RenameEntry(FUSENode* oldDir, const char* oldName,
        FUSENode* newDir, const char* newName)
{
        FUSEEntry* entry = fEntries.Lookup(FUSEEntryRef(oldDir->id, oldName));
        if (entry == NULL)
                return B_ENTRY_NOT_FOUND;

        // get a node reference for the new entry
        FUSENode* node = entry->node;
        node->refCount++;

        // remove the old entry
        _RemoveEntry(entry);

        // make sure there's no entry in our way
        _RemoveEntry(newDir, newName);

        // create a new entry
        entry = FUSEEntry::Create(newDir, newName, node);
        if (entry == NULL) {
                _PutNode(node);
                RETURN_ERROR(B_NO_MEMORY);
        }

        newDir->refCount++;
                // dir reference for the entry

        fEntries.Insert(entry);
        node->entries.Add(entry);

        return B_OK;
}


/*!     Locks the given node and all of its ancestors up to the root. The given
        node is write-locked, if \a writeLock is \c true, read-locked otherwise. All
        ancestors are always read-locked in either case.

        If \a lockParent is \c true, the given node itself is ignored, but locking
        starts with the parent node of the given node (\a writeLock applies to the
        parent node then).

        If the method fails, none of the nodes is locked.

        The volume lock must not be held.
*/
status_t
FUSEVolume::_LockNodeChain(FUSENode* node, bool lockParent, bool writeLock)
{
        AutoLocker<Locker> locker(fLock);

        FUSENode* originalNode = node;

        if (lockParent && node != NULL)
                node = node->Parent();

        if (node == NULL)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        LockIterator iterator(this, node, writeLock, NULL);

        bool done;
        do {
                bool volumeUnlocked;
                status_t error = iterator.LockNext(&done, &volumeUnlocked);
                if (error != B_OK)
                        RETURN_ERROR(error);

                if (volumeUnlocked) {
                        // check whether we're still locking the right node
                        if (lockParent && originalNode->Parent() != node) {
                                // We don't -- unlock everything and try again.
                                node = originalNode->Parent();
                                iterator.SetTo(this, node, writeLock, NULL);
                        }
                }
        } while (!done);

        // Fail, if we couldn't lock all nodes up to the root.
        if (iterator.lastLockedNode != fRootNode)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        iterator.Detach();
        return B_OK;
}


void
FUSEVolume::_UnlockNodeChain(FUSENode* node, bool parent, bool writeLock)
{
        AutoLocker<Locker> locker(fLock);

        if (parent && node != NULL)
                node = node->Parent();

        _UnlockNodeChainInternal(node, writeLock, NULL, NULL);
}


/*!     Unlocks all nodes from \a node up to (and including) \a stopNode (if
        \c NULL, it is ignored). If \a stopBeforeNode is given, the method stops
        before unlocking that node.
        The volume lock must be held.
 */
void
FUSEVolume::_UnlockNodeChainInternal(FUSENode* node, bool writeLock,
        FUSENode* stopNode, FUSENode* stopBeforeNode)
{
        FUSENode* originalNode = node;

        while (node != NULL && node != stopBeforeNode) {
                FUSENode* parent = node->Parent();

                fLockManager.GenericUnlock(node == originalNode && writeLock, node);
                _PutNode(node);

                if (node == stopNode || parent == node)
                        break;

                node = parent;
        }
}


status_t
FUSEVolume::_LockNodeChains(FUSENode* node1, bool lockParent1, bool writeLock1,
        FUSENode* node2, bool lockParent2, bool writeLock2)
{
        // Since in this case locking is more complicated, we use a helper method.
        // It does the main work, but simply returns telling us to retry when the
        // node hierarchy changes.
        bool retry;
        do {
                status_t error = _LockNodeChainsInternal(node1, lockParent1, writeLock1,
                        node2, lockParent2, writeLock2, &retry);
                if (error != B_OK)
                        return error;
        } while (retry);

        return B_OK;
}


status_t
FUSEVolume::_LockNodeChainsInternal(FUSENode* node1, bool lockParent1,
        bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2,
        bool* _retry)
{
        // Locking order:
        // * A child of a node has to be locked before its parent.
        // * Sibling nodes have to be locked in ascending node ID order.
        //
        // This implies the following locking algorithm:
        // * We find the closest common ancestor of the two given nodes (might even
        //   be one of the given nodes).
        // * We lock all ancestors on one branch (the one with the lower common
        //   ancestor child node ID), but not including the common ancestor.
        // * We lock all ancestors on the other branch, not including the common
        //   ancestor.
        // * We lock the common ancestor and all of its ancestors up to the root
        //   node.
        //
        // When the hierarchy changes while we're waiting for a lock, we recheck the
        // conditions and in doubt have to be restarted.

        AutoLocker<Locker> locker(fLock);

        FUSENode* originalNode1 = node1;
        FUSENode* originalNode2 = node2;

        if (lockParent1 && node1 != NULL)
                node1 = node1->Parent();

        if (lockParent2 && node2 != NULL)
                node2 = node2->Parent();

        if (node1 == NULL || node2 == NULL)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        // find the first common ancestor
        FUSENode* commonAncestor;
        bool inverseLockingOrder;
        if (!_FindCommonAncestor(node1, node2, &commonAncestor,
                        &inverseLockingOrder)) {
                RETURN_ERROR(B_ENTRY_NOT_FOUND);
        }

        // lock the both node chains up to (but not including) the common ancestor
        LockIterator iterator1(this, node1, writeLock1, commonAncestor);
        LockIterator iterator2(this, node2, writeLock2, commonAncestor);

        for (int i = 0; i < 2; i++) {
                LockIterator& iterator = (i == 0) != inverseLockingOrder
                        ? iterator1 : iterator2;

                // If the node is the common ancestor, don't enter the "do" loop, since
                // we don't have to lock anything here.
                if (iterator.firstNode == commonAncestor)
                        continue;

                bool done;
                do {
                        bool volumeUnlocked;
                        status_t error = iterator.LockNext(&done, &volumeUnlocked);
                        if (error != B_OK)
                                RETURN_ERROR(error);

                        if (volumeUnlocked) {
                                // check whether we're still locking the right nodes
                                if ((lockParent1 && originalNode1->Parent() != node1)
                                        || (lockParent2 && originalNode2->Parent() != node2)) {
                                        // We don't -- unlock everything and retry.
                                        *_retry = true;
                                        return B_OK;
                                }

                                // also recheck the common ancestor
                                FUSENode* newCommonParent;
                                bool newInverseLockingOrder;
                                if (!_FindCommonAncestor(node1, node2, &newCommonParent,
                                                &newInverseLockingOrder)) {
                                        RETURN_ERROR(B_ENTRY_NOT_FOUND);
                                }

                                if (newCommonParent != commonAncestor
                                        || inverseLockingOrder != newInverseLockingOrder) {
                                        // Something changed -- unlock everything and retry.
                                        *_retry = true;
                                        return B_OK;
                                }
                        }
                } while (!done);
        }

        // Continue locking from the common ancestor to the root. If one of the
        // given nodes is the common ancestor and shall be write locked, we need to
        // use the respective iterator.
        LockIterator& iterator = node2 == commonAncestor && writeLock2
                ? iterator2 : iterator1;
        iterator.SetStopBeforeNode(NULL);

        bool done;
        do {
                bool volumeUnlocked;
                status_t error = iterator.LockNext(&done, &volumeUnlocked);
                if (error != B_OK)
                        RETURN_ERROR(error);

                if (volumeUnlocked) {
                        // check whether we're still locking the right nodes
                        if ((lockParent1 && originalNode1->Parent() != node1)
                                || (lockParent2 && originalNode2->Parent() != node2)) {
                                // We don't -- unlock everything and retry.
                                *_retry = true;
                                return B_OK;
                        }

                        // Also recheck the common ancestor, if we have just locked it.
                        // Otherwise we can just continue to lock, since nothing below the
                        // previously locked node can have changed.
                        if (iterator.lastLockedNode == commonAncestor) {
                                FUSENode* newCommonParent;
                                bool newInverseLockingOrder;
                                if (!_FindCommonAncestor(node1, node2, &newCommonParent,
                                                &newInverseLockingOrder)) {
                                        RETURN_ERROR(B_ENTRY_NOT_FOUND);
                                }

                                if (newCommonParent != commonAncestor
                                        || inverseLockingOrder != newInverseLockingOrder) {
                                        // Something changed -- unlock everything and retry.
                                        *_retry = true;
                                        return B_OK;
                                }
                        }
                }
        } while (!done);

        // Fail, if we couldn't lock all nodes up to the root.
        if (iterator.lastLockedNode != fRootNode)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        // everything went fine
        iterator1.Detach();
        iterator2.Detach();

        *_retry = false;
        return B_OK;
}


void
FUSEVolume::_UnlockNodeChains(FUSENode* node1, bool lockParent1,
        bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2)
{
        AutoLocker<Locker> locker(fLock);

        if (lockParent1 && node1 != NULL)
                node1 = node1->Parent();

        if (lockParent2 && node2 != NULL)
                node2 = node2->Parent();

        if (node1 == NULL || node2 == NULL)
                return;

        // find the common ancestor
        FUSENode* commonAncestor;
        bool inverseLockingOrder;
        if (!_FindCommonAncestor(node1, node2, &commonAncestor,
                        &inverseLockingOrder)) {
                return;
        }

        // Unlock one branch up to the common ancestor and then the complete other
        // branch up to the root. If one of the given nodes is the common ancestor,
        // we need to make sure, we write-unlock it, if requested.
        if (node2 == commonAncestor && writeLock2) {
                _UnlockNodeChainInternal(node1, writeLock1, NULL, commonAncestor);
                _UnlockNodeChainInternal(node2, writeLock2, NULL, NULL);
        } else {
                _UnlockNodeChainInternal(node2, writeLock2, NULL, commonAncestor);
                _UnlockNodeChainInternal(node1, writeLock1, NULL, NULL);
        }
}


bool
FUSEVolume::_FindCommonAncestor(FUSENode* node1, FUSENode* node2,
        FUSENode** _commonAncestor, bool* _inverseLockingOrder)
{
        // handle trivial special case -- both nodes are the same
        if (node1 == node2) {
                *_commonAncestor = node1;
                *_inverseLockingOrder = false;
                return true;
        }

        // get the ancestors of both nodes
        FUSENode* ancestors1[kMaxNodeTreeDepth];
        FUSENode* ancestors2[kMaxNodeTreeDepth];
        uint32 count1;
        uint32 count2;

        if (!_GetNodeAncestors(node1, ancestors1, &count1)
                || !_GetNodeAncestors(node2, ancestors2, &count2)) {
                return false;
        }

        // find the first ancestor not common to both nodes
        uint32 index = 0;
        for (; index < count1 && index < count2; index++) {
                FUSENode* ancestor1 = ancestors1[count1 - index - 1];
                FUSENode* ancestor2 = ancestors2[count2 - index - 1];
                if (ancestor1 != ancestor2) {
                        *_commonAncestor = ancestors1[count1 - index];
                        *_inverseLockingOrder = ancestor1->id > ancestor2->id;
                        return true;
                }
        }

        // one node is an ancestor of the other
        *_commonAncestor = ancestors1[count1 - index];
        *_inverseLockingOrder = index == count1;
        return true;
}


bool
FUSEVolume::_GetNodeAncestors(FUSENode* node, FUSENode** ancestors,
        uint32* _count)
{
        uint32 count = 0;
        while (node != NULL && count < kMaxNodeTreeDepth) {
                ancestors[count++] = node;

                if (node == fRootNode) {
                        *_count = count;
                        return true;
                }

                node = node->Parent();
        }

        // Either the node is not in the tree or we hit the array limit.
        return false;
}


status_t
FUSEVolume::_BuildPath(FUSENode* dir, const char* entryName, char* path,
        size_t& pathLen)
{
        // get the directory path
        status_t error = _BuildPath(dir, path, pathLen);
        if (error != B_OK)
                return error;

        if (path[pathLen - 1] != '/') {
                path[pathLen++] = '/';
                if (pathLen == B_PATH_NAME_LENGTH)
                        RETURN_ERROR(B_NAME_TOO_LONG);
        }

        // append the entry name
        size_t len = strlen(entryName);
        if (pathLen + len >= B_PATH_NAME_LENGTH)
                RETURN_ERROR(B_NAME_TOO_LONG);

        memcpy(path + pathLen, entryName, len + 1);
        pathLen += len;

        return B_OK;
}


status_t
FUSEVolume::_BuildPath(FUSENode* node, char* path, size_t& pathLen)
{
        if (node == fRootNode) {
                // we hit the root
                strcpy(path, "/");
                pathLen = 1;
                return B_OK;
        }

        // get an entry for the node and get its path
        FUSEEntry* entry = node->entries.Head();
        if (entry == NULL)
                RETURN_ERROR(B_ENTRY_NOT_FOUND);

        return _BuildPath(entry->parent, entry->name, path, pathLen);
}


/*static*/ int
FUSEVolume::_AddReadDirEntryLowLevel(void* _buffer, char* buf, size_t bufsize, const char* name,
        const struct stat* st, off_t offset)
{
        ReadDirBuffer* buffer = (ReadDirBuffer*)_buffer;

        ino_t nodeID = st != NULL ? st->st_ino : 0;
        int type = st != NULL ? st->st_mode & S_IFMT : 0;
        return buffer->volume->_AddReadDirEntryLowLevel(buffer, buf, bufsize, name, type, nodeID, offset);
}


/*static*/ int
FUSEVolume::_AddReadDirEntry(void* _buffer, const char* name,
        const struct stat* st, off_t offset)
{
        ReadDirBuffer* buffer = (ReadDirBuffer*)_buffer;

        ino_t nodeID = st != NULL ? st->st_ino : 0;
        int type = st != NULL ? st->st_mode & S_IFMT : 0;
        return buffer->volume->_AddReadDirEntry(buffer, name, type, nodeID, offset);
}


/*static*/ int
FUSEVolume::_AddReadDirEntryGetDir(fuse_dirh_t handle, const char* name,
        int type, ino_t nodeID)
{
        ReadDirBuffer* buffer = (ReadDirBuffer*)handle;
        return buffer->volume->_AddReadDirEntry(buffer, name, type << 12, nodeID, 0);
}


int
FUSEVolume::_AddReadDirEntryLowLevel(ReadDirBuffer* buffer, char* buf, size_t bufsize, const char* name,
        int type, ino_t nodeID, off_t offset)
{
        PRINT(("FUSEVolume::_AddReadDirEntryLowLevel(%p, \"%s\", %#x, %" B_PRId64 ", %"
                B_PRId64 "\n", buffer, name, type, nodeID, offset));

        AutoLocker<Locker> locker(fLock);

        size_t entryLen = 0;

        // create a node and an entry, if necessary
        ino_t dirID = buffer->directory->id;
        FUSEEntry* entry;
        if (strcmp(name, ".") == 0) {
                // current dir entry
                nodeID = dirID;
                type = S_IFDIR;
        } else if (strcmp(name, "..") == 0) {
                // parent dir entry
                FUSEEntry* parentEntry = buffer->directory->entries.Head();
                if (parentEntry == NULL) {
                        ERROR(("FUSEVolume::_AddReadDirEntry(): dir %" B_PRId64
                                " has no entry!\n", dirID));
                        return 0;
                }
                nodeID = parentEntry->parent->id;
                type = S_IFDIR;
        } else if ((entry = fEntries.Lookup(FUSEEntryRef(dirID, name))) == NULL) {
                // get the node
                FUSENode* node = NULL;
                if (fUseNodeIDs)
                        node = fNodes.Lookup(nodeID);
                else
                        nodeID = _GenerateNodeID();

                if (node == NULL) {
                        // no node yet -- create one

                        // If we don't have a valid type, we need to stat the node first.
                        if (type == 0) {
                                struct stat st;
                                int fuseError;
                                if (fOps != NULL) {
                                        fuseError = fuse_ll_getattr(fOps, node->id, &st);
                                } else {
                                        char path[B_PATH_NAME_LENGTH];
                                        size_t pathLen;
                                        status_t error = _BuildPath(buffer->directory, name, path,
                                                        pathLen);
                                        if (error != B_OK) {
                                                buffer->error = error;
                                                return 0;
                                        }

                                        locker.Unlock();

                                        // stat the path
                                        fuseError = fuse_fs_getattr(fFS, path, &st);
                                }

                                locker.Lock();

                                if (fuseError != 0) {
                                        buffer->error = fuseError;
                                        return 0;
                                }

                                type = st.st_mode & S_IFMT;
                        }

                        node = new(std::nothrow) FUSENode(nodeID, type);
                        if (node == NULL) {
                                buffer->error = B_NO_MEMORY;
                                return 1;
                        }
                        PRINT(("  -> create node: %p, id: %" B_PRId64 "\n", node, nodeID));

                        fNodes.Insert(node);
                } else {
                        // get a node reference for the entry
                        node->refCount++;
                }

                // create the entry
                entry = FUSEEntry::Create(buffer->directory, name, node);
                if (entry == NULL) {
                        _PutNode(node);
                        buffer->error = B_NO_MEMORY;
                        return 1;
                }

                buffer->directory->refCount++;
                        // dir reference for the entry

                fEntries.Insert(entry);
                node->entries.Add(entry);
        } else {
                // TODO: Check whether the node's ID matches the one we got (if any)!
                nodeID = entry->node->id;
                type = entry->node->type;
        }

        // fill in the dirent
        dirent* dirEntry = (dirent*)(buf);
        dirEntry->d_dev = fID;
        dirEntry->d_ino = nodeID;
        strcpy(dirEntry->d_name, name);

        // align the entry length, so the next dirent will be aligned
        entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1;
        entryLen = ROUNDUP(entryLen, 8);

        dirEntry->d_reclen = entryLen;

        // update the buffer
        buffer->usedSize += entryLen;

        return 0;
}


int
FUSEVolume::_AddReadDirEntry(ReadDirBuffer* buffer, const char* name,
        int type, ino_t nodeID, off_t offset)
{
        PRINT(("FUSEVolume::_AddReadDirEntry(%p, \"%s\", %#x, %" B_PRId64 ", %"
                B_PRId64 "\n", buffer, name, type, nodeID, offset));

        AutoLocker<Locker> locker(fLock);

        size_t entryLen = 0;
        if (offset != 0) {
                // does the caller want more entries?
                if (buffer->entriesRead == buffer->maxEntries)
                        return 1;

                // compute the entry length and check whether the entry still fits
                entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1;
                if (buffer->usedSize + entryLen > buffer->bufferSize)
                        return 1;
        }

        // create a node and an entry, if necessary
        ino_t dirID = buffer->directory->id;
        FUSEEntry* entry;
        if (strcmp(name, ".") == 0) {
                // current dir entry
                nodeID = dirID;
                type = S_IFDIR;
        } else if (strcmp(name, "..") == 0) {
                // parent dir entry
                FUSEEntry* parentEntry = buffer->directory->entries.Head();
                if (parentEntry == NULL) {
                        ERROR(("FUSEVolume::_AddReadDirEntry(): dir %" B_PRId64
                                " has no entry!\n", dirID));
                        return 0;
                }
                nodeID = parentEntry->parent->id;
                type = S_IFDIR;
        } else if ((entry = fEntries.Lookup(FUSEEntryRef(dirID, name))) == NULL) {
                // get the node
                FUSENode* node = NULL;
                if (fUseNodeIDs)
                        node = fNodes.Lookup(nodeID);
                else
                        nodeID = _GenerateNodeID();

                if (node == NULL) {
                        // no node yet -- create one

                        // If we don't have a valid type, we need to stat the node first.
                        if (type == 0) {
                                struct stat st;
                                int fuseError;
                                if (fOps != NULL) {
                                        fuseError = fuse_ll_getattr(fOps, node->id, &st);
                                } else {
                                        char path[B_PATH_NAME_LENGTH];
                                        size_t pathLen;
                                        status_t error = _BuildPath(buffer->directory, name, path,
                                                        pathLen);
                                        if (error != B_OK) {
                                                buffer->error = error;
                                                return 0;
                                        }

                                        locker.Unlock();

                                        // stat the path
                                        fuseError = fuse_fs_getattr(fFS, path, &st);
                                }

                                locker.Lock();

                                if (fuseError != 0) {
                                        buffer->error = fuseError;
                                        return 0;
                                }

                                type = st.st_mode & S_IFMT;
                        }

                        node = new(std::nothrow) FUSENode(nodeID, type);
                        if (node == NULL) {
                                buffer->error = B_NO_MEMORY;
                                return 1;
                        }
                        PRINT(("  -> create node: %p, id: %" B_PRId64 "\n", node, nodeID));

                        fNodes.Insert(node);
                } else {
                        // get a node reference for the entry
                        node->refCount++;
                }

                // create the entry
                entry = FUSEEntry::Create(buffer->directory, name, node);
                if (entry == NULL) {
                        _PutNode(node);
                        buffer->error = B_NO_MEMORY;
                        return 1;
                }

                buffer->directory->refCount++;
                        // dir reference for the entry

                fEntries.Insert(entry);
                node->entries.Add(entry);
        } else {
                // TODO: Check whether the node's ID matches the one we got (if any)!
                nodeID = entry->node->id;
                type = entry->node->type;
        }

        if (offset == 0) {
                // cache the entry
                if (buffer->cookie->entryCache == NULL) {
                        // no cache yet -- create it
                        buffer->cookie->entryCache = new(std::nothrow) DirEntryCache;
                        if (buffer->cookie->entryCache == NULL) {
                                buffer->error = B_NO_MEMORY;
                                return 1;
                        }
                }

                status_t error = buffer->cookie->entryCache->AddEntry(nodeID, name);
                if (error != B_OK) {
                        buffer->error = error;
                        return 1;
                }
        } else {
                // fill in the dirent
                dirent* dirEntry = (dirent*)((uint8*)buffer->buffer + buffer->usedSize);
                dirEntry->d_dev = fID;
                dirEntry->d_ino = nodeID;
                strcpy(dirEntry->d_name, name);

                if (buffer->entriesRead + 1 < buffer->maxEntries) {
                        // align the entry length, so the next dirent will be aligned
                        entryLen = ROUNDUP(entryLen, 8);
                        entryLen = std::min(entryLen,
                                buffer->bufferSize - buffer->usedSize);
                }

                dirEntry->d_reclen = entryLen;

                // update the buffer
                buffer->usedSize += entryLen;
                buffer->entriesRead++;
                buffer->cookie->currentEntryOffset = offset;
        }

        return 0;
}


status_t
FUSEVolume::_InternalIO(FUSENode* node, FileCookie* cookie, const char* path,
        off_t pos, char* buffer, size_t& length, bool write)
{
        PRINT(("FUSEVolume::_InternalIO(%p, %p, %s, %" B_PRIdOFF ", %p, %" B_PRIuSIZE ", %d)\n",
                node, cookie, path, pos, buffer, length, write));

        int fuseError;
        if (fOps != NULL) {
                if (write)
                        fuseError = fuse_ll_write(fOps, node->id, buffer, length, pos, cookie);
                else
                        fuseError = fuse_ll_read(fOps, node->id, buffer, length, pos, cookie);
        } else {
                char pathBuf[B_PATH_NAME_LENGTH];

                if (path == NULL) {
                        AutoLocker<Locker> locker(fLock);

                        size_t pathLen;
                        status_t error = _BuildPath(node, pathBuf, pathLen);
                        if (error != B_OK)
                                RETURN_ERROR(error);

                        locker.Unlock();

                        path = pathBuf;
                }

                if (write)
                        fuseError = fuse_fs_write(fFS, path, buffer, length, pos, cookie);
                else
                        fuseError = fuse_fs_read(fFS, path, buffer, length, pos, cookie);
        }

        if (fuseError < 0)
                return fuseError;

        length = fuseError;
        return B_OK;
}