⛏️ index : haiku.git

/*
 * 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;
}