Plan 9 from Bell Labs’s /usr/web/sources/contrib/blstuart/θfs/nfs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * Copyright (c) 2013, Coraid, Inc.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Coraid nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CORAID BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <ip.h>
#include "dat.h"

/* RPC -- RFC 1057 */
enum {
	AUTH_NULL = 0,
	AUTH_UNIX,
	AUTH_SHORT,
	AUTH_DES,

	CALL = 0,
	REPLY,

	MSG_ACCEPTED = 0,
	MSG_DENIED,

	SUCCESS = 0,
	PROG_UNAVAIL,
	PROG_MISMATCH,
	PROC_UNAVAIL,
	GARBAGE_ARGS,

	RPC_MISMATCH = 0,
	AUTH_ERROR,

	AUTH_BADCRED = 1,
	AUTH_REJECTEDCRED,
	AUTH_BADVERF,
	AUTH_REJECTEDVERF,
	AUTH_TOOWEAK,

	PMAP_PROG = 100000,
	PMAP_VERS = 2,
	PMAP_PORT = 111,
	IPPROTO_TCP = 6,
	IPPROTO_UDP = 17,

	PMAPPROC_NULL = 0,
	PMAPPROC_SET,
	PMAPPROC_UNSET,
	PMAPPROC_GETPORT,
	PMAPPROC_DUMP,
	PMAPPROC_CALLIT,
};

/* NFSv3 -- RFC 1813 */
enum {
	NFS_PROG = 100003,
	NFS_VERS = 3,
	NFS_PORT = 2049,

	NFS3_FHSIZE = 64,
	NFS3_COOKIEVERFSIZE = 8,
	NFS3_CREATEVERFSIZE = 8,
	NFS3_WRITEVERFSIZE = 8,

	NFS3_OK = 0,
	NFS3ERR_PERM,
	NFS3ERR_NOENT,
	NFS3ERR_IO = 5,
	NFS3ERR_NXIO,
	NFS3ERR_ACCES = 13,
	NFS3ERR_EXIST = 17,
	NFS3ERR_XDEV,
	NFS3ERR_NODEV,
	NFS3ERR_NOTDIR,
	NFS3ERR_ISDIR,
	NFS3ERR_INVAL,
	NFS3ERR_FBIG = 27,
	NFS3ERR_NOSPC,
	NFS3ERR_ROFS = 30,
	NFS3ERR_MLINK,
	NFS3ERR_NAMETOOLONG = 63,
	NFS3ERR_NOTEMPTY = 66,
	NFS3ERR_DQUOT = 69,
	NFS3ERR_STALE,
	NFS3ERR_REMOTE,
	NFS3ERR_BADHANDLE = 10001,
	NFS3ERR_NOT_SYNC,
	NFS3ERR_BAD_COOKIE,
	NFS3ERR_NOTSUPP,
	NFS3ERR_TOOSMALL,
	NFS3ERR_SERVERFAULT,
	NFS3ERR_BADTYPE,
	NFS3ERR_JUKEBOX,

	NF3REG = 1,
	NF3DIR,
	NF3BLK,
	NF3CHR,
	NF3LNK,
	NF3SOCK,
	NF3FIFO,

	DONT_CHANGE = 0,
	SET_TO_SERVER_TIME,
	SET_TO_CLIENT_TIME,

	NFSPROC3_NULL = 0,
	NFSPROC3_GETATTR,
	NFSPROC3_SETATTR,
	NFSPROC3_LOOKUP,
	NFSPROC3_ACCESS,
	NFSPROC3_READLINK,
	NFSPROC3_READ,
	NFSPROC3_WRITE,
	NFSPROC3_CREATE,
	NFSPROC3_MKDIR,
	NFSPROC3_SYMLINK,
	NFSPROC3_MKNOD,
	NFSPROC3_REMOVE,
	NFSPROC3_RMDIR,
	NFSPROC3_RENAME,
	NFSPROC3_LINK,
	NFSPROC3_READDIR,
	NFSPROC3_READDIRPLUS,
	NFSPROC3_FSSTAT,
	NFSPROC3_FSINFO,
	NFSPROC3_PATHCONF,
	NFSPROC3_COMMIT,

	ACCESS3_READ = 0x0001,
	ACCESS3_LOOKUP = 0x0002,
	ACCESS3_MODIFY = 0x0004,
	ACCESS3_EXTEND = 0x0008,
	ACCESS3_DELETE = 0x0010,
	ACCESS3_EXECUTE = 0x0020,

	UNSTABLE = 0,
	DATA_SYNC,
	FILE_SYNC,

	UNCHECKED = 0,
	GUARDED,
	EXCLUSIVE,

	FSF3_LINK = 0x0001,
	FSF3_SYMLINK = 0x0002,
	FSF3_HOMOGENEOUS= 0x0008,
	FSF3_CANSETTIME = 0x0010,

	MNT_PROG = 100005,
	MNT_MIN_VERS = 2,
	MNT_MAX_VERS = 3,
	MNT_PORT = 4003,

	MNTPATHLEN = 1024,
	MNTNAMELEN = 255,
	FHSIZE3 = NFS3_FHSIZE,

	MNT3_OK = 0,
	MNT3ERR_PERM,
	MNT3ERR_NOENT,
	MNT3ERR_IO = 5,
	MNT3ERR_ACCES = 13,
	MNT3ERR_NOTDIR = 20,
	MNT3ERR_INVAL = 22,
	MNT3ERR_NAMETOOLONG = 63,
	MNT3ERR_NOTSUPP = 10004,
	MNT3ERR_SERVERFAULT = 10006,

	MOUNTPROC3_NULL = 0,
	MOUNTPROC3_MNT,
	MOUNTPROC3_DUMP,
	MOUNTPROC3_UMNT,
	MOUNTPROC3_UMNTALL,
	MOUNTPROC3_EXPORT,

	NLM_PROG = 100021,
	NLM_VERS = 4,
	NLM_PORT = 4002,

	NLM4_GRANTED = 0,
	NLM4_DENIED,
	NLM4_DENIED_NLOCKS,
	NLM4_BLOCKED,
	NLM4_DENIED_GRACE_PERIOD,
	NLM4_DEADLOCK,
	NLM4_ROFS,
	NLM4_STALE_FH,
	NLM4_FBIG,
	NLM4_FAILED,

	NLMPROC4_NULL = 0,
	NLMPROC4_TEST,
	NLMPROC4_LOCK,
	NLMPROC4_CANCEL,
	NLMPROC4_UNLOCK,
	NLMPROC4_GRANTED,
	NLMPROC4_TEST_MSG,
	NLMPROC4_LOCK_MSG,
	NLMPROC4_CANCEL_MSG,
	NLMPROC4_UNLOCK_MSG,
	NLMPROC4_GRANTED_MSG,
	NLMPROC4_TEST_RES,
	NLMPROC4_LOCK_RES,
	NLMPROC4_CANCEL_RES,
	NLMPROC4_UNLOCK_RES,
	NLMPROC4_GRANTED_RES,
	NLMPROC4_SHARE = 20,
	NLMPROC4_UNSHARE,
	NLMPROC4_NM_LOCK,
	NLMPROC4_FREE_ALL,
};

typedef struct Rcb Rcb;

struct Rcb {
	int inuse;
	int fd;
	Ioproc *io;
	ulong myprog;
	ulong minver;
	ulong maxver;
	int (*dispatch)(char *, char *, ulong, char *, char *, ulong);
	Rcb *next;
};

static Channel *upchan, *tpchan, *mchan, *nchan;
static Rcb *rcbhd;
static int nfstid, mounttid, tmaptid, umaptid;

int debugnfs;

static int
round4(int x)
{
	return (x + 3) & ~3;
}

static char *
rpcputl(char *p, ulong l)
{
	hnputl(p, l);
	return p + 4;
}

static char *
rpcputv(char *p, uvlong v)
{
	hnputv(p, v);
	return p + 8;
}

static char *
getauth(char **pp)
{
	char *a;
	int n;

	n = nhgetl(*pp + 4);
	a = malloc(n + 8);
	memmove(a, *pp, n + 8);
	*pp += n + 8;
	return a;
}

static char *
putauth(char *p, char *verf)
{
	int n;

	n = nhgetl(verf + 4);
	memmove(p, verf, n + 8);
	return p + n + 8;
}

static char *
initreply(char *buf, ulong xid, ulong stat, void *verf, int rstat)
{
	char *p;

	p = buf;
	p = rpcputl(p, xid);
	p = rpcputl(p, REPLY);
	p = rpcputl(p, stat);
	if(stat == MSG_ACCEPTED)
		p = putauth(p, verf);
	p = rpcputl(p, rstat);
	return p;
}

static void
tcprpcreader(void *a)
{
	Rcb *r;
	char *buf, *p, *auth, *verf;
	ulong xid, mtype, rpcvers, prog, vers, proc;
	int n;

	r = a;
	buf = malloc(34004);
	while(1) {
		n = ioreadn(r->io, r->fd, buf, 4);
		if(shutdown || n < 4) {
			free(buf);
			ioclose(r->io, r->fd);
			r->inuse = 0;
			threadexits(nil);
		}
		n = nhgetl(buf) & 0x7fffffff;
		if(n > 34000) {
			fprint(2, "bogus read size: %d\n", n);
			continue;
		}
		n = ioreadn(r->io, r->fd, buf+4, n);
		if(n <= 0) {
			if(debugnfs)
				fprint(2, "leaving tcpreader for prog %uld\n", r->myprog);
			free(buf);
			ioclose(r->io, r->fd);
			r->inuse = 0;
			threadexits(nil);
		}
		/* if we don't at least have the xid and mtype, ignore */
		if(n < 8)
			continue;
		p = buf+4;
		xid = nhgetl(p);
		p += 4;
		mtype = nhgetl(p);
		p += 4;
		/* we're only a server - ignore replies */
		if(mtype != CALL)
			continue;
		rpcvers = nhgetl(p);
		p += 4;
		prog = nhgetl(p);
		p += 4;
		vers = nhgetl(p);
		p += 4;
		proc = nhgetl(p);
		p += 4;
		if(debugnfs)
			fprint(2, "got message in prog %uld len=%d xid=%uld(%ulx) mtype=%uld rpcvers=%uld prog=%uld vers=%uld proc=%uld\n", r->myprog, n, xid, xid, mtype, rpcvers, prog, vers, proc);
		if(rpcvers != 2) {
			p = initreply(buf+4, xid, MSG_DENIED, nil, RPC_MISMATCH);
			p = rpcputl(p, 2);
			p = rpcputl(p, 2);
			hnputl(buf, (p-(buf+4)) | 0x80000000);
			iowrite(r->io, r->fd, buf, p-buf);
			continue;
		}
		auth = getauth(&p);
		verf = getauth(&p);
		if(prog != r->myprog) {
			p = initreply(buf+4, xid, MSG_ACCEPTED, verf, PROG_UNAVAIL);
			hnputl(buf, (p-(buf+4)) | 0x80000000);
			iowrite(r->io, r->fd, buf, p-buf);
			free(auth);
			free(verf);
			continue;
		}
		if(vers < r->minver || vers > r->maxver) {
			p = initreply(buf+4, xid, MSG_ACCEPTED, verf, PROG_MISMATCH);
			p = rpcputl(p, r->minver);
			p = rpcputl(p, r->maxver);
			hnputl(buf, (p-(buf+4)) | 0x80000000);
			iowrite(r->io, r->fd, buf, p-buf);
			free(auth);
			free(verf);
			continue;
		}
		n = r->dispatch(buf+4, p, xid, auth, verf, proc);
		if(debugnfs) {
			fprint(2, "writing %d bytes in response\n", n);
			if(debugnfs > 1) {
				int i;
				for(i = 0; i < n+4; i += 4) fprint(2, " %ud", nhgetl(buf + i));
				fprint(2, "\n");
			}
		}
		hnputl(buf, n | 0x80000000);
		iowrite(r->io, r->fd, buf, n+4);
		free(auth);
		free(verf);
	}
}

static void
udprpcreader(void *a)
{
	Rcb *r;
	char *buf, *p, *auth, *verf;
	ulong xid, mtype, rpcvers, prog, vers, proc;
	int n;

	r = a;
	buf = malloc(8500);
	n = ioread(r->io, r->fd, buf, 8500);
	if(shutdown || n <= 0)
		goto done2;
	/* if we don't at least have the xid and mtype, ignore */
	if(n < 8)
		goto done2;
	p = buf;
	xid = nhgetl(p);
	p += 4;
	mtype = nhgetl(p);
	p += 4;
	if(debugnfs)
		fprint(2, "got message in prog %uld len=%d xid=%uld(%ulx) mtype=%uld\n", r->myprog, n, xid, xid, mtype);
	/* we're only a server - ignore replies */
	if(mtype != CALL)
		goto done2;
	rpcvers = nhgetl(p);
	p += 4;
	prog = nhgetl(p);
	p += 4;
	vers = nhgetl(p);
	p += 4;
	proc = nhgetl(p);
	p += 4;
	if(debugnfs)
		fprint(2, "rpcvers=%uld prog=%uld vers=%uld proc=%uld\n", rpcvers, prog, vers, proc);
	if(rpcvers != 2) {
		p = initreply(buf, xid, MSG_DENIED, nil, RPC_MISMATCH);
		p = rpcputl(p, 2);
		p = rpcputl(p, 2);
		iowrite(r->io, r->fd, buf, p-buf);
		goto done2;
	}
	auth = getauth(&p);
	verf = getauth(&p);
	if(prog != r->myprog) {
		p = initreply(buf, xid, MSG_ACCEPTED, verf, PROG_UNAVAIL);
		iowrite(r->io, r->fd, buf, p-buf);
		goto done1;
	}
	if(vers < r->minver || vers > r->maxver) {
		p = initreply(buf, xid, MSG_ACCEPTED, verf, PROG_MISMATCH);
		p = rpcputl(p, r->minver);
		p = rpcputl(p, r->maxver);
		iowrite(r->io, r->fd, buf, p-buf);
		goto done1;
	}
	n = r->dispatch(buf, p, xid, auth, verf, proc);
	if(debugnfs) {
		fprint(2, "writing %d bytes in response\n", n);
		if(debugnfs > 1) {
			int i;
			for(i = 0; i < n; i += 4) fprint(2, " %ud", nhgetl(buf + i));
			fprint(2, "\n");
		}
	}
	iowrite(r->io, r->fd, buf, n);
done1:
	free(auth);
	free(verf);
done2:
	free(buf);
	ioclose(r->io, r->fd);
	r->inuse = 0;
	threadexits(nil);
}

static char *
rpcnull(char *buf, ulong xid, char *verf)
{
	char *rp;

	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, 0);
	return rp;
}

/*
 * Fake Port Mapper
 */
static int
pmapdis(char *buf, char *p, ulong xid, char *auth, char *verf, ulong proc)
{
	char *rp;
	ulong prog, vers, prot, nproc;

	switch(proc) {
	case PMAPPROC_NULL:
		rp = rpcnull(buf, xid, verf);
		break;
	case PMAPPROC_GETPORT:
		prog = nhgetl(p);
		p += 4;
		vers = nhgetl(p);
		p += 4;
		prot = nhgetl(p);
		if(debugnfs)
			fprint(2, "In portmap getport prog=%uld vers=%uld prot=%uld\n", prog, vers, prot);
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		hnputl(rp, 0);
		switch(prog) {
		case NFS_PROG:
			if(vers == NFS_VERS && prot == IPPROTO_TCP)
				hnputl(rp, NFS_PORT);
			break;
		case MNT_PROG:
			if(vers >= MNT_MIN_VERS && vers <= MNT_MAX_VERS && prot == IPPROTO_TCP)
				hnputl(rp, MNT_PORT);
			break;
		case NLM_PROG:
			if(vers == NLM_VERS && prot == IPPROTO_TCP)
				hnputl(rp, NLM_PORT);
			break;
		}
		rp += 4;
		break;
	case PMAPPROC_DUMP:
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, 1);
		rp = rpcputl(rp, NFS_PROG);
		rp = rpcputl(rp, NFS_VERS);
		rp = rpcputl(rp, IPPROTO_TCP);
		rp = rpcputl(rp, NFS_PORT);
		rp = rpcputl(rp, 1);
		rp = rpcputl(rp, MNT_PROG);
		rp = rpcputl(rp, MNT_MAX_VERS);
		rp = rpcputl(rp, IPPROTO_TCP);
		rp = rpcputl(rp, MNT_PORT);
		rp = rpcputl(rp, 1);
		rp = rpcputl(rp, NLM_PROG);
		rp = rpcputl(rp, NLM_VERS);
		rp = rpcputl(rp, IPPROTO_TCP);
		rp = rpcputl(rp, NLM_PORT);
		rp = rpcputl(rp, 0);
		break;
	case PMAPPROC_CALLIT:
SET(nproc);
USED(nproc);
USED(auth);
/*
		prog = nhgetl(p);
		p += 4;
		vers = nhgetl(p);
		p += 4;
		nproc = nhgetl(p);
		p += 4;
		switch(prog) {
		case NFS_PROG:
			return nfsdis(buf, p, xid, auth, verf, nproc);
			break;
		case MNT_PROG:
			return mntdis(buf, p, xid, auth, verf, nproc);
			break;
		case NLM_PROG:
			return nlmdis(buf, p, xid, auth, verf, nproc);
			break;
		default:
			rp = initreply(buf, xid, MSG_ACCEPTED, verf);
			break;
		}
		break;
*/
	case PMAPPROC_SET:		/* not used here for fake port mapper */
	case PMAPPROC_UNSET:
	default:
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, PROG_UNAVAIL);
		rp += 4;
		break;
	}
	return rp - buf;
}

static char *
domnt(char *buf, char *p, ulong xid, char *, char *verf)
{
	Qid qid;
	char *rp, *path;
	uvlong meta, x;
	int n;

	n = nhgetl(p);
	path = malloc(n + 1);
	memmove(path, p + 4, n);
	path[n] = 0;
	if(debugnfs)
		fprint(2, "Attempting to mount %s qpath=%ulld\n", path, p2q(-1, path, 0));
	meta = q2m(-1, p2q(-1, path, 0), 0);
	free(path);
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	if(meta == 0) {
		rp = rpcputl(rp, MNT3ERR_NOENT);
		return rp;
	}
	if(getmetaint(-1, meta, "qpath", &x) == MTnone) {
		rp = rpcputl(rp, MNT3ERR_IO);
		return rp;
	}
	qid.path = x;
	getmetaint(-1, meta, "qvers", &x);
	qid.vers = x;
	getmetaint(-1, meta, "qtype", &x);
	qid.type = x;
	if(!(qid.type & QTDIR)) {
		rp = rpcputl(rp, MNT3ERR_NOTDIR);
		return rp;
	}
	if(debugnfs)
		fprint(2, "meta=%ulld qid=(%ulld,%uld,%d)\n", meta, qid.path, qid.vers, qid.type);
	rp = rpcputl(rp, MNT3_OK);
	rp = rpcputl(rp, sizeof(Qid));
	memmove(rp, &qid, sizeof(Qid));
	rp += round4(sizeof(Qid));
	rp = rpcputl(rp, 1);
	rp = rpcputl(rp, AUTH_UNIX);
	return rp;
}

static int
mntdis(char *buf, char *p, ulong xid, char *auth, char *verf, ulong proc)
{
	char *rp;

	switch(proc) {
	case MOUNTPROC3_NULL:
		rp = rpcnull(buf, xid, verf);
		break;
	case MOUNTPROC3_MNT:
		rp = domnt(buf, p, xid, auth, verf);
		break;
	case MOUNTPROC3_DUMP:
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		hnputl(rp, 0);
		rp += 4;
		break;
	case MOUNTPROC3_UMNT:
		rp = rpcnull(buf, xid, verf);
		break;
	case MOUNTPROC3_UMNTALL:
		rp = rpcnull(buf, xid, verf);
		break;
	case MOUNTPROC3_EXPORT:
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, 1);
		rp = rpcputl(rp, 1);
		memmove(rp, "/\0\0\0", 4);
		rp += 4;
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		break;
	default:
		rp = initreply(buf, xid, MSG_DENIED, verf, PROC_UNAVAIL);
		break;
	}
	return rp - buf;
}

static char *
fattr3(int fd, char *rp, Qid *qid, char *auth)
{
	char *symlink, *ugid, *host;
	uvlong meta, len, mtime, x;
	int n;

	meta = q2m(fd, qid->path, 0);
	if(meta == 0)
		return nil;
	host = nil;
	switch(nhgetl(auth)) {
	case AUTH_UNIX:
		auth += 12;
		n = nhgetl(auth);
		host = emalloc9p(n + 1);
		auth += 4;
		memmove(host, auth, n);
		break;
	default:		/* We're going to ignore the others for now */
		break;
	}
	if(qid->type & QTDIR)
		rp = rpcputl(rp, NF3DIR);
	else {
		if((symlink = getmetastr(fd, meta, "symlink")) != nil) {
			rp = rpcputl(rp, NF3LNK);
			free(symlink);
		}
		else if(getmetaint(fd, meta, "nodetype", &x) != MTnone)
			rp = rpcputl(rp, x);
		else
			rp = rpcputl(rp, NF3REG);
	}
	if(getmetaint(fd, meta, "unixmode", &x) != MTnone)
		rp = rpcputl(rp, x);
	else if(getmetaint(fd, meta, "mode", &x) != MTnone)
		rp = rpcputl(rp, x & 0777);
	else
		rp = rpcputl(rp, 0777);
	rp = rpcputl(rp, 1);	/* nlink */
	if(getmetaint(fd, meta, "nuid", &x) != MTnone)	/* uid */
		rp = rpcputl(rp, x);
	else {
		ugid = getmetastr(fd, meta, "uid");
		rp = rpcputl(rp, uname2id(host, ugid));
		free(ugid);
	}
	if(getmetaint(fd, meta, "ngid", &x) != MTnone)	/* gid */
		rp = rpcputl(rp, x);
	else {
		ugid = getmetastr(fd, meta, "gid");
		rp = rpcputl(rp, gname2id(host, ugid));
		free(ugid);
	}
	if(getmetaint(fd, meta, "length", &len) == MTnone)
		len = 0;
	rp = rpcputv(rp, len);	/* size */
	if(getmetaint(fd, meta, "used", &x) == MTnone)
		rp = rpcputv(rp, len);
	else
		rp = rpcputv(rp, x);
	if(getmetaint(fd, meta, "majordev", &x) == MTnone)		/* rdev */
		rp = rpcputl(rp, 0);
	else
		rp = rpcputl(rp, x);
	if(getmetaint(fd, meta, "minordev", &x) == MTnone)
		rp = rpcputl(rp, 0);
	else
		rp = rpcputl(rp, x);
	rp = rpcputv(rp, 0);		/* fsid */
	rp = rpcputv(rp, qid->path);	/* fileid */
	if(getmetaint(fd, meta, "atime", &x) == MTnone)
		rp = rpcputv(rp, 0);
	else {
		rp = rpcputl(rp, x / 1000000000LL);
		rp = rpcputl(rp, x % 1000000000LL);
	}
	if(getmetaint(fd, meta, "mtime", &x) == MTnone)
		mtime = 0;
	else
		mtime = x;
	rp = rpcputl(rp, mtime / 1000000000LL);
	rp = rpcputl(rp, mtime % 1000000000LL);
	if(getmetaint(fd, meta, "ctime", &x) == MTnone) {
		rp = rpcputl(rp, mtime / 1000000000LL);
		rp = rpcputl(rp, mtime % 1000000000LL);
	}
	else {
		rp = rpcputl(rp, x / 1000000000LL);
		rp = rpcputl(rp, x % 1000000000LL);
	}
	free(host);
	return rp;
}

static char *
opattr(int fd, char *rp, Qid *qid, char *auth)
{
	char *trp;

	rp = rpcputl(rp, 1);
	trp = fattr3(fd, rp, qid, auth);
	if(trp == nil) {
		rp -= 4;
		rp = rpcputl(rp, 0);
		return rp;
	}
	return trp;
}

static ulong
getperm(int fd, uvlong meta, char *auth)
{
	char *host, *uid, *gid, *s;
	uvlong mode, x;
	ulong perm;
	int n, nuid, ngid;

	if(allow)
		return 0007;

	getmetaint(fd, meta, "mode", &mode);
	perm = mode & 0007;
	host = nil;
	switch(nhgetl(auth)) {
	case AUTH_UNIX:
		auth += 12;
		n = nhgetl(auth);
		host = emalloc9p(n + 1);
		auth += 4;
		memmove(host, auth, n);
		auth += round4(n);
		nuid = nhgetl(auth);
		auth += 4;
		ngid = nhgetl(auth);
		if(rootallow && nhgetl(auth) == 0) {
			perm = 0007;
			break;
		}
		if((uid = getmetastr(fd, meta, "uid")) != nil && (s = id2uname(host, nuid))) {
			if(strcmp(s, uid) == 0) {
				perm = (mode >> 6) & 0007;
				free(uid);
				break;
			}
		}
		else if(getmetaint(fd, meta, "nuid", &x) != MTnone && x == nuid) {
			perm = (mode >> 6) & 0007;
			free(uid);
			break;
		}
		if((gid = getmetastr(fd, meta, "gid")) != nil && (s = id2gname(host, ngid))) {
			if(strcmp(s, uid) == 0)
				perm = (mode >> 3) & 0007;
		}
		else if(getmetaint(fd, meta, "ngid", &x) != MTnone && x == ngid)
			perm = (mode >> 3) & 0007;
		free(uid);
		free(gid);
		break;
	case AUTH_NULL:
	case AUTH_SHORT:
	case AUTH_DES:
	default:
		break;
	}
	free(host);
	return perm;
}

static int
prewcc(int fd, uvlong qpath, uvlong *len, uvlong *mtime, uvlong *ctime)
{
	uvlong meta, x;

	meta = q2m(fd, qpath, 0);
	if(meta == 0)
		return -1;
	if(getmetaint(fd, meta, "length", &x) == MTnone)
		x = 0;
	*len = x;
	getmetaint(fd, meta, "mtime", &x);
	*mtime = x;
	if(getmetaint(fd, meta, "ctime", &x) == MTnone)
		*ctime = *mtime;
	else
		*ctime = x;
	return 0;
}

static char *
dowcc(int fd, char *rp, char *auth, Qid *qid, uvlong prelen, uvlong premtime, uvlong prectime)
{
	rp = rpcputl(rp, 1);
	rp = rpcputv(rp, prelen);
	rp = rpcputl(rp, premtime / 1000000000LL);
	rp = rpcputl(rp, premtime % 1000000000LL);
	rp = rpcputl(rp, prectime / 1000000000LL);
	rp = rpcputl(rp, prectime % 1000000000LL);
	rp = opattr(fd, rp, qid, auth);
	return rp;
}

static char *
mkpath(int fd, uvlong qpath, int len)
{
	char *str, *name, *p;
	uvlong meta, parent;
	int n;

	if(qpath == 1) {
		str = malloc(len + 2);
		strcpy(str, "/");
		return str;
	}
	meta = q2m(fd, qpath, 0);
	if(meta == 0)
		return nil;
	name = getmetastr(fd, meta, "name");
	n = strlen(name);
	if(getmetaint(fd, meta, "parent", &parent) == MTnone) {
		str = malloc(len + n + 2);
		strcpy(str, name);
		free(name);
		return str;
	}
	str = mkpath(fd, parent, len + n + 1);
	p = str + strlen(str);
	*p++ = '/';
	strcpy(p, name);
	free(name);
	return str;
}

static char *
dosattr(uvlong meta, char *p)
{
	uvlong now, x;
	ulong setit;

	now = nsec();
	setit = nhgetl(p);
	p += 4;
	if(setit) {
		getmetaint(-1, meta, "mode", &x);
		setmetaint(meta, "mode", nil, (nhgetl(p) & 0777) | (x & ~0777));
		setmetaint(meta, "unixmode", nil, nhgetl(p));
		p += 4;
	}
	setit = nhgetl(p);
	p += 4;
	if(setit) {
		setmetaint(meta, "nuid", nil, nhgetl(p));
		p += 4;
	}
	setit = nhgetl(p);
	p += 4;
	if(setit) {
		setmetaint(meta, "ngid", nil, nhgetl(p));
		p += 4;
	}
	setit = nhgetl(p);
	p += 4;
	if(setit) {
		setmetaint(meta, "length", nil, nhgetv(p));
		p += 8;
		setmetaint(meta, "mtime", nil, now);
	}
	setit = nhgetl(p);
	p += 4;
	if(setit == SET_TO_CLIENT_TIME) {
		setmetaint(meta, "atime", nil, nhgetl(p) * 1000000000LL + nhgetl(p + 4));
		p += 8;
	}
	setit = nhgetl(p);
	p += 4;
	if(setit == SET_TO_CLIENT_TIME) {
		setmetaint(meta, "mtime", nil, nhgetl(p) * 1000000000LL + nhgetl(p + 4));
		p += 8;
	}
	setmetaint(meta, "ctime", nil, now);
	return p;
}

static int
opensnap(uvlong qid)
{
	char *sname, *spath;
	uvlong meta;
	int fd;

	meta = q2m(-1, qid, 0);
	if(meta == 0)
		return -1;
	sname = getmetastr(-1, meta, "snap");
	if(sname == nil)
		return -1;
	spath = smprint("%s/%s", ddir, sname);
	free(sname);
	fd = open(spath, OREAD);
	free(spath);
	return fd;
}

static char *
nfsgetattr(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a;
	int fd;

	fd = -1;
	if(nhgetl(p) != sizeof(Qid)) {
		fd = opensnap(nhgetv(p + sizeof(Qid) + 4));
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3_OK);
	a = fattr3(fd, rp, &qid, auth);
	if(a == nil)
		hnputl(rp-4, NFS3ERR_BADHANDLE);
	else
		rp = a;
	if(fd != -1)
		close(fd);
	return rp;
}

static char *
nfssetattr(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp;
	uvlong meta, prelen, premeta, prectime;

	if(nhgetl(p) != sizeof(Qid)) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	if(prewcc(-1, qid.path, &prelen, &premeta, &prectime) < 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	meta = q2m(-1, qid.path, 0);
	dosattr(meta, p);
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3_OK);
	rp = dowcc(-1, rp, auth, &qid, prelen, premeta, prectime);
	return rp;
}

static char *
nfslookup(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid, qid2;
	char *rp, *name, *path, *sname, *spath;
	uvlong meta, qp, x, sqid;
	ulong perms;
	int n, m, fd,pfd;

	pfd = fd = -1;
	sqid = 0;
	if(nhgetl(p) != round4(sizeof(Qid))) {
		sqid = nhgetv(p + round4(sizeof(Qid)) + 4);
		pfd = fd = opensnap(sqid);
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	n = nhgetl(p);
	p += 4;
	name = malloc(n + 1);
	if(name == nil) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_SERVERFAULT);
		return rp;
	}
	memmove(name, p, n);
	name[n] = 0;
	meta = q2m(fd, qid.path, 0);
	if(debugnfs)
		fprint(2, "in nfslookup: qid=(%ulld,%uld,%ud) name=%s\n", qid.path, qid.vers, qid.type, name);
	perms = getperm(fd, meta, auth);
	if((perms & DMEXEC) == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		if(fd != -1)
			close(fd);
		return rp;
	}
	if(strcmp(name, ".") == 0) {
		/* don't need to do anything */
	}
	else if(strcmp(name, "..") == 0) {
		getmetaint(fd, meta, "parent", &qp);
		meta = q2m(fd, qp, 0);
	}
	else {
		path = mkpath(fd, qid.path, n + 1);
		m = strlen(path);
		path[m] = '/';
		strcpy(path + m + 1, name);
		x = p2q(fd, path, 0);
		meta = q2m(fd, x, 0);
		if(meta == 0) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_NOENT);
			rp = rpcputl(rp, 0);
			return rp;
		}
		free(path);
		sname = getmetastr(fd, meta, "snap");
		if(sname) {
			spath = smprint("%s/%s", ddir, sname);
			free(sname);
			fd = open(spath, OREAD);
			free(spath);
			sqid = x;
			meta = q2m(fd, p2q(fd, "/", 0), 0);
		}
	}
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3_OK);
	getmetaint(fd, meta, "qpath", &x);
	qid2.path = x;
	getmetaint(fd, meta, "qvers", &x);
	qid2.vers = x;
	getmetaint(fd, meta, "qtype", &x);
	qid2.type = x;
	if(fd == -1)
		m = round4(sizeof(Qid));
	else
		m = round4(sizeof(Qid)) + sizeof(uvlong);
	rp = rpcputl(rp, m);
	memmove(rp, &qid2, sizeof(Qid));
	rp += round4(sizeof(Qid));
	if(fd != -1)
		rp = rpcputv(rp, sqid);
	rp = opattr(fd, rp, &qid2, auth);
	rp = opattr(pfd, rp, &qid, auth);
	free(name);
	if(fd != -1)
		close(fd);
	if(pfd != -1 && pfd != fd)
		close(pfd);
	return rp;
}

static char *
nfsaccess(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a;
	uvlong meta;
	ulong reqacc, rspacc, perms;
	int fd;

	fd = -1;
	if(nhgetl(p) != round4(sizeof(Qid))) {
		fd = opensnap(nhgetv(p + round4(sizeof(Qid)) + 4));
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	reqacc = nhgetl(p);
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	meta = q2m(fd, qid.path, 0);
	if(meta == 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		if(fd != -1)
			close(fd);
		return rp;
	}
	perms = getperm(fd, meta, auth);
	rspacc = 0;
	if(perms & DMREAD)
		rspacc |= ACCESS3_READ;
	if(perms & DMWRITE)
		rspacc |= ACCESS3_MODIFY | ACCESS3_EXTEND;
	if(perms & DMEXEC)
		rspacc |= ACCESS3_LOOKUP | ACCESS3_EXECUTE;
	rspacc &= reqacc;
	rp = rpcputl(rp, NFS3_OK);
	a = fattr3(fd, rp + 4, &qid, auth);
	if(a == nil) {
		hnputl(rp-4, NFS3ERR_BADHANDLE);
		rpcputl(rp, 0);
	}
	else {
		rpcputl(rp, 1);
		rp = a;
		rp = rpcputl(rp, rspacc);
	}
	if(fd != -1)
		close(fd);
	return rp;
}

static char *
nfsreadlink(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *pp;
	uvlong meta;
	int n, fd;

	fd = -1;
	if(nhgetl(p) != round4(sizeof(Qid))) {
		fd = opensnap(nhgetv(p + round4(sizeof(Qid)) + 4));
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	meta = q2m(fd, qid.path, 0);
	if(meta == 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	if((pp = getmetastr(fd, meta, "symlink")) == nil) {
		rp = rpcputl(rp, NFS3ERR_INVAL);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	n = strlen(pp);
	rp = rpcputl(rp, NFS3_OK);
	rp = opattr(fd, rp, &qid, auth);
	rp = rpcputl(rp, n);
	memmove(rp, pp, n);
	rp += round4(n);
	free(pp);
	if(fd != -1)
		close(fd);
	return rp;
}

static char *
nfsread(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a;
	uvlong offset, meta, len;
	ulong perms;
	long count1, count2;
	int fd;

	fd = -1;
	if(nhgetl(p) != round4(sizeof(Qid))) {
		fd = opensnap(nhgetv(p + round4(sizeof(Qid)) + 4));
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	offset = nhgetv(p);
	p += 8;
	count1 = nhgetl(p);
	if(count1 > 32768)
		count1 = 32768;
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	a = rp + 104;
	meta = q2m(fd, qid.path, 0);
	if(meta == 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	perms = getperm(fd, meta, auth);
	if((perms & DMREAD) == 0) {
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	if(getmetaint(fd, meta, "length", &len) == MTnone)
		len = 0;
	count2 = θpread(fd, qid.path, a, count1, offset);
	a = fattr3(fd, rp + 8, &qid, auth);
	if(a == nil) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	hnputl(rp + 4, 1);
	if(count2 < 0) {
		hnputl(rp, NFS3ERR_IO);
		rp = a;
		if(fd != -1)
			close(fd);
		return rp;
	}
	hnputl(rp, NFS3_OK);
	rp = a;
	rp = rpcputl(rp, count2);
	if(offset + count2 >= len)
		rp = rpcputl(rp, 1);
	else
		rp = rpcputl(rp, 0);
	rp = rpcputl(rp, count2);
	rp += round4(count2);
	if(fd != -1)
		close(fd);
	return rp;
}

static char *
nfswrite(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp;
	uvlong offset, meta, prelen, premtime, prectime;
	ulong perms;
	long count1, count2, stable;

	if(nhgetl(p) != round4(sizeof(Qid))) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		return rp;
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	offset = nhgetv(p);
	p += 8;
	count1 = nhgetl(p);
	p += 4;
	stable = nhgetl(p);
	p += 8;		/* also skip the count at the beginning of the opaque data */
	meta = q2m(-1, qid.path, 0);
	if(meta == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		return rp;
	}
	perms = getperm(-1, meta, auth);
	if((perms & DMWRITE) == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		return rp;
	}
	if(prewcc(-1, qid.path, &prelen, &premtime, &prectime) < 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	count2 = θpwrite(qid.path, p, count1, offset, 1);
	if(stable != UNSTABLE) {
		resetmeta();
		csync();
	}
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	if(count2 < 0) {
		rp = rpcputl(rp, NFS3ERR_IO);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	rp = rpcputl(rp, NFS3_OK);
	rp = dowcc(-1, rp, auth, &qid, prelen, premtime, prectime);
	rp = rpcputl(rp, count2);
	if(stable == UNSTABLE)
		rp = rpcputl(rp, UNSTABLE);
	else
		rp = rpcputl(rp, FILE_SYNC);
	rp = rpcputv(rp, starttime);
	return rp;
}

static char *
mkfile(char *buf, char *p, ulong xid, char *auth, char *verf, int ilk)
{
	Qid qid, nqid;
	char *name, *path, *rp;
	uvlong meta, pmeta, dirblk, now, x;
	uvlong prelen, premeta, prectime;
	ulong perms;
	int n, m, how, nodetype;

	if(nhgetl(p) != round4(sizeof(Qid))) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		return rp;
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	n = nhgetl(p);
	p += 4;
	name = malloc(n + 1);
	if(name == nil) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_SERVERFAULT);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	memmove(name, p, n);
	name[n] = 0;
	p += round4(n);
	if(ilk == NF3REG) {
		how = nhgetl(p);
		p += 4;
	}
	else
		how = GUARDED;
	if(debugnfs)
		fprint(2, "in nfscreate: qid=(%ulld,%uld,%ud) name=%s\n", qid.path, qid.vers, qid.type, name);
	if((qid.type & QTDIR) == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_NOTDIR);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_EXIST);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
if(how == EXCLUSIVE) {
rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
rp = rpcputl(rp, NFS3ERR_NOTSUPP);
rp = rpcputl(rp, 0);
rp = rpcputl(rp, 0);
return rp;
}
	pmeta = q2m(-1, qid.path, 0);
	perms = getperm(-1, pmeta, auth);
	if((perms & DMWRITE) == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	path = mkpath(-1, qid.path, n + 1);
	m = strlen(path);
	path[m] = '/';
	strcpy(path + m + 1, name);
	nqid.path = p2q(-1, path, 1);
	switch(how) {
	case UNCHECKED:
		break;
	case GUARDED:
		meta = q2m(-1, nqid.path, 0);
		if(meta != 0) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_EXIST);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	meta = q2m(-1, nqid.path, 1);
	free(path);
	nqid.vers = 0;
	if(ilk == NF3DIR)
		nqid.type = QTDIR;
	else
		nqid.type = QTFILE;
	prewcc(-1, qid.path, &prelen, &premeta, &prectime);
	setmetastr(meta, "name", nil, name, 0);
	setmetaint(meta, "parent", nil, qid.path);
	setmetaint(meta, "qpath", nil, nqid.path);
	setmetaint(meta, "qvers", nil, nqid.vers);
	setmetaint(meta, "qtype", nil, nqid.type);
	getmetaint(-1, pmeta, "mode", &x);
	if(ilk == NF3DIR)
		setmetaint(meta, "mode", nil, x & 0777 | DMDIR);
	else
		setmetaint(meta, "mode", nil, x & 0777);
	now = nsec();
	setmetaint(pmeta, "mtime", nil, now);
	getmetaint(-1, pmeta, "child", &x);
	setmetaint(meta, "sib", nil, x);
	setmetaint(pmeta, "child", nil, nqid.path);
	nodetype = 0;
	switch(ilk) {
	case NF3DIR:
		setmetaint(meta, "child", nil, 0);
		break;
	case NF3REG:
		dirblk = allocblock();
		cbclean(dirblk);
		cbwrite(dirblk);
		brelease(dirblk);
		setmetaint(meta, "index", nil, dirblk);
		break;
	case NF3CHR:
		nodetype = nhgetl(p);
		p += 4;
		setmetaint(meta, "nodetype", nil, nodetype);
		break;
	}
	setqhash(nqid.path, meta);
	p = dosattr(meta, p);
	if(ilk == NF3LNK) {
		n = nhgetl(p);
		p += 4;
		path = malloc(n + 1);
		memmove(path, p, n);
		path[n] = 0;
		setmetastr(meta, "symlink", nil, path, 0);
		free(path);
	}
	else if(ilk == NF3CHR) {
		if(nodetype == NF3CHR || nodetype == NF3BLK) {
			setmetaint(meta, "majordev", nil, nhgetl(p));
			p += 4;
			setmetaint(meta, "minordev", nil, nhgetl(p));
		}
	}
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3_OK);
	rp = rpcputl(rp, 1);
	rp = rpcputl(rp, round4(sizeof(Qid)));
	memmove(rp, &nqid, sizeof(Qid));
	rp += round4(sizeof(Qid));
	rp = opattr(-1, rp, &nqid, auth);
	rp = dowcc(-1, rp, auth, &qid, prelen, premeta, prectime);
	savesuper();
	return rp;
}

static char *
nfscreate(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	return mkfile(buf, p, xid, auth, verf, NF3REG);
}

static char *
nfsmkdir(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	return mkfile(buf, p, xid, auth, verf, NF3DIR);
}

static char *
nfssymlink(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	return mkfile(buf, p, xid, auth, verf, NF3LNK);
}

static char *
nfsmknod(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	return mkfile(buf, p, xid, auth, verf, NF3CHR);		/* sort out the exact node type in mkfile */
}

static char *
nfsremove(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *name, *path;
	uvlong meta, qpath, pmeta, x;
	uvlong prelen, premtime, prectime;
	ulong perms;
	int n;

	if(nhgetl(p) != round4(sizeof(Qid))) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		return rp;
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	if((qid.type & QTDIR) == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_NOTDIR);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	pmeta = q2m(-1, qid.path, 0);
	if(pmeta == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	perms = getperm(-1, pmeta, auth);
	if((perms & DMWRITE) == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	if(prewcc(-1, qid.path, &prelen, &premtime, &prectime) < 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	n = nhgetl(p);
	p += 4;
	name = malloc(n + 1);
	memmove(name, p, n);
	name[n] = 0;
	path = mkpath(-1, qid.path, n + 1);
	n = strlen(path);
	path[n] = '/';
	strcpy(path + n + 1, name);
	qpath = p2q(-1, path, 0);
	meta = q2m(-1, qpath, 0);
	if(meta == 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_NOENT);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		free(name);
		free(path);
		return rp;
	}
	if(getmetaint(-1, meta, "child", &x) != MTnone && x != 0) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_NOTEMPTY);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		free(name);
		free(path);
		return rp;
	}
	rmq(qpath, meta);
	freedata(meta);
	rmdlist(meta, qpath);
	freeblock(meta);
	rmp(path);
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3_OK);
	rp = dowcc(-1, rp, auth, &qid, prelen, premtime, prectime);
	free(name);
	free(path);
	return rp;
}

static char *
nfsfsstat(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a;

	qid = *((Qid *)(p + 4));
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3_OK);
	a = fattr3(-1, rp + 4, &qid, auth);
	if(a == nil) {
		hnputl(rp - 4, NFS3ERR_BADHANDLE);
		hnputl(rp, 0);
		return rp;
	}
	hnputl(rp, 1);
	rp = a;
	rp = rpcputv(rp, super.nblk * BlkSize);	/* tbytes */
	rp = rpcputv(rp, super.nfree * BlkSize);	/* fbytes */
	rp = rpcputv(rp, super.nfree * BlkSize);	/* abytes */
	rp = rpcputv(rp, super.nht);	/* tfiles */
	rp = rpcputv(rp, super.nht);	/* ffiles */
	rp = rpcputv(rp, super.nht);	/* afiles */
	rp = rpcputl(rp, 0);	/* invarsec */
	return rp;
}

static char *
nfsfsinfo(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a;

	qid = *((Qid *)(p + 4));
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	hnputl(rp, NFS3_OK);
	rp += 4;
	a = fattr3(-1, rp + 4, &qid, auth);
	if(a == nil) {
		hnputl(rp - 4, NFS3ERR_BADHANDLE);
		hnputl(rp, 0);
		return rp;
	}
	hnputl(rp, 1);
	rp = a;
	rp = rpcputl(rp, 32768);	/* rtmax */
	rp = rpcputl(rp, 32768);	/* rtpref */
	rp = rpcputl(rp, 1);	/* rtmult */
	rp = rpcputl(rp, 32768);	/* wtmax */
	rp = rpcputl(rp, 32768);	/* wtpref */
	rp = rpcputl(rp, 1);	/* wtmult */
	rp = rpcputl(rp, 8192);	/* dtpref */
	rp = rpcputv(rp, 1LL << 55);	/* maxfilesize */
	rp = rpcputl(rp, 0);	/* time_delta */
	rp = rpcputl(rp, 1);
	rp = rpcputl(rp, FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME);	/* properties */
	return rp;
}

static char *
nfspathconf(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a;

	qid = *((Qid *)(p + 4));
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	hnputl(rp, NFS3_OK);
	rp += 4;
	a = fattr3(-1, rp + 4, &qid, auth);
	if(a == nil) {
		hnputl(rp - 4, NFS3ERR_BADHANDLE);
		return rp;
	}
	hnputl(rp, 1);
	rp = a;
	rp = rpcputl(rp, 1);
	rp = rpcputl(rp, MNTNAMELEN);
	rp = rpcputl(rp, 1);
	rp = rpcputl(rp, 1);
	rp = rpcputl(rp, 0);
	rp = rpcputl(rp, 1);
	return rp;
}

static char *
nfsrename(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid fqid, tqid;
	char *fname, *tname, *fpath, *tpath, *rp;
	uvlong fdmeta, fmeta, tmeta, qpath, now, x;
	uvlong fprelen, fpremtime, fprectime, tprelen, tpremtime, tprectime;
	ulong perms;
	int fn, tn, n;

	if(nhgetl(p) != round4(sizeof(Qid))) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		return rp;
	}
	fqid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	fn = nhgetl(p);
	p += 4;
	fname = malloc(fn + 1);
	memmove(fname, p, fn);
	fname[fn] = 0;
	p += round4(fn);
	fpath = mkpath(-1, fqid.path, fn + 1);
	n = strlen(fpath);
	fpath[n] = '/';
	strcpy(fpath + n + 1, fname);
	tqid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	tn = nhgetl(p);
	p += 4;
	tname = malloc(tn + 1);
	memmove(tname, p, tn);
	tname[tn] = 0;
	tpath = mkpath(-1, tqid.path, tn + 1);
	n = strlen(tpath);
	tpath[n] = '/';
	strcpy(tpath + n + 1, tname);
	prewcc(-1, fqid.path, &fprelen, &fpremtime, &fprectime);
	prewcc(-1, tqid.path, &tprelen, &tpremtime, &tprectime);
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	fdmeta = q2m(-1, fqid.path, 0);
	if(fdmeta == 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		goto done;
	}
	perms = getperm(-1, fdmeta, auth);
	if((perms & DMWRITE) == 0) {
		rp = rpcputl(rp, NFS3ERR_ACCES);
		goto done;
	}
	qpath = p2q(-1, fpath, 0);
	if(qpath == 0) {
		rp = rpcputl(rp, NFS3ERR_NOENT);
		goto done;
	}
	if((tqid.type & QTDIR) == 0) {
		rp = rpcputl(rp, NFS3ERR_NOTDIR);
		goto done;
	}
	now = nsec();
	fmeta = q2m(-1, qpath, 0);
	if(fqid.path != tqid.path) {
		tmeta = q2m(-1, tqid.path, 0);
		if(tmeta == 0) {
			rp = rpcputl(rp, NFS3ERR_NOENT);
			goto done;
		}
		perms = getperm(-1, tmeta, auth);
		if((perms & DMWRITE) == 0) {
			rp = rpcputl(rp, NFS3ERR_ACCES);
			goto done;
		}
		rmdlist(fmeta, qpath);
		setmetaint(fmeta, "parent", nil, tqid.path);
		getmetaint(-1, tmeta, "child", &x);
		setmetaint(fmeta, "sib", nil, x);
		setmetaint(tmeta, "child", nil, qpath);
		setmetaint(tmeta, "mtime", nil, now);
		setmetaint(tmeta, "atime", nil, now);
	}
	setmetastr(fmeta, "name", nil, tname, 0);
	rehashpath(qpath, fpath, tpath);
	setmetaint(fmeta, "ctime", nil, now);
	setmetaint(fdmeta, "mtime", nil, now);
	setmetaint(fdmeta, "atime", nil, now);
	rp = rpcputl(rp, NFS3_OK);
done:
	rp = dowcc(-1, rp, auth, &fqid, fprelen, fpremtime, fprectime);
	rp = dowcc(-1, rp, auth, &tqid, tprelen, tpremtime, tprectime);
	free(fname);
	free(fpath);
	free(tname);
	free(tpath);
	return rp;
}

static char *
nfsreaddir(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp, *a, *xs;
	uvlong cookie, meta;
	ulong perms;
	long count1, count2;
	int n, fd;

	fd = -1;
	if(nhgetl(p) != round4(sizeof(Qid))) {
		fd = opensnap(nhgetv(p + round4(sizeof(Qid)) + 4));
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	cookie = nhgetv(p);
	p += 16;
	count1 = nhgetl(p);
	if(count1 > 8192)
		count1 = 8192;
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	meta = q2m(fd, qid.path, 0);
	if(meta == 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	perms = getperm(fd, meta, auth);
	if((perms & DMREAD) == 0) {
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	if(cookie == 0) {
		meta = q2m(fd, qid.path, 0);
		if(meta == 0) {
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			return rp;
		}
		getmetaint(fd, meta, "child", &cookie);
	}
	a = rp + 92;
	a = rpcputv(a, 0);
	count2 = a - rp;
	while(cookie != 0) {
		meta = q2m(fd, cookie, 0);
		if(meta == 0) {
			rp = rpcputl(rp, NFS3ERR_IO);
			rp = rpcputl(rp, 0);
			if(fd != -1)
				close(fd);
			return rp;
		}
		xs = getmetastr(fd, meta, "name");
		n = strlen(xs);
		if(count2 + round4(n) + 24 + 8 > count1) {
			free(xs);
			break;
		}
		a = rpcputl(a, 1);
		a = rpcputv(a, cookie);
		a = rpcputl(a, n);
		memmove(a, xs, n);
		a += round4(n);
		free(xs);
		a = rpcputv(a, cookie);
		getmetaint(fd, meta, "sib", &cookie);
		count2 += round4(n) + 24;
	}
	a = fattr3(fd, rp + 8, &qid, auth);
	if(a == nil) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	hnputl(rp, NFS3_OK);
	hnputl(rp + 4, 1);
	rp += count2;
	rp = rpcputl(rp, 0);
	if(cookie == 0)
		rp = rpcputl(rp, 1);
	else
		rp = rpcputl(rp, 0);
	if(fd != -1)
		close(fd);
	return rp;
}

static char *
nfsreaddirplus(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid, qid2;
	char *rp, *a, *xs;
	uvlong cookie, meta, x, sqid;
	ulong perms;
	long count1, count2;
	int n, m, fd;

	fd = -1;
	sqid = 0;
	if(nhgetl(p) != round4(sizeof(Qid))) {
		sqid = nhgetv(p + round4(sizeof(Qid)) + 4);
		fd = opensnap(sqid);
		if(fd == -1) {
			rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			rp = rpcputl(rp, 0);
			return rp;
		}
	}
	qid = *((Qid *)(p + 4));
	p += nhgetl(p) + 4;
	cookie = nhgetv(p);
	p += 16;
	p += 4;		/* use maxcount instead of dircount */
	count1 = nhgetl(p);
	if(count1 > 8192)
		count1 = 8192;
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	meta = q2m(fd, qid.path, 0);
	if(meta == 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	perms = getperm(fd, meta, auth);
	if((perms & DMREAD) == 0) {
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	if(cookie == 0) {
		meta = q2m(fd, qid.path, 0);
		if(meta == 0) {
			rp = rpcputl(rp, NFS3ERR_BADHANDLE);
			rp = rpcputl(rp, 0);
			if(fd != -1)
				close(fd);
			return rp;
		}
		getmetaint(fd, meta, "child", &cookie);
	}
	a = rp + 92;
	a = rpcputv(a, 0);		/* cookieverf */
	count2 = a - rp;
	while(cookie != 0) {
		meta = q2m(fd, cookie, 0);
		if(meta == 0) {
			rp = rpcputl(rp, NFS3ERR_IO);
			rp = rpcputl(rp, 0);
			if(fd != -1)
				close(fd);
			return rp;
		}
		xs = getmetastr(fd, meta, "name");
		n = strlen(xs);
		getmetaint(fd, meta, "qpath", &x);
		qid2.path = x;
		getmetaint(fd, meta, "qvers", &x);
		qid2.vers = x;
		getmetaint(fd, meta, "qtype", &x);
		qid2.type = x;
		if(fd == -1)
			m = round4(sizeof(Qid));
		else
			m = round4(sizeof(Qid)) + sizeof(uvlong);
		if(count2 + 4 + 8 + 4 + round4(n) + 8 + 88 + 4 + 4 + m + 8 > count1) {
			free(xs);
			break;
		}
		a = rpcputl(a, 1);
		a = rpcputv(a, cookie);		/* fileid */
		a = rpcputl(a, n);			/* name */
		memmove(a, xs, n);
		a += round4(n);
		free(xs);
		a = rpcputv(a, cookie);		/* cookie */
		a = opattr(fd, a, &qid2, auth);	/* name_attributes */
		a = rpcputl(a, 1);			/* name_handle */
		a = rpcputl(a, m);
		memmove(a, &qid2, sizeof(Qid));
		a += round4(sizeof(Qid));
		if(fd != -1)
			rp = rpcputv(rp, sqid);
		getmetaint(fd, meta, "sib", &cookie);
		count2 += 4 + 8 + 4 + round4(n) + 8 + 88 + 4 + 4+ m;
	}
	a = fattr3(fd, rp + 8, &qid, auth);
	if(a == nil) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		if(fd != -1)
			close(fd);
		return rp;
	}
	hnputl(rp, NFS3_OK);
	hnputl(rp + 4, 1);
	rp += count2;
	rp = rpcputl(rp, 0);			/* no more entries */
	if(cookie == 0)				/* eof? */
		rp = rpcputl(rp, 1);
	else
		rp = rpcputl(rp, 0);
	if(fd != -1)
		close(fd);
	return rp;
}

static char *
nfscommit(char *buf, char *p, ulong xid, char *auth, char *verf)
{
	Qid qid;
	char *rp;
	uvlong prelen, premtime, prectime;

	if(*((long *)p) != round4(sizeof(Qid))) {
		rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
		rp = rpcputl(rp, NFS3ERR_ACCES);
		rp = rpcputl(rp, 0);
		return rp;
	}
	qid = *((Qid *)(p + 4));
	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	if(prewcc(-1, qid.path, &prelen, &premtime, &prectime) < 0) {
		rp = rpcputl(rp, NFS3ERR_BADHANDLE);
		rp = rpcputl(rp, 0);
		rp = rpcputl(rp, 0);
		return rp;
	}
	resetmeta();
	csync();
	rp = rpcputl(rp, NFS3_OK);
	rp = dowcc(-1, rp, auth, &qid, prelen, premtime, prectime);
	rp = rpcputv(rp, starttime);
	return rp;
}

static char *
nfsunsupp(char *buf, ulong xid, char *verf)
{
	char *rp;

	rp = initreply(buf, xid, MSG_ACCEPTED, verf, SUCCESS);
	rp = rpcputl(rp, NFS3ERR_NOTSUPP);
	rp = rpcputl(rp, 0);
	return rp;
}

static int
nfsdis(char *buf, char *p, ulong xid, char *auth, char *verf, ulong proc)
{
	char *rp;

	switch(proc) {
	case NFSPROC3_NULL:
		rp = rpcnull(buf, xid, verf);
		break;
	case NFSPROC3_GETATTR:
		rp = nfsgetattr(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_SETATTR:
		rp = nfssetattr(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_LOOKUP:
		rp = nfslookup(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_ACCESS:
		rp = nfsaccess(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_READLINK:
		rp = nfsreadlink(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_READ:
		rp = nfsread(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_WRITE:
		rp = nfswrite(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_CREATE:
		rp = nfscreate(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_MKDIR:
		rp = nfsmkdir(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_SYMLINK:
		rp = nfssymlink(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_MKNOD:
		rp = nfsmknod(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_REMOVE:
	case NFSPROC3_RMDIR:
		rp = nfsremove(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_RENAME:
		rp = nfsrename(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_LINK:
		rp = nfsunsupp(buf, xid, verf);			/* $ */
		break;
	case NFSPROC3_READDIR:
		rp = nfsreaddir(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_READDIRPLUS:
		rp = nfsreaddirplus(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_FSSTAT:
		rp = nfsfsstat(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_FSINFO:
		rp = nfsfsinfo(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_PATHCONF:
		rp = nfspathconf(buf, p, xid, auth, verf);
		break;
	case NFSPROC3_COMMIT:
		rp = nfscommit(buf, p, xid, auth, verf);
		break;
	default:
		rp = initreply(buf, xid, MSG_DENIED, verf, PROC_UNAVAIL);
		break;
	}
	return rp - buf;
}

static void
tpstarter(void *)
{
	Rcb *r;
	int fd;

	while(recv(tpchan, &fd)) {
		for(r = rcbhd; r && r->inuse; r = r->next) ;
		if(r == nil) {
			r = emalloc9p(sizeof(Rcb));
			r->inuse = 1;
			r->io = ioproc();
			r->next = rcbhd;
			rcbhd = r;
		}
		r->inuse = 1;
		r->fd = fd;
		r->myprog = PMAP_PROG;
		r->minver = PMAP_VERS;
		r->maxver = PMAP_VERS;
		r->dispatch = pmapdis;
		threadcreate(tcprpcreader, r, 8192);
	}
	threadexits(nil);
}

static void
tportmapper(void *)
{
	char *s;
	int acfd, lcfd, fd;
	char adir[40], ldir[40];

	s = smprint("tcp!*!%d", PMAP_PORT);
	acfd = announce(s, adir);
	if(acfd < 0)
		fprint(2, "error in announce: %r\n");
	if(debugnfs)
		fprint(2, "announce in tcp port mapper got dir: %s:%r\n", adir);
	free(s);
	if(acfd < 0)
		threadexits(nil);
	while(1) {
		lcfd = listen(adir, ldir);
		if(lcfd < 0)
			fprint(2, "error in listen: %r\n");
		if(shutdown)
			threadexits(nil);
		if(debugnfs)
			fprint(2, "back from listen in tcp port mapper: ldir=%s\n", ldir);
		if(lcfd < 0) {
			close(acfd);
			threadexits(nil);
		}
		fd = accept(lcfd, ldir);
		close(lcfd);
		send(tpchan, &fd);
	}
}

static void
upstarter(void *)
{
	Rcb *r;
	int fd;

	while(recv(upchan, &fd)) {
		if(shutdown)
			break;
		for(r = rcbhd; r && r->inuse; r = r->next) ;
		if(r == nil) {
			r = emalloc9p(sizeof(Rcb));
			r->inuse = 1;
			r->io = ioproc();
			r->next = rcbhd;
			rcbhd = r;
		}
		r->inuse = 1;
		r->fd = fd;
		r->myprog = PMAP_PROG;
		r->minver = PMAP_VERS;
		r->maxver = PMAP_VERS;
		r->dispatch = pmapdis;
		threadcreate(udprpcreader, r, 8192);
	}
	threadexits(nil);
}

static void
uportmapper(void *)
{
	char *s;
	int acfd, lcfd, fd;
	char adir[40], ldir[40];

	s = smprint("udp!*!%d", PMAP_PORT);
	acfd = announce(s, adir);
	if(acfd < 0)
		fprint(2, "error in announce: %r\n");
	if(debugnfs)
		fprint(2, "announce in udp port mapper got dir: %s:%r\n", adir);
	free(s);
	if(acfd < 0)
		threadexits(nil);
	while(1) {
		lcfd = listen(adir, ldir);
		if(lcfd < 0)
			fprint(2, "error in listen: %r\n");
		if(shutdown)
			threadexits(nil);
		if(debugnfs)
			fprint(2, "back from listen in udp port mapper: ldir=%s\n", ldir);
		if(lcfd < 0) {
			close(acfd);
			threadexits(nil);
		}
		fd = accept(lcfd, ldir);
		close(lcfd);
		send(upchan, &fd);
	}
}

static void
mountstarter(void *)
{
	Rcb *r;
	int fd;

	while(recv(mchan, &fd)) {
		if(shutdown)
			break;
		for(r = rcbhd; r && r->inuse; r = r->next) ;
		if(r == nil) {
			r = emalloc9p(sizeof(Rcb));
			r->inuse = 1;
			r->io = ioproc();
			r->next = rcbhd;
			rcbhd = r;
		}
		r->inuse = 1;
		r->fd = fd;
		r->myprog = MNT_PROG;
		r->minver = MNT_MIN_VERS;
		r->maxver = MNT_MAX_VERS;
		r->dispatch = mntdis;
		threadcreate(tcprpcreader, r, 8192);
	}
	threadexits(nil);
}

static void
mountd(void *)
{
	char *s;
	int acfd, lcfd, fd;
	char adir[40], ldir[40];

	s = smprint("tcp!*!%d", MNT_PORT);
	acfd = announce(s, adir);
	free(s);
	if(acfd < 0)
		threadexits(nil);
	while(1) {
		lcfd = listen(adir, ldir);
		if(shutdown)
			threadexits(nil);
		if(debugnfs)
			fprint(2, "back from listen in mountd: ldir=%s\n", ldir);
		if(lcfd < 0) {
			close(acfd);
			threadexits(nil);
		}
		fd = accept(lcfd, ldir);
		close(lcfd);
		send(mchan, &fd);
	}
}

static void
nfsdstarter(void *)
{
	Rcb *r;
	int fd;

	while(recv(nchan, &fd)) {
		if(shutdown)
			break;
		for(r = rcbhd; r && r->inuse; r = r->next) ;
		if(r == nil) {
			r = emalloc9p(sizeof(Rcb));
			r->inuse = 1;
			r->io = ioproc();
			r->next = rcbhd;
			rcbhd = r;
		}
		r->inuse = 1;
		r->fd = fd;
		r->myprog = NFS_PROG;
		r->minver = NFS_VERS;
		r->maxver = NFS_VERS;
		r->dispatch = nfsdis;
		threadcreate(tcprpcreader, r, 8192);
	}
	threadexits(nil);
}

static void
nfsd(void *)
{
	char *s;
	int acfd, lcfd, fd;
	char adir[40], ldir[40];

	s = smprint("tcp!*!%d", NFS_PORT);
	acfd = announce(s, adir);
	free(s);
	if(acfd < 0)
		threadexits(nil);
	while(1) {
		lcfd = listen(adir, ldir);
		if(shutdown)
			threadexits(nil);
		if(debugnfs)
			fprint(2, "back from listen in nfsd: ldir=%s\n", ldir);
		if(lcfd < 0) {
			close(acfd);
			threadexits(nil);
		}
		fd = accept(lcfd, ldir);
		close(lcfd);
		send(nchan, &fd);
	}
}

static int
regport(void)
{
	char *buf, *p;
	int fd;
int n, i;

	/*
	 * On Plan 9, don't even bother trying to see if we have
	 * a local portmap running.
	 */
	if(access("/net/ipselftab", AREAD) == 0)
		return 0;
	/*
	 * Take a crack at using a locks instance of portmap/
	 * rpcbind.  If we succeed, we don't need to bother
	 * starting out build-in one.   If 
	 */
	fd = dial("udp!127.1!111", nil, nil, nil);
	if(fd < 0)
		return 0;
fprint(2, "Got portmap connection open\n");
	buf = malloc(1500);
	p = buf;
	p = rpcputl(p, 42);		/* xid */
	p = rpcputl(p, CALL);		/* mtype */
	p = rpcputl(p, 2);		/* rpcvers */
	p = rpcputl(p, PMAP_PROG);	/* prog */
	p = rpcputl(p, PMAP_VERS);	/* vers */
	p = rpcputl(p, PMAPPROC_SET);	/* proc */
	p = rpcputl(p, 0);		/* auth */
	p = rpcputl(p, 0);
	p = rpcputl(p, 0);		/* verf */
	p = rpcputl(p, 0);
	p = rpcputl(p, NFS_PROG);	/* prog */
	p = rpcputl(p, NFS_VERS);		/* vers */
	p = rpcputl(p, IPPROTO_TCP);	/* prot */
	p = rpcputl(p, NFS_PORT);	/* port */
	write(fd, buf, p - buf);
	n = read(fd, buf, 1500);
for(i = 0; i < n; ++i) fprint(2, "%02x ", buf[i]);
fprint(2, "\n");
	close(fd);
	fd = dial("udp!127.1!111", nil, nil, nil);
	if(fd < 0) {
		free(buf);
		return 0;
	}
	p = buf;
	p = rpcputl(p, 42);		/* xid */
	p = rpcputl(p, CALL);		/* mtype */
	p = rpcputl(p, 2);		/* rpcvers */
	p = rpcputl(p, PMAP_PROG);	/* prog */
	p = rpcputl(p, PMAP_VERS);	/* vers */
	p = rpcputl(p, PMAPPROC_SET);	/* proc */
	p = rpcputl(p, 0);		/* auth */
	p = rpcputl(p, 0);
	p = rpcputl(p, 0);		/* verf */
	p = rpcputl(p, 0);
	p = rpcputl(p, MNT_PROG);	/* prog */
	p = rpcputl(p, MNT_MAX_VERS);		/* vers */
	p = rpcputl(p, IPPROTO_TCP);	/* prot */
	p = rpcputl(p, MNT_PORT);	/* port */
	write(fd, buf, p - buf);
	n = read(fd, buf, 1500);
for(i = 0; i < n; ++i) fprint(2, "%02x ", buf[i]);
fprint(2, "\n");
	close(fd);
	free(buf);
	return 1;
}

void
initnfs(void)
{
	if(!regport()) {
		upchan = chancreate(sizeof(ulong), 1);
		threadcreate(upstarter, nil, 1024);
		umaptid = proccreate(uportmapper, nil, 8192);
		tpchan = chancreate(sizeof(ulong), 1);
		threadcreate(tpstarter, nil, 1024);
		tmaptid = proccreate(tportmapper, nil, 8192);
	}
	mchan = chancreate(sizeof(ulong), 1);
	threadcreate(mountstarter, nil, 1024);
	mounttid = proccreate(mountd, nil, 8192);
	nchan = chancreate(sizeof(ulong), 1);
	threadcreate(nfsdstarter, nil, 1024);
	nfstid = proccreate(nfsd, nil, 8192);
}

void
haltnfs(void)
{
	Rcb *r;

	if(upchan == nil)
		return;
/*
	if(upchan) {
		chanclose(upchan);
		chanclose(tpchan);
	}
	chanclose(mchan);
	chanclose(nchan);
*/
	for(r = rcbhd; r; r = r->next) {
		if(r->io) {
			iointerrupt(r->io);
			closeioproc(r->io);
		}
	}
/*
	if(upchan) {
		threadkill(umaptid);
		threadkill(tmaptid);
	}
	threadkill(mounttid);
	threadkill(nfstid);
*/
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.