/*
 * Copyright 2012 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Paweł Dziepak, pdziepak@quarnos.org
 */
 
 
#include "NFS4Server.h"
 
#include <AutoDeleter.h>
 
#include "FileSystem.h"
#include "Inode.h"
#include "Request.h"
#include "WorkQueue.h"
 
 
NFS4Server::NFS4Server(RPC::Server* serv)
	:
	fThreadCancel(true),
	fWaitCancel(create_sem(0, NULL)),
	fLeaseTime(0),
	fClientIdLastUse(0),
	fUseCount(0),
	fServer(serv)
{
	ASSERT(serv != NULL);
 
	mutex_init(&fClientIdLock, NULL);
	mutex_init(&fFSLock, NULL);
	mutex_init(&fThreadStartLock, NULL);
 
}
 
 
NFS4Server::~NFS4Server()
{
	fThreadCancel = true;
	fUseCount = 0;
	release_sem(fWaitCancel);
	status_t result;
	wait_for_thread(fThread, &result);
 
	delete_sem(fWaitCancel);
	mutex_destroy(&fClientIdLock);
	mutex_destroy(&fFSLock);
	mutex_destroy(&fThreadStartLock);
}
 
 
uint64
NFS4Server::ServerRebooted(uint64 clientId)
{
	if (clientId != fClientId)
		return fClientId;
 
	fClientId = ClientId(clientId, true);
 
	// reclaim all opened files and held locks from all filesystems
	MutexLocker _(fFSLock);
	FileSystem* fs = fFileSystems.Head();
	while (fs != NULL) {
		DoublyLinkedList<OpenState>::Iterator iterator
			= fs->OpenFilesLock().GetIterator();
		OpenState* current = iterator.Next();
		while (current != NULL) {
			current->Reclaim(fClientId);
 
			current = iterator.Next();
		}
		fs->OpenFilesUnlock();
 
		fs = fFileSystems.GetNext(fs);
	}
 
	return fClientId;
}
 
 
void
NFS4Server::AddFileSystem(FileSystem* fs)
{
	ASSERT(fs != NULL);
 
	MutexLocker _(fFSLock);
	fFileSystems.Add(fs);
 
	fUseCount += fs->OpenFilesCount();
	if (fs->OpenFilesCount() > 0)
		_StartRenewing();
}
 
 
void
NFS4Server::RemoveFileSystem(FileSystem* fs)
{
	ASSERT(fs != NULL);
 
	MutexLocker _(fFSLock);
	fFileSystems.Remove(fs);
	fUseCount -= fs->OpenFilesCount();
}
 
 
uint64
NFS4Server::ClientId(uint64 prevId, bool forceNew)
{
	MutexLocker _(fClientIdLock);
	if ((fUseCount == 0 && fClientIdLastUse + (time_t)LeaseTime() < time(NULL))
		|| (forceNew && fClientId == prevId)) {
 
		Request request(fServer, NULL);
		request.Builder().SetClientID(fServer);
 
		status_t result = request.Send();
		if (result != B_OK)
			return fClientId;
 
		uint64 ver;
		result = request.Reply().SetClientID(&fClientId, &ver);
		if (result != B_OK)
			return fClientId;
 
		request.Reset();
		request.Builder().SetClientIDConfirm(fClientId, ver);
 
		result = request.Send();
		if (result != B_OK)
			return fClientId;
 
		result = request.Reply().SetClientIDConfirm();
		if (result != B_OK)
			return fClientId;
	}
 
	fClientIdLastUse = time(NULL);
	return fClientId;
}
 
 
status_t
NFS4Server::FileSystemMigrated()
{
	// reclaim all opened files and held locks from all filesystems
	MutexLocker _(fFSLock);
	FileSystem* fs = fFileSystems.Head();
	while (fs != NULL) {
		fs->Migrate(fServer);
		fs = fFileSystems.GetNext(fs);
	}
 
	return B_OK;
}
 
 
status_t
NFS4Server::_GetLeaseTime()
{
	Request request(fServer, NULL);
	request.Builder().PutRootFH();
	Attribute attr[] = { FATTR4_LEASE_TIME };
	request.Builder().GetAttr(attr, sizeof(attr) / sizeof(Attribute));
 
	status_t result = request.Send();
	if (result != B_OK)
		return result;
 
	ReplyInterpreter& reply = request.Reply();
 
	reply.PutRootFH();
 
	AttrValue* values;
	uint32 count;
	result = reply.GetAttr(&values, &count);
	if (result != B_OK)
		return result;
	ArrayDeleter<AttrValue> valuesDeleter(values);
 
	// FATTR4_LEASE_TIME is mandatory
	if (count < 1 || values[0].fAttribute != FATTR4_LEASE_TIME)
		return B_BAD_VALUE;
 
	fLeaseTime = values[0].fData.fValue32;
 
	return B_OK;
}
 
 
status_t
NFS4Server::_StartRenewing()
{
	if (!fThreadCancel)
		return B_OK;
 
	MutexLocker _(fThreadStartLock);
 
	if (!fThreadCancel)
		return B_OK;
 
	if (fLeaseTime == 0) {
		status_t result = _GetLeaseTime();
		if (result != B_OK)
			return result;
	}
 
	fThreadCancel = false;
	fThread = spawn_kernel_thread(&NFS4Server::_RenewalThreadStart,
		"NFSv4 Renewal", B_NORMAL_PRIORITY, this);
	if (fThread < B_OK)
		return fThread;
 
	status_t result = resume_thread(fThread);
	if (result != B_OK) {
		kill_thread(fThread);
		return result;
	}
 
	return B_OK;
}
 
 
status_t
NFS4Server::_Renewal()
{
	while (!fThreadCancel) {
		// TODO: operations like OPEN, READ, CLOSE, etc also renew leases
		status_t result = acquire_sem_etc(fWaitCancel, 1,
			B_RELATIVE_TIMEOUT, sSecToBigTime(fLeaseTime - 2));
		if (result != B_TIMED_OUT) {
			if (result == B_OK)
				release_sem(fWaitCancel);
			return result;
		}
 
		uint64 clientId = fClientId;
 
		if (fUseCount == 0) {
			MutexLocker _(fFSLock);
			if (fUseCount == 0) {
				fThreadCancel = true;
				return B_OK;
			}
		}
 
		Request request(fServer, NULL);
		request.Builder().Renew(clientId);
		result = request.Send();
		if (result != B_OK)
			continue;
 
		switch (request.Reply().NFS4Error()) {
			case NFS4ERR_CB_PATH_DOWN:
				RecallAll();
				break;
			case NFS4ERR_STALE_CLIENTID:
				ServerRebooted(clientId);
				break;
			case NFS4ERR_LEASE_MOVED:
				FileSystemMigrated();
				break;
		}
	}
 
	return B_OK;
}
 
 
status_t
NFS4Server::_RenewalThreadStart(void* ptr)
{
	ASSERT(ptr != NULL);
	NFS4Server* server = reinterpret_cast<NFS4Server*>(ptr);
	return server->_Renewal();
}
 
 
status_t
NFS4Server::ProcessCallback(RPC::CallbackRequest* request,
	Connection* connection)
{
	ASSERT(request != NULL);
	ASSERT(connection != NULL);
 
	RequestInterpreter req(request);
	ReplyBuilder reply(request->XID());
 
	status_t result;
	uint32 count = req.OperationCount();
 
	for (uint32 i = 0; i < count; i++) {
		switch (req.Operation()) {
			case OpCallbackGetAttr:
				result = CallbackGetAttr(&req, &reply);
				break;
			case OpCallbackRecall:
				result = CallbackRecall(&req, &reply);
				break;
			default:
				result = B_NOT_SUPPORTED;
		}
 
		if (result != B_OK)
			break;
	}
 
	XDR::WriteStream& stream = reply.Reply()->Stream();
	connection->Send(stream.Buffer(), stream.Size());
 
	return B_OK;
}
 
 
status_t
NFS4Server::CallbackRecall(RequestInterpreter* request, ReplyBuilder* reply)
{
	ASSERT(request != NULL);
	ASSERT(reply != NULL);
 
	uint32 stateID[3];
	uint32 stateSeq;
	bool truncate;
	FileHandle handle;
 
	status_t result = request->Recall(&handle, truncate, &stateSeq, stateID);
	if (result != B_OK)
		return result;
 
	MutexLocker locker(fFSLock);
 
	Delegation* delegation = NULL;
	FileSystem* current = fFileSystems.Head();
	while (current != NULL) {
		delegation = current->GetDelegation(handle);
		if (delegation != NULL)
			break;
 
		current = fFileSystems.GetNext(current);
	}
	locker.Unlock();
 
	if (delegation == NULL) {
		reply->Recall(B_FILE_NOT_FOUND);
		return B_FILE_NOT_FOUND;
	}
 
	DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
	args->fDelegation = delegation;
	args->fTruncate = truncate;
	gWorkQueue->EnqueueJob(DelegationRecall, args);
 
	reply->Recall(B_OK);
 
	return B_OK;
}
 
 
status_t
NFS4Server::CallbackGetAttr(RequestInterpreter* request, ReplyBuilder* reply)
{
	ASSERT(request != NULL);
	ASSERT(reply != NULL);
 
	FileHandle handle;
	int mask;
 
	status_t result = request->GetAttr(&handle, &mask);
	if (result != B_OK)
		return result;
 
	MutexLocker locker(fFSLock);
 
	Delegation* delegation = NULL;
	FileSystem* current = fFileSystems.Head();
	while (current != NULL) {
		delegation = current->GetDelegation(handle);
		if (delegation != NULL)
			break;
 
		current = fFileSystems.GetNext(current);
	}
	locker.Unlock();
 
	if (delegation == NULL) {
		reply->GetAttr(B_FILE_NOT_FOUND, 0, 0, 0);
		return B_FILE_NOT_FOUND;
	}
 
	struct stat st;
	delegation->GetInode()->Stat(&st);
 
	uint64 change;
	change = delegation->GetInode()->Change();
	if (delegation->GetInode()->Dirty())
		change++;
	reply->GetAttr(B_OK, mask, st.st_size, change);
 
	return B_OK;
}
 
 
status_t
NFS4Server::RecallAll()
{
	MutexLocker _(fFSLock);
	FileSystem* fs = fFileSystems.Head();
	while (fs != NULL) {
		DoublyLinkedList<Delegation>& list = fs->DelegationsLock();
		DoublyLinkedList<Delegation>::Iterator iterator = list.GetIterator();
 
		Delegation* current = iterator.Next();
		while (current != NULL) {
			DelegationRecallArgs* args = new(std::nothrow) DelegationRecallArgs;
			args->fDelegation = current;
			args->fTruncate = false;
			gWorkQueue->EnqueueJob(DelegationRecall, args);
 
			current = iterator.Next();
		}	
		fs->DelegationsUnlock();
 
		fs = fFileSystems.GetNext(fs);
	}
 
	return B_OK;
}
 

V547 Expression 'fUseCount == 0' is always true.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fThread, fClientId.