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


#include "Cookie.h"
#include "FileSystem.h"
#include "NFS4Object.h"
#include "OpenState.h"
#include "Request.h"


static inline bigtime_t
RetryDelay(uint32 attempt, uint32 leaseTime = 0)
{
        attempt = min_c(attempt, 10);

        bigtime_t delay = (bigtime_t(1) << (attempt - 1)) * 100000;
        if (leaseTime != 0)
                delay = min_c(delay, sSecToBigTime(leaseTime));
        return delay;
}


bool
NFS4Object::HandleErrors(uint32& attempt, uint32 nfs4Error, RPC::Server* server,
        OpenStateCookie* cookie, OpenState* state, uint32* sequence)
{
        // No request send by the client should cause any of the following errors.
        ASSERT(nfs4Error != NFS4ERR_CLID_INUSE);
        ASSERT(nfs4Error != NFS4ERR_BAD_STATEID);
        ASSERT(nfs4Error != NFS4ERR_RESTOREFH);
        ASSERT(nfs4Error != NFS4ERR_LOCKS_HELD);
        ASSERT(nfs4Error != NFS4ERR_OP_ILLEGAL);

        attempt++;

        if (cookie != NULL)
                state = cookie->fOpenState;

        uint32 leaseTime;
        status_t result;
        switch (nfs4Error) {
                case NFS4_OK:
                        return false;

                // retransmission of CLOSE caused seqid to fall back
                case NFS4ERR_BAD_SEQID:
                        if (attempt == 1) {
                                ASSERT(sequence != NULL);
                                (*sequence)++;
                                return true;
                        }
                        return false;

                // resource is locked, we need to wait
                case NFS4ERR_DENIED:
                        if (cookie == NULL)
                                return false;

                        if (sequence != NULL)
                                fFileSystem->OpenOwnerSequenceUnlock(*sequence);

                        result = acquire_sem_etc(cookie->fSnoozeCancel, 1,
                                B_RELATIVE_TIMEOUT, RetryDelay(attempt));

                        if (sequence != NULL)
                                *sequence = fFileSystem->OpenOwnerSequenceLock();

                        if (result != B_TIMED_OUT) {
                                if (result == B_OK)
                                        release_sem(cookie->fSnoozeCancel);
                                return false;
                        }
                        return true;

                // server needs more time, we need to wait
                case NFS4ERR_LOCKED:
                case NFS4ERR_DELAY:
                        if (sequence != NULL)
                                fFileSystem->OpenOwnerSequenceUnlock(*sequence);

                        if (cookie == NULL) {
                                snooze_etc(RetryDelay(attempt), B_SYSTEM_TIMEBASE,
                                        B_RELATIVE_TIMEOUT);
                                

                                if (sequence != NULL)
                                        *sequence = fFileSystem->OpenOwnerSequenceLock();

                                return true;
                        }

                        if ((cookie->fMode & O_NONBLOCK) == 0) {
                                result = acquire_sem_etc(cookie->fSnoozeCancel, 1,
                                        B_RELATIVE_TIMEOUT, RetryDelay(attempt));

                                if (sequence != NULL)
                                        *sequence = fFileSystem->OpenOwnerSequenceLock();

                                if (result != B_TIMED_OUT) {
                                        if (result == B_OK)
                                                release_sem(cookie->fSnoozeCancel);
                                        return false;
                                }
                                return true;
                        }

                        if (sequence != NULL)
                                *sequence = fFileSystem->OpenOwnerSequenceLock();
                        return false;

                // server is in grace period, we need to wait
                case NFS4ERR_GRACE:
                        leaseTime = fFileSystem->NFSServer()->LeaseTime();
                        if (sequence != NULL)
                                fFileSystem->OpenOwnerSequenceUnlock(*sequence);

                        if (cookie == NULL) {
                                snooze_etc(RetryDelay(attempt, leaseTime), B_SYSTEM_TIMEBASE,
                                        B_RELATIVE_TIMEOUT);
                                if (sequence != NULL)
                                        *sequence = fFileSystem->OpenOwnerSequenceLock();
                                return true;
                        }

                        if ((cookie->fMode & O_NONBLOCK) == 0) {
                                result = acquire_sem_etc(cookie->fSnoozeCancel, 1,
                                        B_RELATIVE_TIMEOUT, RetryDelay(attempt, leaseTime));

                                if (sequence != NULL)
                                        *sequence = fFileSystem->OpenOwnerSequenceLock();

                                if (result != B_TIMED_OUT) {
                                        if (result == B_OK)
                                                release_sem(cookie->fSnoozeCancel);
                                        return false;
                                }
                                return true;
                        }

                        if (sequence != NULL)
                                *sequence = fFileSystem->OpenOwnerSequenceLock();
                        return false;

                // server has rebooted, reclaim share and try again
                case NFS4ERR_STALE_CLIENTID:
                case NFS4ERR_STALE_STATEID:
                        if (state != NULL) {
                                if (sequence != NULL)
                                        fFileSystem->OpenOwnerSequenceUnlock(*sequence);

                                fFileSystem->NFSServer()->ServerRebooted(state->fClientID);
                                if (sequence != NULL)
                                        *sequence = fFileSystem->OpenOwnerSequenceLock();

                                return true;
                        }
                        return false;

                // File Handle has expired, is invalid or the node has been deleted
                case NFS4ERR_BADHANDLE:
                case NFS4ERR_FHEXPIRED:
                case NFS4ERR_STALE:
                        if (fInfo.UpdateFileHandles(fFileSystem) == B_OK)
                                return true;
                        return false;

                // filesystem has been moved
                case NFS4ERR_LEASE_MOVED:
                case NFS4ERR_MOVED:
                        fFileSystem->Migrate(server);
                        return true;

                // lease has expired
                case NFS4ERR_EXPIRED:
                        if (state != NULL) {
                                fFileSystem->NFSServer()->ClientId(state->fClientID, true);
                                return true;
                        }
                        return false;

                // delegation might have just been recalled, or is about to be recalled
                case NFS4ERR_OPENMODE:
                        if (state->fDelegation != NULL)
                                return true;
                        return false;

                default:
                        return false;
        }
}


status_t
NFS4Object::ConfirmOpen(const FileHandle& fh, OpenState* state,
        uint32* sequence)
{
        ASSERT(state != NULL);
        ASSERT(sequence != NULL);

        uint32 attempt = 0;
        do {
                RPC::Server* serv = fFileSystem->Server();
                Request request(serv, fFileSystem, geteuid(), getegid());

                RequestBuilder& req = request.Builder();

                req.PutFH(fh);
                req.OpenConfirm(*sequence, state->fStateID, state->fStateSeq);

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

                ReplyInterpreter& reply = request.Reply();

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

                if (HandleErrors(attempt, reply.NFS4Error(), serv, NULL, state))
                        continue;

                result = reply.OpenConfirm(&state->fStateSeq);
                if (result != B_OK)
                        return result;

                return B_OK;
        } while (true);
}


uint32
NFS4Object::IncrementSequence(uint32 error)
{
        if (error != NFS4ERR_STALE_CLIENTID && error != NFS4ERR_STALE_STATEID
                && error != NFS4ERR_BAD_STATEID && error != NFS4ERR_BAD_SEQID
                && error != NFS4ERR_BADXDR && error != NFS4ERR_RESOURCE
                && error != NFS4ERR_NOFILEHANDLE)
                return 1;

        return 0;
}