Plan 9 version, nothing tweaked yet.
diff --git a/src/cmd/plumb/fsys.c b/src/cmd/plumb/fsys.c
new file mode 100644
index 0000000..6f95a23
--- /dev/null
+++ b/src/cmd/plumb/fsys.c
@@ -0,0 +1,975 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <regexp.h>
+#include <thread.h>
+#include <auth.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "plumber.h"
+
+enum
+{
+	Stack = 8*1024
+};
+
+typedef struct Dirtab Dirtab;
+typedef struct Fid Fid;
+typedef struct Holdq Holdq;
+typedef struct Readreq Readreq;
+typedef struct Sendreq Sendreq;
+
+struct Dirtab
+{
+	char		*name;
+	uchar	type;
+	uint		qid;
+	uint		perm;
+	int		nopen;		/* #fids open on this port */
+	Fid		*fopen;
+	Holdq	*holdq;
+	Readreq	*readq;
+	Sendreq	*sendq;
+};
+
+struct Fid
+{
+	int		fid;
+	int		busy;
+	int		open;
+	int		mode;
+	Qid		qid;
+	Dirtab	*dir;
+	long		offset;		/* zeroed at beginning of each message, read or write */
+	char		*writebuf;		/* partial message written so far; offset tells how much */
+	Fid		*next;
+	Fid		*nextopen;
+};
+
+struct Readreq
+{
+	Fid		*fid;
+	Fcall		*fcall;
+	uchar	*buf;
+	Readreq	*next;
+};
+
+struct Sendreq
+{
+	int			nfid;		/* number of fids that should receive this message */
+	int			nleft;		/* number left that haven't received it */
+	Fid			**fid;	/* fid[nfid] */
+	Plumbmsg	*msg;
+	char			*pack;	/* plumbpack()ed message */
+	int			npack;	/* length of pack */
+	Sendreq		*next;
+};
+
+struct Holdq
+{
+	Plumbmsg	*msg;
+	Holdq		*next;
+};
+
+struct	/* needed because incref() doesn't return value */
+{
+	Lock;
+	int			ref;
+} rulesref;
+
+enum
+{
+	DEBUG	= 0,
+	NDIR	= 50,
+	Nhash	= 16,
+
+	Qdir		= 0,
+	Qrules	= 1,
+	Qsend	= 2,
+	Qport	= 3,
+	NQID	= Qport
+};
+
+static Dirtab dir[NDIR] =
+{
+	{ ".",			QTDIR,	Qdir,			0500|DMDIR },
+	{ "rules",		QTFILE,	Qrules,		0600 },
+	{ "send",		QTFILE,	Qsend,		0200 },
+};
+static int	ndir = NQID;
+
+static int		srvfd;
+static int		srvclosefd;			/* rock for end of pipe to close */
+static int		clockfd;
+static int		clock;
+static Fid		*fids[Nhash];
+static QLock	readlock;
+static QLock	queue;
+static char	srvfile[128];
+static int		messagesize = 8192+IOHDRSZ;	/* good start */
+
+static void	fsysproc(void*);
+static void fsysrespond(Fcall*, uchar*, char*);
+static Fid*	newfid(int);
+
+static Fcall* fsysflush(Fcall*, uchar*, Fid*);
+static Fcall* fsysversion(Fcall*, uchar*, Fid*);
+static Fcall* fsysauth(Fcall*, uchar*, Fid*);
+static Fcall* fsysattach(Fcall*, uchar*, Fid*);
+static Fcall* fsyswalk(Fcall*, uchar*, Fid*);
+static Fcall* fsysopen(Fcall*, uchar*, Fid*);
+static Fcall* fsyscreate(Fcall*, uchar*, Fid*);
+static Fcall* fsysread(Fcall*, uchar*, Fid*);
+static Fcall* fsyswrite(Fcall*, uchar*, Fid*);
+static Fcall* fsysclunk(Fcall*, uchar*, Fid*);
+static Fcall* fsysremove(Fcall*, uchar*, Fid*);
+static Fcall* fsysstat(Fcall*, uchar*, Fid*);
+static Fcall* fsyswstat(Fcall*, uchar*, Fid*);
+
+Fcall* 	(*fcall[Tmax])(Fcall*, uchar*, Fid*) =
+{
+	[Tflush]	= fsysflush,
+	[Tversion]	= fsysversion,
+	[Tauth]	= fsysauth,
+	[Tattach]	= fsysattach,
+	[Twalk]	= fsyswalk,
+	[Topen]	= fsysopen,
+	[Tcreate]	= fsyscreate,
+	[Tread]	= fsysread,
+	[Twrite]	= fsyswrite,
+	[Tclunk]	= fsysclunk,
+	[Tremove]= fsysremove,
+	[Tstat]	= fsysstat,
+	[Twstat]	= fsyswstat,
+};
+
+char	Ebadfcall[] =	"bad fcall type";
+char	Eperm[] = 	"permission denied";
+char	Enomem[] =	"malloc failed for buffer";
+char	Enotdir[] =	"not a directory";
+char	Enoexist[] =	"plumb file does not exist";
+char	Eisdir[] =		"file is a directory";
+char	Ebadmsg[] =	"bad plumb message format";
+char Enosuchport[] ="no such plumb port";
+char Enoport[] =	"couldn't find destination for message";
+char	Einuse[] = 	"file already open";
+
+/*
+ * Add new port.  A no-op if port already exists or is the null string
+ */
+void
+addport(char *port)
+{
+	int i;
+
+	if(port == nil)
+		return;
+	for(i=NQID; i<ndir; i++)
+		if(strcmp(port, dir[i].name) == 0)
+			return;
+	if(i == NDIR){
+		fprint(2, "plumb: too many ports; max %d\n", NDIR);
+		return;
+	}
+	ndir++;
+	dir[i].name = estrdup(port);
+	dir[i].qid = i;
+	dir[i].perm = 0400;
+	nports++;
+	ports = erealloc(ports, nports*sizeof(char*));
+	ports[nports-1] = dir[i].name;
+}
+
+static ulong
+getclock(void)
+{
+	char buf[32];
+
+	seek(clockfd, 0, 0);
+	read(clockfd, buf, sizeof buf);
+	return atoi(buf);
+}
+
+void
+startfsys(void)
+{
+	int p[2], fd;
+
+	fmtinstall('F', fcallfmt);
+	clockfd = open("/dev/time", OREAD|OCEXEC);
+	clock = getclock();
+	if(pipe(p) < 0)
+		error("can't create pipe: %r");
+	/* 0 will be server end, 1 will be client end */
+	srvfd = p[0];
+	srvclosefd = p[1];
+	sprint(srvfile, "/srv/plumb.%s.%d", user, getpid());
+	if(putenv("plumbsrv", srvfile) < 0)
+		error("can't write $plumbsrv: %r");
+	fd = create(srvfile, OWRITE|OCEXEC|ORCLOSE, 0600);
+	if(fd < 0)
+		error("can't create /srv file: %r");
+	if(fprint(fd, "%d", p[1]) <= 0)
+		error("can't write /srv/file: %r");
+	/* leave fd open; ORCLOSE will take care of it */
+
+	procrfork(fsysproc, nil, Stack, RFFDG);
+
+	close(p[0]);
+	if(mount(p[1], -1, "/mnt/plumb", MREPL, "") < 0)
+		error("can't mount /mnt/plumb: %r");
+	close(p[1]);
+}
+
+static void
+fsysproc(void*)
+{
+	int n;
+	Fcall *t;
+	Fid *f;
+	uchar *buf;
+
+	close(srvclosefd);
+	srvclosefd = -1;
+	t = nil;
+	for(;;){
+		buf = malloc(messagesize);	/* avoid memset of emalloc */
+		if(buf == nil)
+			error("malloc failed: %r");
+		qlock(&readlock);
+		n = read9pmsg(srvfd, buf, messagesize);
+		if(n <= 0){
+			if(n < 0)
+				error("i/o error on server channel");
+			threadexitsall("unmounted");
+		}
+		if(readlock.head == nil)	/* no other processes waiting to read; start one */
+			proccreate(fsysproc, nil, Stack);
+		qunlock(&readlock);
+		if(t == nil)
+			t = emalloc(sizeof(Fcall));
+		if(convM2S(buf, n, t) != n)
+			error("convert error in convM2S");
+		if(DEBUG)
+			fprint(2, "<= %F\n", t);
+		if(fcall[t->type] == nil)
+			fsysrespond(t, buf, Ebadfcall);
+		else{
+			if(t->type==Tversion || t->type==Tauth)
+				f = nil;
+			else
+				f = newfid(t->fid);
+			t = (*fcall[t->type])(t, buf, f);
+		}
+	}
+}
+
+static void
+fsysrespond(Fcall *t, uchar *buf, char *err)
+{
+	int n;
+
+	if(err){
+		t->type = Rerror;
+		t->ename = err;
+	}else
+		t->type++;
+	if(buf == nil)
+		buf = emalloc(messagesize);
+	n = convS2M(t, buf, messagesize);
+	if(n < 0)
+		error("convert error in convS2M");
+	if(write(srvfd, buf, n) != n)
+		error("write error in respond");
+	if(DEBUG)
+		fprint(2, "=> %F\n", t);
+	free(buf);
+}
+
+static
+Fid*
+newfid(int fid)
+{
+	Fid *f, *ff, **fh;
+
+	qlock(&queue);
+	ff = nil;
+	fh = &fids[fid&(Nhash-1)];
+	for(f=*fh; f; f=f->next)
+		if(f->fid == fid)
+			goto Return;
+		else if(ff==nil && !f->busy)
+			ff = f;
+	if(ff){
+		ff->fid = fid;
+		f = ff;
+		goto Return;
+	}
+	f = emalloc(sizeof *f);
+	f->fid = fid;
+	f->next = *fh;
+	*fh = f;
+    Return:
+	qunlock(&queue);
+	return f;
+}
+
+static uint
+dostat(Dirtab *dir, uchar *buf, uint nbuf, uint clock)
+{
+	Dir d;
+
+	d.qid.type = dir->type;
+	d.qid.path = dir->qid;
+	d.qid.vers = 0;
+	d.mode = dir->perm;
+	d.length = 0;	/* would be nice to do better */
+	d.name = dir->name;
+	d.uid = user;
+	d.gid = user;
+	d.muid = user;
+	d.atime = clock;
+	d.mtime = clock;
+	return convD2M(&d, buf, nbuf);
+}
+
+static void
+queuesend(Dirtab *d, Plumbmsg *m)
+{
+	Sendreq *s, *t;
+	Fid *f;
+	int i;
+
+	s = emalloc(sizeof(Sendreq));
+	s->nfid = d->nopen;
+	s->nleft = s->nfid;
+	s->fid = emalloc(s->nfid*sizeof(Fid*));
+	i = 0;
+	/* build array of fids open on this channel */
+	for(f=d->fopen; f!=nil; f=f->nextopen)
+		s->fid[i++] = f;
+	s->msg = m;
+	s->next = nil;
+	/* link to end of queue; drainqueue() searches in sender order so this implements a FIFO */
+	for(t=d->sendq; t!=nil; t=t->next)
+		if(t->next == nil)
+			break;
+	if(t == nil)
+		d->sendq = s;
+	else
+		t->next = s;
+}
+
+static void
+queueread(Dirtab *d, Fcall *t, uchar *buf, Fid *f)
+{
+	Readreq *r;
+
+	r = emalloc(sizeof(Readreq));
+	r->fcall = t;
+	r->buf = buf;
+	r->fid = f;
+	r->next = d->readq;
+	d->readq = r;
+}
+
+static void
+drainqueue(Dirtab *d)
+{
+	Readreq *r, *nextr, *prevr;
+	Sendreq *s, *nexts, *prevs;
+	int i, n;
+
+	prevs = nil;
+	for(s=d->sendq; s!=nil; s=nexts){
+		nexts = s->next;
+		for(i=0; i<s->nfid; i++){
+			prevr = nil;
+			for(r=d->readq; r!=nil; r=nextr){
+				nextr = r->next;
+				if(r->fid == s->fid[i]){
+					/* pack the message if necessary */
+					if(s->pack == nil)
+						s->pack = plumbpack(s->msg, &s->npack);
+					/* exchange the stuff... */
+					r->fcall->data = s->pack+r->fid->offset;
+					n = s->npack - r->fid->offset;
+					if(n > messagesize-IOHDRSZ)
+						n = messagesize-IOHDRSZ;
+					if(n > r->fcall->count)
+						n = r->fcall->count;
+					r->fcall->count = n;
+					fsysrespond(r->fcall, r->buf, nil);
+					r->fid->offset += n;
+					if(r->fid->offset >= s->npack){
+						/* message transferred; delete this fid from send queue */
+						r->fid->offset = 0;
+						s->fid[i] = nil;
+						s->nleft--;
+					}
+					/* delete read request from queue */
+					if(prevr)
+						prevr->next = r->next;
+					else
+						d->readq = r->next;
+					free(r->fcall);
+					free(r);
+					break;
+				}else
+					prevr = r;
+			}
+		}
+		/* if no fids left, delete this send from queue */
+		if(s->nleft == 0){
+			free(s->fid);
+			plumbfree(s->msg);
+			free(s->pack);
+			if(prevs)
+				prevs->next = s->next;
+			else
+				d->sendq = s->next;
+			free(s);
+		}else
+			prevs = s;
+	}
+}
+
+/* can't flush a send because they are always answered synchronously */
+static void
+flushqueue(Dirtab *d, int oldtag)
+{
+	Readreq *r, *prevr;
+
+	prevr = nil;
+	for(r=d->readq; r!=nil; r=r->next){
+		if(oldtag == r->fcall->tag){
+			/* delete read request from queue */
+			if(prevr)
+				prevr->next = r->next;
+			else
+				d->readq = r->next;
+			free(r->fcall);
+			free(r->buf);
+			free(r);
+			return;
+		}
+		prevr = r;
+	}
+}
+
+/* remove messages awaiting delivery to now-closing fid */
+static void
+removesenders(Dirtab *d, Fid *fid)
+{
+	Sendreq *s, *nexts, *prevs;
+	int i;
+
+	prevs = nil;
+	for(s=d->sendq; s!=nil; s=nexts){
+		nexts = s->next;
+		for(i=0; i<s->nfid; i++)
+			if(fid == s->fid[i]){
+				/* delete this fid from send queue */
+				s->fid[i] = nil;
+				s->nleft--;
+				break;
+			}
+		/* if no fids left, delete this send from queue */
+		if(s->nleft == 0){
+			free(s->fid);
+			plumbfree(s->msg);
+			free(s->pack);
+			if(prevs)
+				prevs->next = s->next;
+			else
+				d->sendq = s->next;
+			free(s);
+		}else
+			prevs = s;
+	}
+}
+
+static void
+hold(Plumbmsg *m, Dirtab *d)
+{
+	Holdq *h, *q;
+
+	h = emalloc(sizeof(Holdq));
+	h->msg = m;
+	/* add to end of queue */
+	if(d->holdq == nil)
+		d->holdq = h;
+	else{
+		for(q=d->holdq; q->next!=nil; q=q->next)
+			;
+		q->next = h;
+	}
+}
+
+static void
+queueheld(Dirtab *d)
+{
+	Holdq *h;
+
+	while(d->holdq != nil){
+		h = d->holdq;
+		d->holdq = h->next;
+		queuesend(d, h->msg);
+		/* no need to drain queue because we know no-one is reading yet */
+		free(h);
+	}
+}
+
+static void
+dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
+{
+	int i;
+	char *err;
+
+	qlock(&queue);
+	err = nil;
+	if(m->dst==nil || m->dst[0]=='\0'){
+		err = Enoport;
+		if(rs != nil)
+			err = startup(rs, e);
+		plumbfree(m);
+	}else
+		for(i=NQID; i<ndir; i++)
+			if(strcmp(m->dst, dir[i].name) == 0){
+				if(dir[i].nopen == 0){
+					err = startup(rs, e);
+					if(e!=nil && e->holdforclient)
+						hold(m, &dir[i]);
+					else
+						plumbfree(m);
+				}else{
+					queuesend(&dir[i], m);
+					drainqueue(&dir[i]);
+				}
+				break;
+			}
+	freeexec(e);
+	qunlock(&queue);
+	fsysrespond(t, buf, err);
+	free(t);
+}
+
+static Fcall*
+fsysversion(Fcall *t, uchar *buf, Fid*)
+{
+	if(t->msize < 256){
+		fsysrespond(t, buf, "version: message size too small");
+		return t;
+	}
+	if(t->msize < messagesize)
+		messagesize = t->msize;
+	t->msize = messagesize;
+	if(strncmp(t->version, "9P2000", 6) != 0){
+		fsysrespond(t, buf, "unrecognized 9P version");
+		return t;
+	}
+	t->version = "9P2000";
+	fsysrespond(t, buf, nil);
+	return t;
+}
+
+static Fcall*
+fsysauth(Fcall *t, uchar *buf, Fid*)
+{
+	fsysrespond(t, buf, "plumber: authentication not required");
+	return t;
+}
+
+static Fcall*
+fsysattach(Fcall *t, uchar *buf, Fid *f)
+{
+	Fcall out;
+
+	if(strcmp(t->uname, user) != 0){
+		fsysrespond(&out, buf, Eperm);
+		return t;
+	}
+	f->busy = 1;
+	f->open = 0;
+	f->qid.type = QTDIR;
+	f->qid.path = Qdir;
+	f->qid.vers = 0;
+	f->dir = dir;
+	memset(&out, 0, sizeof(Fcall));
+	out.type = t->type;
+	out.tag = t->tag;
+	out.fid = f->fid;
+	out.qid = f->qid;
+	fsysrespond(&out, buf, nil);
+	return t;
+}
+
+static Fcall*
+fsysflush(Fcall *t, uchar *buf, Fid*)
+{
+	int i;
+
+	qlock(&queue);
+	for(i=NQID; i<ndir; i++)
+		flushqueue(&dir[i], t->oldtag);
+	qunlock(&queue);
+	fsysrespond(t, buf, nil);
+	return t;
+}
+
+static Fcall*
+fsyswalk(Fcall *t, uchar *buf, Fid *f)
+{
+	Fcall out;
+	Fid *nf;
+	ulong path;
+	Dirtab *d, *dir;
+	Qid q;
+	int i;
+	uchar type;
+	char *err;
+
+	if(f->open){
+		fsysrespond(t, buf, "clone of an open fid");
+		return t;
+	}
+
+	nf = nil;
+	if(t->fid  != t->newfid){
+		nf = newfid(t->newfid);
+		if(nf->busy){
+			fsysrespond(t, buf, "clone to a busy fid");
+			return t;
+		}
+		nf->busy = 1;
+		nf->open = 0;
+		nf->dir = f->dir;
+		nf->qid = f->qid;
+		f = nf;	/* walk f */
+	}
+
+	out.nwqid = 0;
+	err = nil;
+	dir = f->dir;
+	q = f->qid;
+
+	if(t->nwname > 0){
+		for(i=0; i<t->nwname; i++){
+			if((q.type & QTDIR) == 0){
+				err = Enotdir;
+				break;
+			}
+			if(strcmp(t->wname[i], "..") == 0){
+				type = QTDIR;
+				path = Qdir;
+	Accept:
+				q.type = type;
+				q.vers = 0;
+				q.path = path;
+				out.wqid[out.nwqid++] = q;
+				continue;
+			}
+			d = dir;
+			d++;	/* skip '.' */
+			for(; d->name; d++)
+				if(strcmp(t->wname[i], d->name) == 0){
+					type = d->type;
+					path = d->qid;
+					dir = d;
+					goto Accept;
+				}
+			err = Enoexist;
+			break;
+		}
+	}
+
+	out.type = t->type;
+	out.tag = t->tag;
+	if(err!=nil || out.nwqid<t->nwname){
+		if(nf)
+			nf->busy = 0;
+	}else if(out.nwqid == t->nwname){
+		f->qid = q;
+		f->dir = dir;
+	}
+
+	fsysrespond(&out, buf, err);
+	return t;
+}
+
+static Fcall*
+fsysopen(Fcall *t, uchar *buf, Fid *f)
+{
+	int m, clearrules, mode;
+
+	clearrules = 0;
+	if(t->mode & OTRUNC){
+		if(f->qid.path != Qrules)
+			goto Deny;
+		clearrules = 1;
+	}
+	/* can't truncate anything, so just disregard */
+	mode = t->mode & ~(OTRUNC|OCEXEC);
+	/* can't execute or remove anything */
+	if(mode==OEXEC || (mode&ORCLOSE))
+		goto Deny;
+	switch(mode){
+	default:
+		goto Deny;
+	case OREAD:
+		m = 0400;
+		break;
+	case OWRITE:
+		m = 0200;
+		break;
+	case ORDWR:
+		m = 0600;
+		break;
+	}
+	if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
+		goto Deny;
+	if(f->qid.path==Qrules && (mode==OWRITE || mode==ORDWR)){
+		lock(&rulesref);
+		if(rulesref.ref++ != 0){
+			rulesref.ref--;
+			unlock(&rulesref);
+			fsysrespond(t, buf, Einuse);
+			return t;
+		}
+		unlock(&rulesref);
+	}
+	if(clearrules){
+		writerules(nil, 0);
+		rules[0] = nil;
+	}
+	t->qid = f->qid;
+	t->iounit = 0;
+	qlock(&queue);
+	f->mode = mode;
+	f->open = 1;
+	f->dir->nopen++;
+	f->nextopen = f->dir->fopen;
+	f->dir->fopen = f;
+	queueheld(f->dir);
+	qunlock(&queue);
+	fsysrespond(t, buf, nil);
+	return t;
+
+    Deny:
+	fsysrespond(t, buf, Eperm);
+	return t;
+}
+
+static Fcall*
+fsyscreate(Fcall *t, uchar *buf, Fid*)
+{
+	fsysrespond(t, buf, Eperm);
+	return t;
+}
+
+static Fcall*
+fsysreadrules(Fcall *t, uchar *buf)
+{
+	char *p;
+	int n;
+
+	p = printrules();
+	n = strlen(p);
+	t->data = p;
+	if(t->offset >= n)
+		t->count = 0;
+	else{
+		t->data = p+t->offset;
+		if(t->offset+t->count > n)
+			t->count = n-t->offset;
+	}
+	fsysrespond(t, buf, nil);
+	free(p);
+	return t;
+}
+
+static Fcall*
+fsysread(Fcall *t, uchar *buf, Fid *f)
+{
+	uchar *b;
+	int i, n, o, e;
+	uint len;
+	Dirtab *d;
+	uint clock;
+
+	if(f->qid.path != Qdir){
+		if(f->qid.path == Qrules)
+			return fsysreadrules(t, buf);
+		/* read from port */
+		if(f->qid.path < NQID){
+			fsysrespond(t, buf, "internal error: unknown read port");
+			return t;
+		}
+		qlock(&queue);
+		queueread(f->dir, t, buf, f);
+		drainqueue(f->dir);
+		qunlock(&queue);
+		return nil;
+	}
+	o = t->offset;
+	e = t->offset+t->count;
+	clock = getclock();
+	b = malloc(messagesize-IOHDRSZ);
+	if(b == nil){
+		fsysrespond(t, buf, Enomem);
+		return t;
+	}
+	n = 0;
+	d = dir;
+	d++;	/* first entry is '.' */
+	for(i=0; d->name!=nil && i<e; i+=len){
+		len = dostat(d, b+n, messagesize-IOHDRSZ-n, clock);
+		if(len <= BIT16SZ)
+			break;
+		if(i >= o)
+			n += len;
+		d++;
+	}
+	t->data = (char*)b;
+	t->count = n;
+	fsysrespond(t, buf, nil);
+	free(b);
+	return t;
+}
+
+static Fcall*
+fsyswrite(Fcall *t, uchar *buf, Fid *f)
+{
+	Plumbmsg *m;
+	int i, n;
+	long count;
+	char *data;
+	Exec *e;
+
+	switch((int)f->qid.path){
+	case Qdir:
+		fsysrespond(t, buf, Eisdir);
+		return t;
+	case Qrules:
+		clock = getclock();
+		fsysrespond(t, buf, writerules(t->data, t->count));
+		return t;
+	case Qsend:
+		if(f->offset == 0){
+			data = t->data;
+			count = t->count;
+		}else{
+			/* partial message already assembled */
+			f->writebuf = erealloc(f->writebuf, f->offset + t->count);
+			memmove(f->writebuf+f->offset, t->data, t->count);
+			data = f->writebuf;
+			count = f->offset+t->count;
+		}
+		m = plumbunpackpartial(data, count, &n);
+		if(m == nil){
+			if(n == 0){
+				f->offset = 0;
+				free(f->writebuf);
+				f->writebuf = nil;
+				fsysrespond(t, buf, Ebadmsg);
+				return t;
+			}
+			/* can read more... */
+			if(f->offset == 0){
+				f->writebuf = emalloc(t->count);
+				memmove(f->writebuf, t->data, t->count);
+			}
+			/* else buffer has already been grown */
+			f->offset += t->count;
+			fsysrespond(t, buf, nil);
+			return t;
+		}
+		/* release partial buffer */
+		f->offset = 0;
+		free(f->writebuf);
+		f->writebuf = nil;
+		for(i=0; rules[i]; i++)
+			if((e=matchruleset(m, rules[i])) != nil){
+				dispose(t, buf, m, rules[i], e);
+				return nil;
+			}
+		if(m->dst != nil){
+			dispose(t, buf, m, nil, nil);
+			return nil;
+		}
+		fsysrespond(t, buf, "no matching plumb rule");
+		return t;
+	}
+	fsysrespond(t, buf, "internal error: write to unknown file");
+	return t;
+}
+
+static Fcall*
+fsysstat(Fcall *t, uchar *buf, Fid *f)
+{
+	t->stat = emalloc(messagesize-IOHDRSZ);
+	t->nstat = dostat(f->dir, t->stat, messagesize-IOHDRSZ, clock);
+	fsysrespond(t, buf, nil);
+	free(t->stat);
+	t->stat = nil;
+	return t;
+}
+
+static Fcall*
+fsyswstat(Fcall *t, uchar *buf, Fid*)
+{
+	fsysrespond(t, buf, Eperm);
+	return t;
+}
+
+static Fcall*
+fsysremove(Fcall *t, uchar *buf, Fid*)
+{
+	fsysrespond(t, buf, Eperm);
+	return t;
+}
+
+static Fcall*
+fsysclunk(Fcall *t, uchar *buf, Fid *f)
+{
+	Fid *prev, *p;
+	Dirtab *d;
+
+	qlock(&queue);
+	if(f->open){
+		d = f->dir;
+		d->nopen--;
+		if(d->qid==Qrules && (f->mode==OWRITE || f->mode==ORDWR)){
+			/*
+			 * just to be sure last rule is parsed; error messages will be lost, though,
+			 * unless last write ended with a blank line
+			 */
+			writerules(nil, 0);
+			lock(&rulesref);
+			rulesref.ref--;
+			unlock(&rulesref);
+		}
+		prev = nil;
+		for(p=d->fopen; p; p=p->nextopen){
+			if(p == f){
+				if(prev)
+					prev->nextopen = f->nextopen;
+				else
+					d->fopen = f->nextopen;
+				removesenders(d, f);
+				break;
+			}
+			prev = p;
+		}
+	}
+	f->busy = 0;
+	f->open = 0;
+	f->offset = 0;
+	if(f->writebuf != nil){
+		free(f->writebuf);
+		f->writebuf = nil;
+	}
+	qunlock(&queue);
+	fsysrespond(t, buf, nil);
+	return t;
+}