root/src/add-ons/kernel/file_systems/nfs4/Inode.cpp
/*
 * Copyright 2012-2016 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Paweł Dziepak, pdziepak@quarnos.org
 */


#include "Inode.h"

#include <ctype.h>
#include <string.h>

#include <AutoDeleter.h>
#include <fs_cache.h>
#include <NodeMonitor.h>

#include "IdMap.h"
#include "Request.h"
#include "RootInode.h"
#include "WorkQueue.h"


Inode::Inode()
        :
        fMetaCache(this),
        fCache(NULL),
        fAttrCache(NULL),
        fDelegation(NULL),
        fFileCache(NULL),
        fMaxFileSize(0),
        fOpenState(NULL),
        fWriteDirty(false),
        fAIOWait(create_sem(1, NULL)),
        fAIOCount(0),
        fOpenStateReleasesPending(0),
        fStale(false)
{
        rw_lock_init(&fDelegationLock, "nfs4 Inode::fDelegationLock");
        mutex_init(&fStateLock, "nfs4 Inode::fStateLock");
        mutex_init(&fFileCacheLock, "nfs4 Inode::fFileCacheLock");
        rw_lock_init(&fWriteLock, "nfs4 Inode::fWriteLock");
        mutex_init(&fAIOLock, "nfs4 Inode::fAIOLock");
}


status_t
Inode::CreateInode(FileSystem* fs, const FileInfo& fi, Inode** _inode)
{
        ASSERT(fs != NULL);
        ASSERT(_inode != NULL);

        Inode* inode = NULL;
        if (fs->Root() == NULL)
                inode = new(std::nothrow) RootInode;
        else
                inode = new(std::nothrow) Inode;

        if (inode == NULL)
                return B_NO_MEMORY;

        inode->fInfo = fi;
        inode->fFileSystem = fs;

        uint32 attempt = 0;
        uint64 size;
        do {
                RPC::Server* serv = fs->Server();
                Request request(serv, fs, geteuid(), getegid());
                RequestBuilder& req = request.Builder();

                req.PutFH(inode->fInfo.fHandle);

                Attribute attr[] = { FATTR4_TYPE, FATTR4_CHANGE, FATTR4_SIZE,
                        FATTR4_FSID, FATTR4_FILEID };
                req.GetAttr(attr, sizeof(attr) / sizeof(Attribute));

                status_t result = request.Send();
                if (result != B_OK)
                        return result;

                ReplyInterpreter& reply = request.Reply();

                if (inode->HandleErrors(attempt, reply.NFS4Error(), serv))
                        continue;

                reply.PutFH();

                AttrValue* values;
                uint32 count;
                result = reply.GetAttr(&values, &count);
                if (result != B_OK)
                        return result;

                if (fi.fFileId == 0) {
                        if (count < 5 || values[4].fAttribute != FATTR4_FILEID)
                                inode->fInfo.fFileId = fs->AllocFileId();
                        else
                                inode->fInfo.fFileId = values[4].fData.fValue64;
                } else
                        inode->fInfo.fFileId = fi.fFileId;

                // FATTR4_TYPE is mandatory
                inode->fType = values[0].fData.fValue32;

                if (inode->fType == NF4DIR)
                        inode->fCache = new DirectoryCache(inode);
                inode->fAttrCache = new DirectoryCache(inode, true);

                // FATTR4_CHANGE is mandatory
                inode->fChange = values[1].fData.fValue64;

                // FATTR4_SIZE is mandatory
                size = values[2].fData.fValue64;
                inode->fMaxFileSize = size;

                // FATTR4_FSID is mandatory
                FileSystemId* fsid
                        = reinterpret_cast<FileSystemId*>(values[3].fData.fPointer);
                if (*fsid != fs->FsId()) {
                        delete[] values;
                        return B_ENTRY_NOT_FOUND;
                }

                delete[] values;

                *_inode = inode;

                break;
        } while (true);

        if (inode->fType == NF4REG)
                inode->fFileCache = file_cache_create(fs->DevId(), inode->ID(), size);

        return B_OK;
}


Inode::~Inode()
{
        if (fDelegation != NULL)
                RecallDelegation();

        if (fFileCache != NULL)
                file_cache_delete(fFileCache);

        delete fCache;
        delete fAttrCache;

        delete_sem(fAIOWait);
        mutex_destroy(&fAIOLock);
        mutex_destroy(&fStateLock);
        mutex_destroy(&fFileCacheLock);
        rw_lock_destroy(&fDelegationLock);
        rw_lock_destroy(&fWriteLock);

        ASSERT(fAIOCount == 0);
}


status_t
Inode::RevalidateFileCache()
{
        if (fDelegation != NULL)
                return B_OK;

        uint64 change;
        status_t result = GetChangeInfo(&change);
        if (result != B_OK)
                return result;

        MutexLocker _(fFileCacheLock);
        if (change == fChange)
                return B_OK;
        SyncAndCommit(true);

        result = file_cache_disable(fFileCache);
        if (result != B_OK) {
                INFORM("RevalidateFileCache: failed to disable cache\n");
                return result;
        }

        struct stat st;
        fMetaCache.InvalidateStat();
        result = Stat(&st);
        if (result == B_OK)
                fMaxFileSize = st.st_size;
        file_cache_set_size(fFileCache, fMaxFileSize);
        file_cache_enable(fFileCache);

        fChange = change;
        return B_OK;
}


status_t
Inode::LookUp(const char* name, ino_t* id)
{
        ASSERT(name != NULL);
        ASSERT(id != NULL);

        if (fType != NF4DIR)
                return B_NOT_A_DIRECTORY;

        // attempt to get the id from the DirectoryCache
        fCache->Lock();
        status_t result = fCache->Revalidate();
                // At times, this will do a full readdir, in order to sync the DirectoryCache
                // with server changes.  However, if the directory contents have not changed since
                // the last readdir, it will either perform no RPC, or an RPC that just checks
                // FATTR4_CHANGE.
        if (result == B_OK) {
                SinglyLinkedList<NameCacheEntry>& entriesList = fCache->EntriesList();
                NameCacheEntry* entry = entriesList.Head();
                while (entry != NULL) {
                        if (strcmp(name, entry->fName) == 0)
                                break;
                        entry = entriesList.GetNext(entry);
                }
                if (entry != NULL) {
                        // Verify that the InoIdMap already has an entry for this inode.
                        // If not, we need to get the file handle from the server and call ChildAdded().
                        FileInfo info;
                        result = fFileSystem->InoIdMap()->GetFileInfo(&info, entry->fNode);
                        if (result == B_OK) {
                                *id = entry->fNode;
                                fCache->Unlock();
                                return B_OK;
                        }
                }
        }
        fCache->Unlock();

        uint64 change;
        uint64 fileID;
        FileHandle handle;
        result = NFS4Inode::LookUp(name, &change, &fileID, &handle);
        if (result != B_OK)
                return result;

        *id = FileIdToInoT(fileID);

        result = ChildAdded(name, fileID, handle);
        if (result != B_OK)
                return result;

        fCache->Lock();
        if (!fCache->Valid()) {
                fCache->Reset();
                fCache->SetChangeInfo(change);
        } else
                fCache->ValidateChangeInfo(change);

        fCache->AddEntry(name, *id);
        fCache->Unlock();

        return B_OK;
}


status_t
Inode::Link(Inode* dir, const char* name)
{
        ASSERT(dir != NULL);
        ASSERT(name != NULL);

        ChangeInfo changeInfo;
        status_t result = NFS4Inode::Link(dir, name, &changeInfo);
        if (result != B_OK)
                return result;

        fFileSystem->Root()->MakeInfoInvalid();
        fInfo.fNames->AddName(dir->fInfo.fNames, name);
        fMetaCache.InvalidateStat();

        dir->fCache->Lock();
        if (dir->fCache->Valid()) {
                if (changeInfo.fAtomic
                        && dir->fCache->ChangeInfo() == changeInfo.fBefore) {
                        dir->fCache->AddEntry(name, fInfo.fFileId, true);
                        dir->fCache->SetChangeInfo(changeInfo.fAfter);
                } else
                        dir->fCache->Trash();
        }
        dir->fCache->Unlock();

        notify_entry_created(fFileSystem->DevId(), dir->ID(), name, ID());

        return B_OK;
}


status_t
Inode::Remove(const char* name, FileType type, ino_t* id)
{
        ASSERT(name != NULL);

        MemoryDeleter nameDeleter;
        if (type == NF4NAMEDATTR) {
                status_t result = LoadAttrDirHandle();
                if (result != B_OK)
                        return result;

                name = AttrToFileName(name);
                if (name == NULL)
                        return B_NO_MEMORY;
                nameDeleter.SetTo(const_cast<char*>(name));
        }

        ChangeInfo changeInfo;
        uint64 fileID;

        status_t result = NFS4Inode::RemoveObject(name, type, &changeInfo, &fileID);
        if (result != B_OK)
                return result;

        DirectoryCache* cache = type != NF4NAMEDATTR ? fCache : fAttrCache;
        cache->Lock();
        if (cache->Valid()) {
                if (changeInfo.fAtomic
                        && fCache->ChangeInfo() == changeInfo.fBefore) {
                        cache->RemoveEntry(name);
                        cache->SetChangeInfo(changeInfo.fAfter);
                } else if (cache->ChangeInfo() != changeInfo.fBefore)
                        cache->Trash();
        }
        cache->Unlock();

        fFileSystem->Root()->MakeInfoInvalid();
        if (id != NULL)
                *id = FileIdToInoT(fileID);

        if (type == NF4NAMEDATTR) {
                notify_attribute_changed(fFileSystem->DevId(), -1, ID(), name,
                        B_ATTR_REMOVED);
        } else {
                notify_entry_removed(fFileSystem->DevId(), ID(), name,
                        FileIdToInoT(fileID));
        }

        return B_OK;
}


status_t
Inode::Rename(Inode* from, Inode* to, const char* fromName, const char* toName,
        bool attribute, ino_t* id, ino_t* oldID)
{
        ASSERT(from != NULL);
        ASSERT(fromName != NULL);
        ASSERT(to != NULL);
        ASSERT(toName != NULL);

        if (from->fFileSystem != to->fFileSystem)
                return B_DONT_DO_THAT;

        MemoryDeleter fromNameDeleter;
        MemoryDeleter toNameDeleter;
        if (attribute) {
                status_t result = from->LoadAttrDirHandle();
                if (result != B_OK)
                        return result;

                result = to->LoadAttrDirHandle();
                if (result != B_OK)
                        return result;

                fromName = from->AttrToFileName(fromName);
                toName = to->AttrToFileName(toName);

                fromNameDeleter.SetTo(const_cast<char*>(fromName));
                toNameDeleter.SetTo(const_cast<char*>(toName));
                if (fromName == NULL || toName == NULL)
                        return B_NO_MEMORY;
        }

        uint64 oldFileID = 0;
        if (!attribute)
                to->NFS4Inode::LookUp(toName, NULL, &oldFileID, NULL);

        uint64 fileID;
        ChangeInfo fromChange, toChange;
        status_t result = NFS4Inode::RenameNode(from, to, fromName, toName,
                &fromChange, &toChange, &fileID, attribute);
        if (result != B_OK)
                return result;

        from->fFileSystem->Root()->MakeInfoInvalid();

        if (id != NULL)
                *id = FileIdToInoT(fileID);
        if (oldID != NULL)
                *oldID = FileIdToInoT(oldFileID);

        DirectoryCache* cache = NULL;
        if (*oldID != 0) {
                // If we overwrote an existing file, remove the DirectoryCache entry of
                // the overwritten file, which now contains an incorrect inode value.
                cache = attribute ? to->fAttrCache : to->fCache;
                cache->Lock();
                if (cache->Valid()) {
                        if (toChange.fAtomic
                                && (cache->ChangeInfo() == toChange.fBefore)) {
                                cache->RemoveEntry(toName);
                        } else {
                                cache->Trash();
                        }
                }
                cache->Unlock();
        }

        cache = attribute ? from->fAttrCache : from->fCache;
        cache->Lock();
        if (cache->Valid()) {
                if (fromChange.fAtomic && cache->ChangeInfo() == fromChange.fBefore) {
                        cache->RemoveEntry(fromName);
                        if (to == from)
                                cache->AddEntry(toName, fileID, true);
                        cache->SetChangeInfo(fromChange.fAfter);
                } else
                        cache->Trash();
        }
        cache->Unlock();

        if (to != from) {
                cache = attribute ? to->fAttrCache : to->fCache;
                cache->Lock();
                if (cache->Valid()) {
                        if (toChange.fAtomic
                                && (cache->ChangeInfo() == toChange.fBefore)) {
                                cache->AddEntry(toName, fileID, true);
                                cache->SetChangeInfo(toChange.fAfter);
                        } else
                                cache->Trash();
                }
                cache->Unlock();
        }

        if (attribute) {
                notify_attribute_changed(from->fFileSystem->DevId(), -1, from->ID(),
                        fromName, B_ATTR_REMOVED);
                notify_attribute_changed(to->fFileSystem->DevId(), -1, to->ID(), toName,
                        B_ATTR_CREATED);
        } else {
                notify_entry_moved(from->fFileSystem->DevId(), from->ID(), fromName,
                        to->ID(), toName, FileIdToInoT(fileID));
        }

        return B_OK;
}


status_t
Inode::CreateLink(const char* name, const char* path, int mode, ino_t* id)
{
        return CreateObject(name, path, mode, NF4LNK, id);
}


status_t
Inode::CreateObject(const char* name, const char* path, int mode, FileType type,
        ino_t* id)
{
        ASSERT(name != NULL);
        ASSERT(type != NF4LNK || path != NULL);

        ChangeInfo changeInfo;
        uint64 fileID;
        FileHandle handle;

        status_t result = NFS4Inode::CreateObject(name, path, mode, type,
                &changeInfo, &fileID, &handle);
        if (result != B_OK)
                return result;

        fFileSystem->EnsureNoCollision(FileIdToInoT(fileID), handle);

        fFileSystem->Root()->MakeInfoInvalid();

        result = ChildAdded(name, fileID, handle);
        if (result != B_OK)
                return result;

        fCache->Lock();
        if (fCache->Valid()) {
                if (changeInfo.fAtomic && fCache->ChangeInfo() == changeInfo.fBefore) {
                        fCache->AddEntry(name, fileID, true);
                        fCache->SetChangeInfo(changeInfo.fAfter);
                } else
                        fCache->Trash();
        }
        fCache->Unlock();

        notify_entry_created(fFileSystem->DevId(), ID(), name,
                FileIdToInoT(fileID));

        *id = FileIdToInoT(fileID);
        return B_OK;
}


status_t
Inode::Access(int mode)
{
        int acc = 0;

        uint32 allowed;
        bool cache = fFileSystem->GetConfiguration().fCacheMetadata;
        status_t result = fMetaCache.GetAccess(geteuid(), &allowed);
        if (result != B_OK || !cache) {
                result = NFS4Inode::Access(&allowed);
                if (result != B_OK)
                        return result;
                fMetaCache.SetAccess(geteuid(), allowed);
        }

        if ((allowed & ACCESS4_READ) != 0)
                acc |= R_OK;

        if ((allowed & ACCESS4_LOOKUP) != 0)
                acc |= X_OK | R_OK;

        if ((allowed & ACCESS4_EXECUTE) != 0)
                acc |= X_OK;

        if ((allowed & ACCESS4_MODIFY) != 0)
                acc |= W_OK;

        if ((mode & acc) != mode)
                return B_NOT_ALLOWED;

        return B_OK;
}


status_t
Inode::Stat(struct stat* st, OpenAttrCookie* attr, bool revalidate)
{
        ASSERT(st != NULL);

        if (attr != NULL)
                return GetStat(st, attr);

        bool cache = fFileSystem->GetConfiguration().fCacheMetadata;
        if (!cache || revalidate)
                return GetStat(st, NULL);

        status_t result = fMetaCache.GetStat(st);
        if (result != B_OK) {
                struct stat temp;
                result = GetStat(&temp);
                if (result != B_OK)
                        return result;
                fMetaCache.SetStat(temp);
                fMetaCache.GetStat(st);
        }

        return B_OK;
}


status_t
Inode::GetStat(struct stat* st, OpenAttrCookie* attr)
{
        ASSERT(st != NULL);

        AttrValue* values;
        uint32 count;
        status_t result = NFS4Inode::GetStat(&values, &count, attr);
        if (result != B_OK)
                return result;

        // FATTR4_SIZE is mandatory
        if (count < 1 || values[0].fAttribute != FATTR4_SIZE) {
                delete[] values;
                return B_BAD_VALUE;
        }
        st->st_size = values[0].fData.fValue64;

        uint32 next = 1;
        st->st_mode = Type();
        if (count >= next && values[next].fAttribute == FATTR4_MODE) {
                st->st_mode |= values[next].fData.fValue32;
                next++;
        } else
                st->st_mode = 777;

        if (count >= next && values[next].fAttribute == FATTR4_NUMLINKS) {
                st->st_nlink = values[next].fData.fValue32;
                next++;
        } else
                st->st_nlink = 1;

        if (count >= next && values[next].fAttribute == FATTR4_OWNER) {
                char* owner = reinterpret_cast<char*>(values[next].fData.fPointer);
                if (owner != NULL && isdigit(owner[0]))
                        st->st_uid = atoi(owner);
                else
                        st->st_uid = gIdMapper->GetUserId(owner);
                next++;
        } else
                st->st_uid = 0;

        if (count >= next && values[next].fAttribute == FATTR4_OWNER_GROUP) {
                char* group = reinterpret_cast<char*>(values[next].fData.fPointer);
                if (group != NULL && isdigit(group[0]))
                        st->st_gid = atoi(group);
                else
                        st->st_gid = gIdMapper->GetGroupId(group);
                next++;
        } else
                st->st_gid = 0;

        if (count >= next && values[next].fAttribute == FATTR4_TIME_ACCESS) {
                memcpy(&st->st_atim, values[next].fData.fPointer,
                        sizeof(timespec));
                next++;
        } else
                memset(&st->st_atim, 0, sizeof(timespec));

        if (count >= next && values[next].fAttribute == FATTR4_TIME_CREATE) {
                memcpy(&st->st_crtim, values[next].fData.fPointer,
                        sizeof(timespec));
                next++;
        } else
                memset(&st->st_crtim, 0, sizeof(timespec));

        if (count >= next && values[next].fAttribute == FATTR4_TIME_METADATA) {
                memcpy(&st->st_ctim, values[next].fData.fPointer,
                        sizeof(timespec));
                next++;
        } else
                memset(&st->st_ctim, 0, sizeof(timespec));

        if (count >= next && values[next].fAttribute == FATTR4_TIME_MODIFY) {
                memcpy(&st->st_mtim, values[next].fData.fPointer,
                        sizeof(timespec));
                next++;
        } else
                memset(&st->st_mtim, 0, sizeof(timespec));
        delete[] values;

        st->st_blksize = fFileSystem->Root()->IOSize();
        st->st_blocks = st->st_size / DEV_BSIZE;
        st->st_blocks += (st->st_size % DEV_BSIZE) == 0 ? 0 : 1;

        return B_OK;
}


status_t
Inode::WriteStat(const struct stat* st, uint32 mask, OpenAttrCookie* cookie)
{
        ASSERT(st != NULL);

        status_t result;
        AttrValue attr[6];
        uint32 i = 0;

        if ((mask & B_STAT_SIZE) != 0) {
                fMaxFileSize = st->st_size;
                file_cache_set_size(fFileCache, st->st_size);

                attr[i].fAttribute = FATTR4_SIZE;
                attr[i].fFreePointer = false;
                attr[i].fData.fValue64 = st->st_size;
                i++;
        }

        if ((mask & B_STAT_MODE) != 0) {
                attr[i].fAttribute = FATTR4_MODE;
                attr[i].fFreePointer = false;
                attr[i].fData.fValue32 = st->st_mode;
                i++;
        }

        if ((mask & B_STAT_UID) != 0) {
                attr[i].fAttribute = FATTR4_OWNER;
                attr[i].fFreePointer = true;
                attr[i].fData.fPointer = gIdMapper->GetOwner(st->st_uid);
                i++;
        }

        if ((mask & B_STAT_GID) != 0) {
                attr[i].fAttribute = FATTR4_OWNER_GROUP;
                attr[i].fFreePointer = true;
                attr[i].fData.fPointer = gIdMapper->GetOwnerGroup(st->st_gid);
                i++;
        }

        if ((mask & B_STAT_ACCESS_TIME) != 0) {
                attr[i].fAttribute = FATTR4_TIME_ACCESS_SET;
                attr[i].fFreePointer = true;
                attr[i].fData.fPointer = malloc(sizeof(st->st_atim));
                memcpy(attr[i].fData.fPointer, &st->st_atim, sizeof(st->st_atim));
                i++;
        }

        if ((mask & B_STAT_MODIFICATION_TIME) != 0) {
                attr[i].fAttribute = FATTR4_TIME_MODIFY_SET;
                attr[i].fFreePointer = true;
                attr[i].fData.fPointer = malloc(sizeof(st->st_mtim));
                memcpy(attr[i].fData.fPointer, &st->st_mtim, sizeof(st->st_mtim));
                i++;
        }

        ReadLocker delegationLocker(fDelegationLock);
        if (cookie == NULL) {
                MutexLocker stateLocker(fStateLock);
                ASSERT(fOpenState != NULL || !(mask & B_STAT_SIZE));
                result = NFS4Inode::WriteStat(fOpenState, fDelegation, attr, i);
        } else {
                ASSERT(cookie->fOpenState->fDelegation == fDelegation);
                result = NFS4Inode::WriteStat(cookie->fOpenState, NULL, attr, i);
        }

        fMetaCache.InvalidateStat();

        const uint32 kAccessMask = B_STAT_MODE | B_STAT_UID | B_STAT_GID;
        if ((mask & kAccessMask) != 0)
                fMetaCache.InvalidateAccess();

        return result;
}


inline status_t
Inode::CheckLockType(short ltype, uint32 mode)
{
        switch (ltype) {
                case F_UNLCK:
                        return B_OK;

                case F_RDLCK:
                        if ((mode & O_RDONLY) == 0 && (mode & O_RDWR) == 0)
                                return EBADF;
                        return B_OK;

                case F_WRLCK:
                        if ((mode & O_WRONLY) == 0 && (mode & O_RDWR) == 0)
                                return EBADF;
                        return B_OK;

                default:
                        return B_BAD_VALUE;
        }
}


status_t
Inode::TestLock(OpenFileCookie* cookie, struct flock* lock)
{
        ASSERT(cookie != NULL);
        ASSERT(lock != NULL);

        if (lock->l_type == F_UNLCK)
                return B_OK;

        status_t result = CheckLockType(lock->l_type, cookie->fMode);
        if (result != B_OK)
                return result;

        LockType ltype = sGetLockType(lock->l_type, false);
        uint64 position = lock->l_start;
        uint64 length;
        if (lock->l_len + lock->l_start == OFF_MAX)
                length = UINT64_MAX;
        else
                length = lock->l_len;

        bool conflict;
        result = NFS4Inode::TestLock(cookie, &ltype, &position, &length, conflict);
        if (result != B_OK)
                return result;

        if (conflict) {
                lock->l_type = sLockTypeToHaiku(ltype);
                lock->l_start = static_cast<off_t>(position);
                if (length >= OFF_MAX)
                        lock->l_len = OFF_MAX;
                else
                        lock->l_len = static_cast<off_t>(length);
        } else
                lock->l_type = F_UNLCK;

        return B_OK;
}


status_t
Inode::AcquireLock(OpenFileCookie* cookie, const struct flock* lock,
        bool wait)
{
        ASSERT(cookie != NULL);
        ASSERT(lock != NULL);

        OpenState* state = cookie->fOpenState;

        status_t result = CheckLockType(lock->l_type, cookie->fMode);
        if (result != B_OK)
                return result;

        thread_info info;
        get_thread_info(find_thread(NULL), &info);

        MutexLocker locker(state->fOwnerLock);
        LockOwner* owner = state->GetLockOwner(info.team);
        if (owner == NULL)
                return B_NO_MEMORY;

        LockInfo* linfo = new(std::nothrow) LockInfo(owner);
        if (linfo == NULL)
                return B_NO_MEMORY;
        locker.Unlock();

        linfo->fStart = lock->l_start;
        if (lock->l_len + lock->l_start == OFF_MAX)
                linfo->fLength = UINT64_MAX;
        else
                linfo->fLength = lock->l_len;
        linfo->fType = sGetLockType(lock->l_type, wait);

        result = NFS4Inode::AcquireLock(cookie, linfo, wait);
        if (result != B_OK) {
                delete linfo;
                return result;
        }

        MutexLocker _(state->fLocksLock);
        state->AddLock(linfo);
        cookie->AddLock(linfo);

        return B_OK;
}


status_t
Inode::ReleaseLock(OpenFileCookie* cookie, const struct flock* lock)
{
        ASSERT(cookie != NULL);
        ASSERT(lock != NULL);

        SyncAndCommit();

        LockInfo* prev = NULL;

        thread_info info;
        get_thread_info(find_thread(NULL), &info);
        uint32 owner = info.team;

        OpenState* state = cookie->fOpenState;
        MutexLocker locker(state->fLocksLock);
        LockInfo* linfo = state->fLocks;
        while (linfo != NULL) {
                if (linfo->fOwner->fOwner == owner && *linfo == *lock) {
                        state->RemoveLock(linfo, prev);
                        break;
                }

                prev = linfo;
                linfo = linfo->fNext;
        }

        prev = NULL;
        linfo = cookie->fLocks;
        while (linfo != NULL) {
                if (linfo->fOwner->fOwner == owner && *linfo == *lock) {
                        cookie->RemoveLock(linfo, prev);
                        break;
                }

                prev = linfo;
                linfo = linfo->fCookieNext;
        }
        locker.Unlock();

        if (linfo == NULL)
                return B_BAD_VALUE;

        status_t result = NFS4Inode::ReleaseLock(cookie, linfo);
        if (result != B_OK)
                return result;

        state->DeleteLock(linfo);

        return B_OK;
}


status_t
Inode::ReleaseAllLocks(OpenFileCookie* cookie)
{
        ASSERT(cookie != NULL);

        if (cookie->fLocks)
                SyncAndCommit();

        OpenState* state = cookie->fOpenState;
        MutexLocker _(state->fLocksLock);
        LockInfo* linfo = cookie->fLocks;
        while (linfo != NULL) {
                cookie->RemoveLock(linfo, NULL);

                LockInfo* prev = NULL;
                LockInfo* stateLock = state->fLocks;
                while (stateLock != NULL) {
                        if (*linfo == *stateLock) {
                                state->RemoveLock(stateLock, prev);
                                break;
                        }

                        prev = stateLock;
                        stateLock = stateLock->fNext;
                }

                NFS4Inode::ReleaseLock(cookie, linfo);
                state->DeleteLock(linfo);

                linfo = cookie->fLocks;
        }

        return B_OK;
}


status_t
Inode::ChildAdded(const char* name, uint64 fileID,
        const FileHandle& fileHandle)
{
        ASSERT(name != NULL);

        fFileSystem->Root()->MakeInfoInvalid();

        FileInfo fi;
        fi.fFileId = fileID;
        fi.fHandle = fileHandle;

        return fFileSystem->InoIdMap()->AddName(fi, fInfo.fNames, name,
                FileIdToInoT(fileID));
}


const char*
Inode::Name() const
{
        ASSERT(fInfo.fNames->fNames.Head() != NULL);
        return fInfo.fNames->fNames.Head()->fName;
}


void
Inode::SetDelegation(Delegation* delegation)
{
        ASSERT(delegation != NULL);

        WriteLocker _(fDelegationLock);

        fMetaCache.InvalidateStat();
        struct stat st;
        Stat(&st);
        fMetaCache.LockValid();

        fDelegation = delegation;
        fOpenState->AcquireReference();
        fOpenState->fDelegation = delegation;
        fFileSystem->AddDelegation(delegation);
}


void
Inode::RecallDelegation(bool truncate)
{
        WriteLocker _(fDelegationLock);
        if (fDelegation == NULL)
                return;
        ReturnDelegation(truncate);
}


/*! Flush write data to the server if needed before returning the delegation.
        @post If data needs to be flushed, an IO job is enqueued but may not be complete.
*/
void
Inode::PrepareDelegationRecall(bool truncate)
{
        rw_lock_write_lock(&fDelegationLock);
        fDelegation->MarkRecalled();
        rw_lock_write_unlock(&fDelegationLock);

        ReadLocker _(fDelegationLock);
        if (fDelegation == NULL)
                return;

        fDelegation->PrepareGiveUp(truncate);

        return;
}


/*! Return the delegation after data has been flushed to the server.
        @pre RecallDelegationAsyncPrep has been called.
*/
void
Inode::RecallDelegationAsync(bool truncate)
{
        WriteLocker _(fDelegationLock);
        if (fDelegation == NULL)
                return;

        fDelegation->DoGiveUp(truncate, false);

        fMetaCache.UnlockValid();
        fFileSystem->RemoveDelegation(fDelegation);

        MutexLocker stateLocker(fStateLock);
        fOpenState->fDelegation = NULL;
        ReleaseOpenState();

        delete fDelegation;
        fDelegation = NULL;

        return;
}


void
Inode::RecallReadDelegation()
{
        WriteLocker _(fDelegationLock);
        if (fDelegation == NULL || fDelegation->Type() != OPEN_DELEGATE_READ)
                return;
        ReturnDelegation(false);
}


void
Inode::ReturnDelegation(bool truncate)
{
        ASSERT(fDelegation != NULL);

        fDelegation->GiveUp(truncate);

        fMetaCache.UnlockValid();
        fFileSystem->RemoveDelegation(fDelegation);

        MutexLocker stateLocker(fStateLock);
        fOpenState->fDelegation = NULL;
        ReleaseOpenState();

        delete fDelegation;
        fDelegation = NULL;
}


/*! Temporarily unlock the locks that need to be acquired by the WorkQueue when a delegation
        is recalled.
        @pre fStateLock is locked and fDelegation lock is read-locked.
*/
void
Inode::UnlockAndRelockStateLocks()
{
        rw_lock_read_unlock(&fDelegationLock);
        mutex_unlock(&fStateLock);
        rw_lock_read_lock(&fDelegationLock);
        mutex_lock(&fStateLock);
}


/*! Temporarily unlock fWriteLock.  Useful for allowing an IO job to complete.
        @pre fWriteLock is write-locked and fDelegationLock is read-locked.
*/
void
Inode::UnlockAndRelockWriteLock()
{
        rw_lock_write_unlock(&fWriteLock);
        rw_lock_write_lock(&fWriteLock);
}


/*! Release a reference to fOpenState or set up later release if IO is not complete.
        @pre fStateLock is locked.
*/
void
Inode::ReleaseOpenState()
{
        ASSERT(fOpenState != NULL);

        // If IO is pending, wait until it is finished to release the (possibly last) reference.
        // It would be simpler to call WaitAIOComplete here, but that won't work if this
        // is the WorkQueue thread.
        MutexLocker _(fAIOLock);
        if (fAIOCount > 0) {
                ++fOpenStateReleasesPending;
        } else {
                if (fOpenState->ReleaseReference() == 1)
                        fOpenState = NULL;
        }

        return;
}


/*! Sync file cache data to server.
        @param wait If true, the function returns only after the sync is finished.
*/
status_t
Inode::Sync(bool force, bool wait)
{
        ReadLocker locker(fDelegationLock);
        if (!force && fDelegation != NULL && fDelegation->Type() == OPEN_DELEGATE_WRITE
                && !fDelegation->RecallInitiated()) {
                locker.Unlock();
                if (wait == true) {
                        // Wait for any IO jobs that may already be enqueued.
                        WaitAIOComplete();
                }
                return B_OK;
        }
        locker.Unlock();

        status_t status = file_cache_sync(fFileCache);
        if (wait == true)
                WaitAIOComplete();

        return status;
}


status_t
Inode::SyncAndCommit(bool force, OpenStateCookie* cookie)
{
        Sync(force, true);

        // The server is liable to deny a commit request that does not come from a user who
        // opened the file.
        uid_t uid = cookie != NULL ? cookie->fUid : geteuid();
        gid_t gid = cookie != NULL ? cookie->fGid : getegid();
        return Commit(uid, gid);
}


void
Inode::BeginAIOOp()
{
        MutexLocker _(fAIOLock);
        fAIOCount++;
        if (fAIOCount == 1)
                acquire_sem(fAIOWait);
}


void
Inode::EndAIOOp()
{
        MutexLocker AIOLocker(fAIOLock);
        ASSERT(fAIOCount > 0);
        fAIOCount--;
        if (fAIOCount == 0)
                release_sem(fAIOWait);

        if (fOpenStateReleasesPending > 0) {
                MutexLocker stateLocker(fStateLock);
                --fOpenStateReleasesPending;
                if (fOpenState->ReleaseReference() == 1) {
                        ASSERT(fAIOCount == 0);
                        ASSERT(fOpenStateReleasesPending == 0);
                        fOpenState = NULL;
                }
        }
}


bool
Inode::AIOIncomplete()
{
        MutexLocker _(fAIOLock);

        if (fAIOCount > 0)
                return true;

        return false;
}


/*! Print the ID, handle, names, and DirectoryCache if applicable.
        @pre The parent VnodeToInode is locked.
*/
void
Inode::Dump(void (*xprintf)(const char*, ...))
{
        bool dumpDelegation = true;
        bool dumpAIO = true;
        if (xprintf != kprintf) {
                status_t status = rw_lock_read_lock_with_timeout(&fDelegationLock, B_RELATIVE_TIMEOUT, 0);
                if (status != B_OK)
                        dumpDelegation = false;
                status = mutex_trylock(&fAIOLock);
                if (status != B_OK)
                        dumpAIO = false;
        }

        _DumpLocked(xprintf, dumpDelegation, dumpAIO);

        if (xprintf != kprintf) {
                if (dumpDelegation)
                        rw_lock_read_unlock(&fDelegationLock);
                if (dumpAIO)
                        mutex_unlock(&fAIOLock);
        }

        if (GetFileSystem()->Root() != this)
                fInfo.fNames->Dump(xprintf);

        if (fCache != NULL)
                fCache->Dump(xprintf);

        fMetaCache.Dump(xprintf);

        if (fOpenState == NULL) {
                xprintf("No OpenState\n");
        } else {
                status_t status = mutex_trylock(&fStateLock);
                if (status == B_OK) {
                        fOpenState->Dump(xprintf);
                        mutex_unlock(&fStateLock);
                } else {
                        xprintf("fStateLock locked\n");
                }
        }

        gWorkQueue->Dump(xprintf);

        return;
}

/*!     Dump members that have const Dump methods or are dumped manually.

*/
void
Inode::_DumpLocked(void (*xprintf)(const char*, ...), bool dumpDelegation, bool dumpAIO) const
{
        if (GetFileSystem()->Root() == this)
                xprintf("Root inode\t%" B_PRIu64 " at %p\n", fInfo.fFileId, this);
        else
                xprintf("Inode\t%" B_PRIu64 " at %p\n", fInfo.fFileId, this);

        xprintf("FileHandle ");
        fInfo.fHandle.Dump(xprintf);

        xprintf("\tfType %" B_PRIu32 ", fChange %" B_PRIu64 ", fStale %d\n", fType, fChange, fStale);

        if (dumpAIO)
                xprintf("\tfAIOCount %" B_PRIu32 "\n", fAIOCount);
        else
                xprintf("\tAIO locked\n");

        if (fDelegation == NULL)
                xprintf("\tNo Delegation\n");
        else if (dumpDelegation)
                fDelegation->Dump();
        else
                xprintf("Delegation locked\n");

        return;
}