/*
 * Simple read-only NFS v3 server.
 * Runs every request in its own thread.
 * Expects client to provide the fsxxx routines in nfs3srv.h.
 */
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <sunrpc.h>
#include <nfs3.h>
#include "nfs3srv.h"

static SunStatus
authunixunpack(SunRpc *rpc, SunAuthUnix *au)
{
	uchar *p, *ep;
	SunAuthInfo *ai;

	ai = &rpc->cred;
	if(ai->flavor != SunAuthSys)
		return SunAuthTooWeak;
	p = ai->data;
	ep = p+ai->ndata;
	if(sunauthunixunpack(p, ep, &p, au) < 0)
		return SunGarbageArgs;
	if(au->uid == 0)
		au->uid = -1;
	if(au->gid == 0)
		au->gid = -1;

	return SunSuccess;
}

static int
rnull(SunMsg *m)
{
	NfsMount3RNull rx;

	memset(&rx, 0, sizeof rx);
	return sunmsgreply(m, &rx.call);
}

static int
rmnt(SunMsg *m)
{
	Nfs3Handle nh;
	NfsMount3RMnt rx;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	/* ignore file system path and return the dump tree */

	memset(&rx, 0, sizeof rx);
	rx.nauth = 0;
	rx.status = 0;
	memset(&nh, 0, sizeof nh);
	fsgetroot(&nh);
	rx.handle = nh.h;
	rx.len = nh.len;

	return sunmsgreply(m, &rx.call);
}

static int
rumnt(SunMsg *m)
{
	NfsMount3RUmnt rx;

	/* ignore */
	
	memset(&rx, 0, sizeof rx);
	return sunmsgreply(m, &rx.call);
}

static int
rumntall(SunMsg *m)
{
	NfsMount3RUmntall rx;

	/* ignore */

	memset(&rx, 0, sizeof rx);
	return sunmsgreply(m, &rx.call);
}

static int
rexport(SunMsg *m)
{
	NfsMount3RExport rx;

	/* ignore */

	memset(&rx, 0, sizeof rx);
	rx.count = 0;
	return sunmsgreply(m, &rx.call);
}

static void
rmount3(void *v)
{
	SunMsg *m;

	m = v;
	switch(m->call->type){
	default:
		sunmsgreplyerror(m, SunProcUnavail);
	case NfsMount3CallTNull:
		rnull(m);
		break;
	case NfsMount3CallTMnt:
		rmnt(m);
		break;
	case NfsMount3CallTDump:
		rmnt(m);
		break;
	case NfsMount3CallTUmnt:
		rumnt(m);
		break;
	case NfsMount3CallTUmntall:
		rumntall(m);
		break;
	case NfsMount3CallTExport:
		rexport(m);
		break;
	}
}

void
mount3proc(void *v)
{
	Channel *c;
	SunMsg *m;

	threadsetname("mount1");
	c = v;
	while((m=recvp(c)) != nil)
		threadcreate(rmount3, m, SunStackSize);
}

static int
senderror(SunMsg *m, SunCall *rc, Nfs3Status status)
{
	/* knows that status is first field in all replies */
	((Nfs3RGetattr*)rc)->status = status;
	return sunmsgreply(m, rc);
}

static int
rnull0(SunMsg *m)
{
	Nfs3RNull rx;

	memset(&rx, 0, sizeof rx);
	return sunmsgreply(m, &rx.call);
}

static int
rgetattr(SunMsg *m)
{
	Nfs3TGetattr *tx = (Nfs3TGetattr*)m->call;
	Nfs3RGetattr rx;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	memset(&rx, 0, sizeof rx);
	rx.status = fsgetattr(&au, &tx->handle, &rx.attr);
	return sunmsgreply(m, &rx.call);
}

static int
rlookup(SunMsg *m)
{
	Nfs3TLookup *tx = (Nfs3TLookup*)m->call;
	Nfs3RLookup rx;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	memset(&rx, 0, sizeof rx);
	rx.status = fsgetattr(&au, &tx->handle, &rx.dirAttr);
	if(rx.status != Nfs3Ok)
		return sunmsgreply(m, &rx.call);
	rx.haveDirAttr = 1;
	rx.status = fslookup(&au, &tx->handle, tx->name, &rx.handle);
	if(rx.status != Nfs3Ok)
		return sunmsgreply(m, &rx.call);
	rx.status = fsgetattr(&au, &rx.handle, &rx.attr);
	if(rx.status != Nfs3Ok)
		return sunmsgreply(m, &rx.call);
	rx.haveAttr = 1;
	return sunmsgreply(m, &rx.call);
}

static int
raccess(SunMsg *m)
{
	Nfs3TAccess *tx = (Nfs3TAccess*)m->call;
	Nfs3RAccess rx;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	memset(&rx, 0, sizeof rx);
	rx.haveAttr = 1;
	rx.status = fsaccess(&au, &tx->handle, tx->access, &rx.access, &rx.attr);
	return sunmsgreply(m, &rx.call);
}

static int
rreadlink(SunMsg *m)
{
	Nfs3RReadlink rx;
	Nfs3TReadlink *tx = (Nfs3TReadlink*)m->call;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	memset(&rx, 0, sizeof rx);
	rx.haveAttr = 0;
	rx.data = nil;
	rx.status = fsreadlink(&au, &tx->handle, &rx.data);
	sunmsgreply(m, &rx.call);
	free(rx.data);
	return 0;
}

static int
rread(SunMsg *m)
{
	Nfs3TRead *tx = (Nfs3TRead*)m->call;
	Nfs3RRead rx;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	memset(&rx, 0, sizeof rx);
	rx.haveAttr = 0;
	rx.data = nil;
	rx.status = fsreadfile(&au, &tx->handle, tx->count, tx->offset, &rx.data, &rx.count, &rx.eof);
	if(rx.status == Nfs3Ok)
		rx.ndata = rx.count;

	sunmsgreply(m, &rx.call);
	free(rx.data);
	return 0;
}

static int
rreaddir(SunMsg *m)
{
	Nfs3TReadDir *tx = (Nfs3TReadDir*)m->call;
	Nfs3RReadDir rx;
	SunAuthUnix au;
	int ok;

	if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
		return sunmsgreplyerror(m, ok);

	memset(&rx, 0, sizeof rx);
	rx.status = fsreaddir(&au, &tx->handle, tx->count, tx->cookie, &rx.data, &rx.count, &rx.eof);
	sunmsgreply(m, &rx.call);
	free(rx.data);
	return 0;
}

static int
rreaddirplus(SunMsg *m)
{
	Nfs3RReadDirPlus rx;

	memset(&rx, 0, sizeof rx);
	rx.status = Nfs3ErrNotSupp;
	sunmsgreply(m, &rx.call);
	return 0;
}

static int
rfsstat(SunMsg *m)
{
	Nfs3RFsStat rx;

	/* just make something up */
	memset(&rx, 0, sizeof rx);
	rx.status = Nfs3Ok;
	rx.haveAttr = 0;
	rx.totalBytes = 1000000000;
	rx.freeBytes = 0;
	rx.availBytes = 0;
	rx.totalFiles = 100000;
	rx.freeFiles = 0;
	rx.availFiles = 0;
	rx.invarSec = 0;
	return sunmsgreply(m, &rx.call);
}

static int
rfsinfo(SunMsg *m)
{
	Nfs3RFsInfo rx;

	/* just make something up */
	memset(&rx, 0, sizeof rx);
	rx.status = Nfs3Ok;
	rx.haveAttr = 0;
	rx.readMax = MaxDataSize;
	rx.readPref = MaxDataSize;
	rx.readMult = MaxDataSize;
	rx.writeMax = MaxDataSize;
	rx.writePref = MaxDataSize;
	rx.writeMult = MaxDataSize;
	rx.readDirPref = MaxDataSize;
	rx.maxFileSize = 1LL<<60;
	rx.timePrec.sec = 1;
	rx.timePrec.nsec = 0;
	rx.flags = Nfs3FsHomogeneous|Nfs3FsCanSetTime;
	return sunmsgreply(m, &rx.call);
}

static int
rpathconf(SunMsg *m)
{
	Nfs3RPathconf rx;

	memset(&rx, 0, sizeof rx);
	rx.status = Nfs3Ok;
	rx.haveAttr = 0;
	rx.maxLink = 1;
	rx.maxName = 1024;
	rx.noTrunc = 1;
	rx.chownRestricted = 0;
	rx.caseInsensitive = 0;
	rx.casePreserving = 1;
	return sunmsgreply(m, &rx.call);
}

static int
rrofs(SunMsg *m)
{
	uchar buf[512];	/* clumsy hack*/

	memset(buf, 0, sizeof buf);
	return senderror(m, (SunCall*)buf, Nfs3ErrRoFs);
}
	

static void
rnfs3(void *v)
{
	SunMsg *m;

	m = v;
	switch(m->call->type){
	default:
		abort();
	case Nfs3CallTNull:
		rnull0(m);
		break;
	case Nfs3CallTGetattr:
		rgetattr(m);
		break;
	case Nfs3CallTLookup:
		rlookup(m);
		break;
	case Nfs3CallTAccess:
		raccess(m);
		break;
	case Nfs3CallTReadlink:
		rreadlink(m);
		break;
	case Nfs3CallTRead:
		rread(m);
		break;
	case Nfs3CallTReadDir:
		rreaddir(m);
		break;
	case Nfs3CallTReadDirPlus:
		rreaddirplus(m);
		break;
	case Nfs3CallTFsStat:
		rfsstat(m);
		break;
	case Nfs3CallTFsInfo:
		rfsinfo(m);
		break;
	case Nfs3CallTPathconf:
		rpathconf(m);
		break;
	case Nfs3CallTSetattr:
	case Nfs3CallTWrite:
	case Nfs3CallTCreate:
	case Nfs3CallTMkdir:
	case Nfs3CallTSymlink:
	case Nfs3CallTMknod:
	case Nfs3CallTRemove:
	case Nfs3CallTRmdir:
	case Nfs3CallTLink:
	case Nfs3CallTCommit:
		rrofs(m);
		break;
	}
}

void
nfs3proc(void *v)
{
	Channel *c;
	SunMsg *m;

	c = v;
	threadsetname("nfs3");
	while((m = recvp(c)) != nil)
		threadcreate(rnfs3, m, SunStackSize);
}