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


#include "OpenState.h"

#include <util/AutoLock.h>

#include "FileSystem.h"
#include "Request.h"
#include "WorkQueue.h"


OpenState::OpenState()
        :
        fOpened(false),
        fDelegation(NULL),
        fLocks(NULL),
        fLockOwners(NULL),
        fUid(geteuid()),
        fGid(getegid())
{
        mutex_init(&fLock, "nfs4 OpenState::fLock");

        mutex_init(&fLocksLock, "nfs4 OpenState::fLocksLock");
        mutex_init(&fOwnerLock, "nfs4 OpenState::fOwnerLock");
}


OpenState::~OpenState()
{
        if (fOpened)
                fFileSystem->RemoveOpenFile(this);
        Close();

        mutex_destroy(&fLock);

        mutex_destroy(&fLocksLock);
        mutex_destroy(&fOwnerLock);
}


LockOwner*
OpenState::GetLockOwner(uint32 owner)
{
        LockOwner* current = fLockOwners;
        while (current != NULL) {
                if (current->fOwner == owner)
                        return current;

                current = current->fNext;
        }

        current = new LockOwner(owner);
        if (current == NULL)
                return NULL;

        current->fNext = fLockOwners;
        if (fLockOwners != NULL)
                fLockOwners->fPrev = current;
        fLockOwners = current;

        return current;
}


// Caller must hold fLocksLock
void
OpenState::AddLock(LockInfo* lock)
{
        lock->fNext = fLocks;
        fLocks = lock;
}


// Caller must hold fLocksLock
void
OpenState::RemoveLock(LockInfo* lock, LockInfo* prev)
{
        if (prev != NULL)
                prev->fNext = lock->fNext;
        else
                fLocks = lock->fNext;
}


void
OpenState::DeleteLock(LockInfo* lock)
{
        MutexLocker _(fOwnerLock);

        LockOwner* owner = lock->fOwner;
        delete lock;

        if (owner->fUseCount == 0) {
                if (owner->fPrev)
                        owner->fPrev->fNext = owner->fNext;
                else
                        fLockOwners = owner->fNext;
                if (owner->fNext)
                        owner->fNext->fPrev = owner->fPrev;

                _ReleaseLockOwner(owner);
                delete owner;
        }
}


status_t
OpenState::_ReleaseLockOwner(LockOwner* owner)
{
        ASSERT(owner != NULL);

        uint32 attempt = 0;
        do {
                RPC::Server* server = fFileSystem->Server();
                Request request(server, fFileSystem, fUid, fGid);
                RequestBuilder& req = request.Builder();

                req.ReleaseLockOwner(this, owner);

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

                ReplyInterpreter& reply = request.Reply();

                if (HandleErrors(attempt, reply.NFS4Error(), server))
                        continue;

                return reply.ReleaseLockOwner();
        } while (true);
}


status_t
OpenState::Reclaim(uint64 newClientID)
{
        if (!fOpened)
                return B_OK;

        MutexLocker _(fLock);

        if (fClientID == newClientID)
                return B_OK;
        fClientID = newClientID;

        _ReclaimOpen(newClientID);
        _ReclaimLocks(newClientID);

        return B_OK;
}


status_t
OpenState::_ReclaimOpen(uint64 newClientID)
{
        bool confirm;
        OpenDelegationData delegation;
        delegation.fType = OPEN_DELEGATE_NONE;
        delegation.fRecall = false;

        status_t result;
        uint32 sequence = fFileSystem->OpenOwnerSequenceLock();
        OpenDelegation delegType = fDelegation != NULL ? fDelegation->Type()
                : OPEN_DELEGATE_NONE;
        uint32 attempt = 0;
        do {
                RPC::Server* server = fFileSystem->Server();
                Request request(server, fFileSystem, fUid, fGid);
                RequestBuilder& req = request.Builder();

                req.PutFH(fInfo.fHandle);
                req.Open(CLAIM_PREVIOUS, sequence, sModeToAccess(fMode), newClientID,
                        OPEN4_NOCREATE, fFileSystem->OpenOwner(), NULL, NULL, 0, false,
                        delegType);

                result = request.Send();
                if (result != B_OK) {
                        fFileSystem->OpenOwnerSequenceUnlock(sequence);
                        return result;
                }

                ReplyInterpreter& reply = request.Reply();

                result = reply.PutFH();
                if (result == B_OK)
                        sequence += IncrementSequence(reply.NFS4Error());

                if (reply.NFS4Error() != NFS4ERR_STALE_CLIENTID
                        && HandleErrors(attempt, reply.NFS4Error(), server, NULL, NULL,
                                &sequence)) {
                        continue;
                }

                result = reply.Open(fStateID, &fStateSeq, &confirm, &delegation);
                if (result != B_OK) {
                        fFileSystem->OpenOwnerSequenceUnlock(sequence);
                        return result;
                }

                break;
        } while (true);

        if (fDelegation != NULL)
                fDelegation->SetData(delegation);

        if (delegation.fRecall) {
                DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
                args->fDelegation = fDelegation;
                args->fTruncate = false;
                gWorkQueue->EnqueueJob(DelegationRecall, args);
        }

        if (confirm)
                result = ConfirmOpen(fInfo.fHandle, this, &sequence);

        fFileSystem->OpenOwnerSequenceUnlock(sequence);
        return result;
}


status_t
OpenState::_ReclaimLocks(uint64 newClientID)
{
        MutexLocker _(fLocksLock);
        LockInfo* linfo = fLocks;
        while (linfo != NULL) {
                MutexLocker locker(linfo->fOwner->fLock);

                if (linfo->fOwner->fClientId != newClientID) {
                        memset(linfo->fOwner->fStateId, 0, sizeof(linfo->fOwner->fStateId));
                        linfo->fOwner->fClientId = newClientID;
                }

                uint32 attempt = 0;
                uint32 sequence = fFileSystem->OpenOwnerSequenceLock();
                do {
                        RPC::Server* server = fFileSystem->Server();
                        Request request(server, fFileSystem, fUid, fGid);
                        RequestBuilder& req = request.Builder();

                        req.PutFH(fInfo.fHandle);
                        req.Lock(this, linfo, &sequence, true);

                        status_t result = request.Send();
                        if (result != B_OK) {
                                fFileSystem->OpenOwnerSequenceUnlock(sequence);
                                break;
                        }

                        ReplyInterpreter& reply = request.Reply();

                        result = reply.PutFH();
                        if (result == B_OK)
                                sequence += IncrementSequence(reply.NFS4Error());

                        if (reply.NFS4Error() != NFS4ERR_STALE_CLIENTID
                                && reply.NFS4Error() !=  NFS4ERR_STALE_STATEID
                                && HandleErrors(attempt, reply.NFS4Error(), server, NULL, NULL,
                                        &sequence)) {
                                continue;
                        }

                        reply.Lock(linfo);

                        fFileSystem->OpenOwnerSequenceUnlock(sequence);
                        break;
                } while (true);
                locker.Unlock();

                linfo = linfo->fNext;
        }

        return B_OK;
}


status_t
OpenState::Close()
{
        if (!fOpened)
                return B_OK;

        MutexLocker _(fLock);
        fOpened = false;

        uint32 attempt = 0;
        uint32 sequence = fFileSystem->OpenOwnerSequenceLock();
        do {
                RPC::Server* serv = fFileSystem->Server();
                Request request(serv, fFileSystem, fUid, fGid);
                RequestBuilder& req = request.Builder();

                req.PutFH(fInfo.fHandle);
                req.Close(sequence, fStateID, fStateSeq);

                status_t result = request.Send();
                if (result != B_OK) {
                        fFileSystem->OpenOwnerSequenceUnlock(sequence);
                        return result;
                }

                ReplyInterpreter& reply = request.Reply();

                result = reply.PutFH();
                if (result == B_OK)
                        sequence += IncrementSequence(reply.NFS4Error());

                // RFC 3530 8.10.1. Some servers does not do anything to help client
                // recognize retried CLOSE requests so we just assume that BAD_STATEID
                // on CLOSE request is just a result of retransmission.
                if (reply.NFS4Error() == NFS4ERR_BAD_STATEID) {
                        fFileSystem->OpenOwnerSequenceUnlock(sequence);
                        return B_OK;
                }

                if (HandleErrors(attempt, reply.NFS4Error(), serv, NULL, this,
                                &sequence)) {
                        continue;
                }
                fFileSystem->OpenOwnerSequenceUnlock(sequence);

                return reply.Close();
        } while (true);
}


void
OpenState::Dump(void (*xprintf)(const char*, ...))
{
        status_t status = B_OK;
        if (xprintf != kprintf)
                status = mutex_trylock(&fLock);

        if (status == B_OK)
                _DumpLocked(xprintf);
        else
                xprintf("OpenState at %p locked\n", this);

        if (xprintf != kprintf && status == B_OK)
                mutex_unlock(&fLock);

        return;
}


void
OpenState::_DumpLocked(void (*xprintf)(const char*, ...)) const
{
        xprintf("OpenState at %p for ino %" B_PRIdINO "\n", this, Inode::FileIdToInoT(fInfo.fFileId));

        xprintf("\tFileHandle ");
        fInfo.fHandle.Dump(xprintf);

        xprintf("\t");
        fInfo.fNames->Dump(xprintf);

        xprintf("\tmode %x, opened %d, delegation %p, refs %" B_PRIu32 "\n",
                static_cast<unsigned int>(fMode), fOpened, fDelegation, CountReferences());

        xprintf("\tuid %" B_PRIu32 ", gid %" B_PRIu32 "\n", fUid, fGid);

        xprintf("\tstate id and sequence %" B_PRIu32 " %" B_PRIu32 " %" B_PRIu32 " %" B_PRIu32 "\n",
                fStateID[0], fStateID[1], fStateID[2], fStateSeq);

        return;
}