#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/fcall.h>
#include <9pm/thread.h>
#include <9pm/ns.h>

enum
{
	DBG9p = (1<<0),
	DBGproc = (1<<1),
	DBGflush = (1<<2),

	excheck = 0
};

static int	exdebug = 0;

typedef	struct Fid	Fid;
typedef	struct Qidt	Qidt;
typedef	struct Export	Export;
typedef	struct Exq	Exq;

enum
{
	Nfidhash	= 32,	/* power of 2 */
	Nqidhash	= 32,	/* power of 2 */

	PATHHI		= 0x7fffffff,

	MAXRPC		= IOHDRSZ+8192,
};

struct Export
{
	struct {
		Lock l;
		int ref;
	} r;
	Exq*	work;
	QLock	fidlock;
	Fid*	fid[Nfidhash];
	Qidt*	qidt[Nqidhash];
	Qidt*	qidp[Nqidhash];
	Chan*	io;
	Chan*	root;
	Fgrp*	fgrp;
	Pgrp*	pgrp;
	int	async;
	int	version;
	char	*user;
	Queue	*q;
};

struct Fid
{
	Fid*	next;
	Fid**	last;
	Chan*	chan;
	int	fid;
	Qidt	*qidt;		/* claim to Qid */
	int	ref;		/* fcalls using the fid; locked by Export.Lock */
	int	attached;	/* fid attached or cloned but not clunked */
};

struct Qidt
{
	Qidt*	next;
	Qidt**	last;
	Qidt*	pnext;
	Qidt**	plast;
	int	ref;
	int	type;
	int	dev;
	ulong	path;		/* orig */
	ulong	uniqpath;	/* allocated */
};

struct Exq
{
	Lock	lk;
	int	nointr;
	int	noresponse;	/* don't respond to this one */
	Exq*	next;
	int	shut;		/* has been noted for shutdown */
	Export*	export;
	void*	slave;
	Fcall	rpc;
	uchar	*buf;
	int	buflen;
	Block	*b;
};

struct
{
	Lock	l;
	QLock	qwait;
	Rendez	rwait;
	Exq	*head;		/* work waiting for a slave */
	Exq	*tail;
}exq;

struct
{
	Lock	l;
	Exq*	free;
} exqalloc;

struct
{
	Lock	l;
	Fid*	free;
} fidalloc;

struct
{
	Lock	l;
	Qidt*	free;
	int	path;
} qidalloc;

static void	exshutdown(Export*);
static void	exflush(Export*, int, int);
static void	exslave(void*);
static void	exfree(Export*);
static void	exportproc(void*);
static int	exportsrv(Export*);

static char*	Exversion(Export*, Fcall*);
static char*	Exattach(Export*, Fcall*);
static char*	Exclunk(Export*, Fcall*);
static char*	Excreate(Export*, Fcall*);
static char*	Exopen(Export*, Fcall*);
static char*	Exread(Export*, Fcall*);
static char*	Exremove(Export*, Fcall*);
static char*	Exauth(Export*, Fcall*);
static char*	Exstat(Export*, Fcall*);
static char*	Exwalk(Export*, Fcall*);
static char*	Exwrite(Export*, Fcall*);
static char*	Exwstat(Export*, Fcall*);

static char	*(*fcalls[Tmax])(Export*, Fcall*);

static char	Enofid[]   = "no such fid";
static char	Eseekdir[] = "can't seek on a directory";
static char	Ereaddir[] = "unaligned read of a directory";
static char	Eversion[] = "Bad 9P2000 version";

static int	excloses;
static int	exopens;
static int	collisions;
static int	exqs;
static int	fids;
static int	qidts;
static int	slaves;

int
exportfs(int fd, int async)
{
	Chan *c;
	Pgrp *pg;
	Export *fs;
	Mhead *mh;

	if(waserror())
		return -1;

	c = fdtochan(up->fgrp, fd, ORDWR, 1, 1);
	poperror();
	c->flag |= CMSG;

	fs = malloc(sizeof(Export));
	fs->r.ref = 1;
	kstrdup(&fs->user, pm_conf.eve);

	fs->fgrp = dupfgrp(nil);
	pg = up->pgrp;
	fs->pgrp = pg;
	incref(&pg->ref);
	fs->root = up->slash;
	incref(&fs->root->ref);
	mh = nil;
	domount(&fs->root, &mh);
	fs->io = c;
	if(fs->io->iounit == 0)
		fs->io->iounit = MAXRPC;	/* reasonable default; may change in Exversion */
	fs->async = async;
	fs->q = qopen(10*MAXRPC, 0, nil, nil);
	if(async)
		nsaddproc("exportfs", exportproc, fs, 0);
	else
		return exportsrv(fs);

	return 0;
}

static void
exqfree(Exq *e)
{
	uchar *p;

	p = nil;
	lock(&exqalloc.l);
	e->next = exqalloc.free;
	exqalloc.free = e;
	if(e->buflen > 2*MAXRPC) {
		p = e->buf;
		e->buf = nil;
		e->buflen = 0;
	}
	unlock(&exqalloc.l);
	free(p);
}

static void
freefid(Fid *f)
{
	lock(&fidalloc.l);
	f->next = fidalloc.free;
	fidalloc.free = f;
	unlock(&fidalloc.l);
}

static void
freeqid(Qidt *q)
{
	lock(&qidalloc.l);
	q->next = qidalloc.free;
	qidalloc.free = q;
	unlock(&qidalloc.l);
}

static void
exportinit(void)
{
	lock(&exq.l);
	if(fcalls[Tauth] != nil) {
		unlock(&exq.l);
		return;
	}

	fmtinstall('F', fcallconv);
	fcalls[Tauth] = Exauth;
	fcalls[Tattach] = Exattach;
	fcalls[Twalk] = Exwalk;
	fcalls[Topen] = Exopen;
	fcalls[Tcreate] = Excreate;
	fcalls[Tread] = Exread;
	fcalls[Twrite] = Exwrite;
	fcalls[Tclunk] = Exclunk;
	fcalls[Tremove] = Exremove;
	fcalls[Tstat] = Exstat;
	fcalls[Tversion] = Exversion;
	fcalls[Twstat] = Exwstat;
	unlock(&exq.l);
}

static int
exrpcread(Export *fs, Exq *q)
{
	int i, t, len, hlen;
	Block *b, **l, *nb;

	q->rpc.type = 0;
	q->rpc.tag = 0;

	/* read at least length, type, and tag and pullup to a single block */
	while(qlen(fs->q) < BIT32SZ+BIT8SZ+BIT16SZ){
		b = devtab[fs->io->type]->bread(fs->io, 2*MAXRPC, 0);
		if(b == nil)
			return -1;
		if(BLEN(b) == 0) {	/* EOF */
			freeb(b);
			return -1;
		}
		qaddlist(fs->q, b);
	}
	nb = pullupqueue(fs->q, BIT32SZ+BIT8SZ+BIT16SZ);
	len = GBIT32(nb->rp);

	/* read in the rest of the message */
	while(qlen(fs->q) < len){
		b = devtab[fs->io->type]->bread(fs->io, 2*MAXRPC, 0);
		if(b == nil)
			return -1;
		qaddlist(fs->q, b);
	}

	/* pullup the header (i.e. everything except data) */
	t = nb->rp[BIT32SZ];
	switch(t){
	case Twrite:
		hlen = BIT32SZ+BIT8SZ+BIT16SZ+BIT32SZ+BIT64SZ+BIT32SZ;
		break;
	default:
		hlen = len;
		break;
	}
	nb = pullupqueue(fs->q, hlen);

	if(convM2S(nb->rp, len, &q->rpc) <= 0){
		/* bad message, dump it */
		print("exrpcread: convM2S failed\n");
		qdiscard(fs->q, len);
		return -1;
	}

	/* hang the data off of the Exq struct */
	l = &q->b;
	*l = nil;
	do {
		b = qremove(fs->q);
		if(hlen > 0){
			b->rp += hlen;
			len -= hlen;
			hlen = 0;
		}
		i = BLEN(b);
		if(i <= len){
			len -= i;
			*l = b;
			l = &(b->next);
		} else {
			/* split block and put unused bit back */
			nb = allocb(i-len);
			memmove(nb->wp, b->rp+len, i-len);
			b->wp = b->rp+len;
			nb->wp += i-len;
			qputback(fs->q, nb);
			*l = b;
			return 0;
		}
	}while(len > 0);

	return 0;
}

static void
exportproc(void *a)
{
	Export *fs = a;

	if(exportsrv(fs) < 0)
		iprint("exportsrv error: %r\n");
	pexit("mount shut down", 0);
}

static int
exportsrv(Export *fs)
{
	Exq *q;
	int n;

	exportinit();
	exopens++;

	for(;;){
		lock(&exqalloc.l);
		q = exqalloc.free;
		if(q != nil)
			exqalloc.free = q->next;
		unlock(&exqalloc.l);

		if(q == nil) {
			exqs++;
			q = malloc(sizeof(Exq));
			if(q == nil) {
				werrstr(Enomem);
				goto bad;
			}
		}
		if(q->buflen < fs->io->iounit) {
			q->buflen = fs->io->iounit;
			q->buf = realloc(q->buf, q->buflen);
		}

		if(waserror())
			goto bad;
		n = exrpcread(fs, q);
		poperror();
		if(n < 0)
			goto bad;

		if(exdebug&DBG9p)
			iprint("export %d <- %F\n", getpid(), &q->rpc);

		switch(q->rpc.type){
		case Tflush:
			exflush(fs, q->rpc.tag, q->rpc.oldtag);
			exqfree(q);
			continue;
		case Twrite:
			q->b = bl2mem(q->buf, q->b, q->rpc.count);
			q->rpc.data = (char*)q->buf;
			break;
		}

		q->export = fs;
		lock(&fs->r.l);
		fs->r.ref++;
		unlock(&fs->r.l);

		lock(&exq.l);
		if(exq.head == nil)
			exq.head = q;
		else
			exq.tail->next = q;
		q->next = nil;
		exq.tail = q;
		unlock(&exq.l);
		if(exq.qwait.head == nil) {
			n = nsaddproc("exportfs", exslave, nil, 0);
			if(exdebug&DBGproc)
				iprint("launch export (pid=%ux)\n", n);
		}
		rendwakeup(&exq.rwait);
	}
bad:
	if(q != nil)
		exqfree(q);
	exshutdown(fs);
	exfree(fs);

	if(excheck) {
		iprint("exportfs:\n");
		iprint("%d opens, %d closes\n", exopens, excloses);
		iprint("%d slaves, %d qid collisions\n", slaves, collisions);
		iprint("%d e %d f %d q\n", exqs, fids, qidts);
	}
	return -1;
}

static void
exflush(Export *fs, int flushtag, int tag)
{
	Exq *q, **last;
	int n;
	Fcall fc;
	char buf[MAXRPC];

	/* hasn't been started? */
	lock(&exq.l);
	last = &exq.head;
	for(q = exq.head; q != nil; q = q->next){
		if(q->export == fs && q->rpc.tag == tag){
			*last = q->next;
			unlock(&exq.l);
			exfree(fs);
			exqfree(q);
			goto Respond;
		}
		last = &q->next;
	}
	unlock(&exq.l);

	/* in progress? */
	lock(&fs->r.l);
	for(q = fs->work; q != nil; q = q->next){
		if(q->rpc.tag == tag && !q->noresponse){
			lock(&q->lk);
			q->noresponse = 1;
			if(!q->nointr)
				swiproc(q->slave, 0);
			unlock(&q->lk);
			unlock(&fs->r.l);
			goto Respond;
			return;
		}
	}
	unlock(&fs->r.l);

	if(exdebug&DBGflush)
		iprint("exflush: did not find rpc: %d\n", tag);

Respond:
	fc.type = Rflush;
	fc.tag = flushtag;
	n = convS2M(&fc, (uchar*)buf, sizeof buf);
	if(exdebug&DBGflush)
		iprint("exflush -> %F\n", &fc);
	if(!waserror()){
		devtab[fs->io->type]->_write(fs->io, buf, n, 0);
		poperror();
	}
}

static void
exshutdown(Export *fs)
{
	Exq *q, **last;

	lock(&exq.l);
	last = &exq.head;
	for(q = exq.head; q != nil; q = *last){
		if(q->export == fs){
			*last = q->next;
			exfree(fs);
			exqfree(q);
			continue;
		}
		last = &q->next;
	}
	unlock(&exq.l);

rescan:
	lock(&fs->r.l);
	for(q = fs->work; q != nil; q = q->next) {
		if(!q->shut) {
			q->shut = 1;
			unlock(&fs->r.l);
			swiproc(q->slave, 0);
			goto rescan;
		}
	}
	unlock(&fs->r.l);
}

static void
decrqid(Qidt *q)
{
	if(q == nil)
		return;

	q->ref--;
	if(q->ref > 0)
		return;

	*q->last = q->next;
	if(q->next != nil)
		q->next->last = q->last;
	*q->plast = q->pnext;
	if(q->pnext != nil)
		q->pnext->plast = q->plast;
	freeqid(q);
}

static void
exfreefids(Export *fs)
{
	Fid *f, *n;
	int i;

	for(i = 0; i < Nfidhash; i++){
		for(f = fs->fid[i]; f != nil; f = n){
			n = f->next;
			if(excheck && f->ref != 0)
				print("exfree ref\n");
			if(f->chan != nil)
				cclose(f->chan);
			freefid(f);
		}
	}
}

static void
exfreeqids(Export *fs)
{
	Qidt *q, *n;
	int i;

	for(i = 0; i < Nqidhash; i++){
		for(q = fs->qidt[i]; q != nil; q = n){
			n = q->next;
			freeqid(q);
		}
	}
}

static void
exfree(Export *fs)
{
	lock(&fs->r.l);
	if(--fs->r.ref != 0){
		unlock(&fs->r.l);
		return;
	}
	unlock(&fs->r.l);
	closefgrp(fs->fgrp);
	closepgrp(fs->pgrp);
	cclose(fs->root);
	cclose(fs->io);
	exfreefids(fs);
	exfreeqids(fs);
	qfree(fs->q);
	free(fs);
	excloses++;
}

static int
exwork(void *a)
{
	USED(a);
	return exq.head != nil;
}

static void
exslave(void *a)
{
	Export *volatile fs;
	Exq *volatile q, *t, **last;
	char *volatile err;
	int n;

	USED(a);
	slaves++;

	if(exdebug&DBGproc)
		iprint("new exslave %d\n", getpid());

	for(;;){
		if(exdebug&DBGproc)
			iprint("exslave %d waiting for work\n", getpid());
		qlock(&exq.qwait);
		rendsleep(&exq.rwait, exwork, nil);

		lock(&exq.l);
		q = exq.head;
		if(q == nil) {
			unlock(&exq.l);
			qunlock(&exq.qwait);
			continue;
		}
		exq.head = q->next;
		q->slave = up;
		unlock(&exq.l);

		qunlock(&exq.qwait);

		q->noresponse = 0;
		q->nointr = 0;
		fs = q->export;
		lock(&fs->r.l);
		q->next = fs->work;
		fs->work = q;
		unlock(&fs->r.l);

		up->fgrp = q->export->fgrp;
		up->pgrp = q->export->pgrp;
//		kstrdup(&up->user, q->export->user);

		if(exdebug&DBGproc)
			iprint("exslave %d dispatched %F\n", getpid(), &q->rpc);

		if(waserror()){
			iprint("exslave err %r\n");
			err = up->err;
			goto Err;
		}
		if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
			err = "bad fcall type";
		else
			err = (*fcalls[q->rpc.type])(fs, &q->rpc);

		poperror();
		Err:;
		freeblist(q->b);
		lock(&fs->r.l);
		notkilled();
		last = &fs->work;
		for(t = fs->work; t != nil; t = t->next){
			if(t == q){
				*last = q->next;
				break;
			}
			last = &t->next;
		}

		if(q->shut) {
			unlock(&fs->r.l);
			exfree(q->export);
			exqfree(q);
			continue;
		}
		unlock(&fs->r.l);

		q->rpc.type++;
		if(err){
			q->rpc.type = Rerror;
			q->rpc.ename = err;
		}
		n = convS2M(&q->rpc, q->buf, q->buflen);
		if(n < 0)
			panic("bad message type in exslave");

		if(exdebug&DBG9p)
			iprint("exslave %d -> %F\n", getpid(), &q->rpc);

		switch(q->rpc.type) {
		case Rread:
			free(q->rpc.data);
			break;
		case Rstat:
			free(q->rpc.stat);
			break;
		}

		lock(&q->lk);
		if(q->noresponse == 0){
			q->nointr = 1;
			up->killed = 0;
			if(!waserror()){
				devtab[fs->io->type]->_write(fs->io, q->buf, n, 0);
				poperror();
			}
		}
		unlock(&q->lk);

		/*
		 * exflush might set noresponse at this point, but
		 * setting noresponse means don't send a response now;
		 * it's okay that we sent a response already.
		 */
		if(exdebug&DBGproc)
			iprint("exslave %d written %d\n", getpid(), q->rpc.tag);

		exfree(q->export);
		exqfree(q);
	}
	iprint("exslave shut down");
	pexit("exslave shut down", 0);
}

static int
pathhash(uvlong vpath)
{
	ulong path;

	path = (ulong)vpath ^ (ulong)(vpath>>32);
	return (path^(path>>8)^(path>>16)^(path>>24)) & (Nqidhash-1);
}

static Qid
Exrmtqid(Export *fs, Fid *f)
{
	Qid r;
	Chan *c;
	Qidt *q;
	uvlong path;
	int h0, h1;

	c = f->chan;
	r.vers = c->qid.vers;
	r.type = c->qid.type;
	path = c->qid.path;
	h0 = pathhash(path);

	qlock(&fs->fidlock);
	decrqid(f->qidt);
	f->qidt = nil;

	/* qidt hashes (type, dev, path) */
	for(q = fs->qidt[h0]; q != nil; q = q->next) {
		if(q->type==c->type && q->dev==c->dev && q->path==path) {
			q->ref++;
			f->qidt = q;
			qunlock(&fs->fidlock);
			r.path = q->uniqpath;
			return r;
		}
	}

	h1 = h0;
	for (;;) {
		/* qidp hashes uniqpath */
		for(q = fs->qidp[h1]; q != nil; q = q->next)
			if(q->uniqpath == path)
				break;

		if(q == nil)
			break;

		collisions++;
		lock(&qidalloc.l);
		path = qidalloc.path;
		if(path == 0)
			path = PATHHI;
		qidalloc.path = path - 1;
		unlock(&qidalloc.l);

		h1 = pathhash(path);
	}

	r.path = path;

	lock(&qidalloc.l);
	q = qidalloc.free;
	if(q != nil)
		qidalloc.free = q->next;
	unlock(&qidalloc.l);

	if(q == nil) {
		qidts++;
		q = malloc(sizeof(Qidt));
		if(q == nil) {
			qunlock(&fs->fidlock);
			return r;
		}
	}

	q->next = fs->qidt[h0];
	if(q->next != nil)
		q->next->last = &q->next;
	q->last = &fs->qidt[h0];
	fs->qidt[h0] = q;

	q->pnext = fs->qidp[h1];
	if(q->pnext != nil)
		q->pnext->plast = &q->pnext;
	q->plast = &fs->qidp[h1];
	fs->qidp[h1] = q;

	q->ref = 1;
	q->type = c->type;
	q->dev = c->dev;
	q->path = c->qid.path;
	q->uniqpath = path;
	f->qidt = q;

	qunlock(&fs->fidlock);
	return r;
}

static Fid*
Exmkfid(Export *fs, int fid)
{
	ulong h;
	Fid *f, *nf;

	lock(&fidalloc.l);
	nf = fidalloc.free;
	if(nf != nil)
		fidalloc.free = nf->next;
	unlock(&fidalloc.l);

	if(nf == nil) {
		fids++;
		nf = malloc(sizeof(Fid));
		if(nf == nil)
			return nil;
	}

	qlock(&fs->fidlock);
	h = fid & (Nfidhash-1);
	for(f = fs->fid[h]; f != nil; f = f->next){
		if(f->fid == fid){
			qunlock(&fs->fidlock);
			free(nf);
			return nil;
		}
	}

	nf->next = fs->fid[h];
	if(nf->next != nil)
		nf->next->last = &nf->next;
	nf->last = &fs->fid[h];
	fs->fid[h] = nf;

	nf->fid = fid;
	nf->ref = 1;
	nf->attached = 1;
	nf->chan = nil;
	nf->qidt = nil;
	qunlock(&fs->fidlock);
	return nf;
}

static Fid*
Exgetfid(Export *fs, int fid)
{
	Fid *f;
	ulong h;

	qlock(&fs->fidlock);
	h = fid & (Nfidhash-1);
	for(f = fs->fid[h]; f; f = f->next) {
		if(f->fid == fid){
			if(f->attached == 0)
				break;
			f->ref++;
			qunlock(&fs->fidlock);
			return f;
		}
	}
	qunlock(&fs->fidlock);
	return nil;
}

static void
Exputfid(Export *fs, Fid *f)
{
	Chan *c;

	qlock(&fs->fidlock);
	f->ref--;
	if(f->ref == 0 && f->attached == 0){
		c = f->chan;
		f->chan = nil;
		*f->last = f->next;
		if(f->next != nil)
			f->next->last = f->last;
		decrqid(f->qidt);
		qunlock(&fs->fidlock);
		if(c != nil)
			cclose(c);
		freefid(f);
		return;
	}
	qunlock(&fs->fidlock);
}

static char*
Exversion(Export *e, Fcall *rpc)
{
	e->io->iounit = rpc->msize;
	if(strncmp(rpc->version, "9P2000", 6) != 0)
		return Eversion;
	return nil;
}

static char*
Exauth(Export *e, Fcall *rpc)
{
	USED(e);
	USED(rpc);
	return "authentication not supported";
}

static char*
Exattach(Export *fs, Fcall *rpc)
{
	Fid *f;

	f = Exmkfid(fs, rpc->fid);
	if(f == nil)
		return Einuse;
	if(waserror()){
		f->attached = 0;
		Exputfid(fs, f);
		return up->err;
	}
	f->chan = cclone(fs->root);
	poperror();
	rpc->qid = Exrmtqid(fs, f);
	Exputfid(fs, f);
	return nil;
}

static Fid*
exclonefid(Export *fs, Fid *f, int new)
{
	Fid *n;

	n = Exmkfid(fs, new);
	if(n == nil) {
		n = Exgetfid(fs, new);
		if(n == nil)
			return nil;
		n->attached = 0;
		Exputfid(fs, n);
		n = Exmkfid(fs, new);
		if(n == nil)
			return nil;
	}
	if(waserror())
		return nil;
	n->chan = cclone(f->chan);
	poperror();
	return n;
}

static char*
Exwalk(Export *fs, Fcall *rpc)
{
	char *e;
	Chan *c;
	Fid *f, *nf;
	int i, nwqid;
	Qid wqid[MAXWELEM];

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;

	nf = nil;
	if(rpc->newfid != rpc->fid){
		nf = exclonefid(fs, f, rpc->newfid);
		f = nf;
		if(f == nil)
			panic("Exwalk");
	}

	nwqid = 0;
	e = nil;
	c = f->chan;
	for(i = 0; i < rpc->nwname; i++){
		if(i == MAXWELEM){
			e = "Too many path elements";
			break;
		}
	
		if(waserror()){
			e = up->err;
			break;
		}
		if(walk(&c, &rpc->wname[i], 1, 0) < 0) {
			poperror();
			e = Enonexist;
			c = nil;
			break;
		}
		poperror();

		wqid[nwqid++] = c->qid;
	}
	f->chan = c;

	if(nf != nil && (e != nil || nwqid != rpc->nwname)) {
		nf->attached = 0;
		Exputfid(fs, nf);
	}

	Exputfid(fs, f);
	if(e != nil)
		return e;

	rpc->qid = Exrmtqid(fs, f);
	rpc->nwqid = nwqid;
	memmove(rpc->wqid, wqid, nwqid*sizeof(Qid));
	return nil;
}

static char*
Exclunk(Export *fs, Fcall *rpc)
{
	Fid *f;

	f = Exgetfid(fs, rpc->fid);
	if(f != nil){
		f->attached = 0;
		Exputfid(fs, f);
	}
	return nil;
}

static char*
Exopen(Export *fs, Fcall *rpc)
{
	Fid *f;
	Chan *c;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;
	if(waserror()){
		Exputfid(fs, f);
		return up->err;
	}
	c = f->chan;
	c = devtab[c->type]->_open(c, rpc->mode);
	poperror();

	f->chan = c;
	rpc->qid = Exrmtqid(fs, f);
	rpc->iounit = c->iounit;
	Exputfid(fs, f);
	return nil;
}

extern	Chan*	cunique(Chan*);
extern	int		findmount(Chan *volatile *, Mhead *volatile *, int, int, Qid);
extern	Chan*	createdir(Chan*, Mhead*);

static char*
Excreate(Export *fs, Fcall *rpc)
{
	Fid *f;
	Mhead *volatile m;
	Chan *c, *volatile cnew;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;
	if(waserror()){
		Exputfid(fs, f);
		return up->err;
	}
	c = f->chan;
	m = nil;
	cnew = nil;	/* is this assignment necessary? */
	if(!waserror()){	/* try create */
		if(findmount(&cnew, &m, c->type, c->dev, c->qid))
			cnew = createdir(cnew, m);
		else{
			cnew = c;
			incref(&cnew->ref);
		}
		cnew = cunique(cnew);

		devtab[cnew->type]->_create(cnew, rpc->name, rpc->mode&~(OEXCL|OCEXEC), rpc->perm);
		poperror();
		if(rpc->mode & OCEXEC)
			cnew->flag |= CCEXEC;
		if(rpc->mode & ORCLOSE)
			cnew->flag |= CRCLOSE;
		if(m)
			putmhead(m);
		cclose(c);
		c = cnew;
	}else{		/* create failed */
		cclose(cnew);
		if(m)
			putmhead(m);
		if(rpc->mode & OEXCL)
			nexterror();
		if(walk(&c, &rpc->name, 1, 0) < 0)
			nexterror();
		rpc->mode |= OTRUNC;
		c = devtab[c->type]->_open(c, rpc->mode);
	}
	poperror();

	f->chan = c;
	rpc->qid = Exrmtqid(fs, f);
	rpc->iounit = c->iounit;
	Exputfid(fs, f);
	return nil;
}

static char*
Exread(Export *fs, Fcall *rpc)
{
	Fid *f;
	Chan *c;
	Lock *cl;
	int n, dir, count, seeking, maxread;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;

	n = rpc->count;
	maxread = fs->io->iounit-IOHDRSZ;
	if(n > maxread)
		n = maxread;

	c = f->chan;
	dir = c->qid.type & QTDIR;
	if(dir){
		if(rpc->offset < c->offset){
			Exputfid(fs, f);
			return Eseekdir;
		}
	}

	rpc->data = malloc(n);
	if(rpc->data == nil)
		return Enomem;

	if(waserror()) {
		Exputfid(fs, f);
		return up->err;
	}

	do{
		seeking = 0;
		if(dir && rpc->offset > c->offset){
			count = rpc->offset - c->offset;
			if(count > maxread)
				count = maxread;
			seeking = 1;
		}else if(dir && rpc->offset < c->offset){
			return Eseekdir;
		}else{
			count = n;
			c->offset = rpc->offset;
		}
		if(dir && c->umh != nil)
			count = unionread(c, rpc->data, count);
		else {
			count = devtab[c->type]->_read(c, rpc->data, count, c->offset);
			lock(&c->lk);
			c->offset += count;
			unlock(&c->lk);
		}
	}while(count > 0 && seeking);

	rpc->count = count;
	poperror();
	Exputfid(fs, f);
	return nil;
}

static char*
Exwrite(Export *fs, Fcall *rpc)
{
	Fid *f;
	Chan *c;
	int n, maxwrite;

	n = rpc->count;
	maxwrite = fs->io->iounit-IOHDRSZ;
	if(n > maxwrite)
		n = maxwrite;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;
	if(waserror()){
		Exputfid(fs, f);
		return up->err;
	}
	c = f->chan;
	if(c->qid.type & QTDIR)
		error(Eisdir);
	rpc->count = devtab[c->type]->_write(c, rpc->data, n, rpc->offset);
	poperror();
	Exputfid(fs, f);
	return nil;
}

static char*
Exstat(Export *fs, Fcall *rpc)
{
	int n;
	Fid *f;
	Chan *c;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;
	if(waserror()){
		Exputfid(fs, f);
		return up->err;
	}
	c = f->chan;
	n = fs->io->iounit;
	rpc->stat = malloc(n);
	rpc->nstat = devtab[c->type]->_stat(c, rpc->stat, n);
	poperror();
	Exputfid(fs, f);
	return nil;
}

static char*
Exwstat(Export *fs, Fcall *rpc)
{
	Fid *f;
	Chan *c;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;
	if(waserror()){
		Exputfid(fs, f);
		return up->err;
	}
	c = f->chan;
	devtab[c->type]->_wstat(c, rpc->stat, rpc->nstat);
	poperror();
	Exputfid(fs, f);
	return nil;
}

static char*
Exremove(Export *fs, Fcall *rpc)
{
	Fid *f;
	Chan *c;

	f = Exgetfid(fs, rpc->fid);
	if(f == nil)
		return Enofid;
	if(waserror()){
		f->attached = 0;
		Exputfid(fs, f);
		return up->err;
	}
	c = f->chan;
	devtab[c->type]->_remove(c);
	poperror();

	/*
	 * chan is already clunked by remove.
	 * however, we need to recover the chan,
	 * and follow sysremove's lead in making it point to root.
	 */
	c->type = 0;

	f->attached = 0;
	Exputfid(fs, f);
	return nil;
}
