Small tweaks
Lots of new code imported.
diff --git a/src/lib9p/_post.c b/src/lib9p/_post.c
new file mode 100644
index 0000000..e8313be
--- /dev/null
+++ b/src/lib9p/_post.c
@@ -0,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <auth.h>
+#include "post.h"
+
+Postcrud*
+_post1(Srv *s, char *name, char *mtpt, int flag)
+{
+	Postcrud *p;
+
+	p = emalloc9p(sizeof *p);
+	if(!s->nopipe){
+		if(pipe(p->fd) < 0)
+			sysfatal("pipe: %r");
+		s->infd = s->outfd = p->fd[1];
+		s->srvfd = p->fd[0];
+	}
+	if(name)
+		if(postfd(name, s->srvfd) < 0)
+			sysfatal("postfd %s: %r", name);
+	p->s = s;
+	p->mtpt = mtpt;
+	p->flag = flag;
+	return p;
+}
+
+void
+_post2(void *v)
+{
+	Srv *s;
+
+	s = v;
+	rfork(RFNOTEG);
+	if(!s->leavefdsopen){
+		rendezvous((ulong)s, 0);
+		close(s->srvfd);
+	}
+	srv(s);
+}
+
+void
+_post3(Postcrud *p)
+{
+	/*
+	 * Normally the server is posting as the last thing it does
+	 * before exiting, so the correct thing to do is drop into
+	 * a different fd space and close the 9P server half of the
+	 * pipe before trying to mount the kernel half.  This way,
+	 * if the file server dies, we don't have a ref to the 9P server
+	 * half of the pipe.  Then killing the other procs will drop
+	 * all the refs on the 9P server half, and the mount will fail.
+	 * Otherwise the mount hangs forever.
+	 *
+	 * Libthread in general and acme win in particular make
+	 * it hard to make this fd bookkeeping work out properly,
+	 * so leaveinfdopen is a flag that win sets to opt out of this
+	 * safety net.
+	 */
+	if(!p->s->leavefdsopen){
+		rfork(RFFDG);
+		rendezvous((ulong)p->s, 0);
+		close(p->s->infd);
+		if(p->s->infd != p->s->outfd)
+			close(p->s->outfd);
+	}
+
+	if(p->mtpt){
+		if(amount(p->s->srvfd, p->mtpt, p->flag, "") == -1)
+			sysfatal("mount %s: %r", p->mtpt);
+	}else
+		close(p->s->srvfd);
+	free(p);
+}
+
diff --git a/src/lib9p/dirread.c b/src/lib9p/dirread.c
new file mode 100644
index 0000000..7fb8830
--- /dev/null
+++ b/src/lib9p/dirread.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+void
+dirread9p(Req *r, Dirgen *gen, void *aux)
+{
+	int start;
+	uchar *p, *ep;
+	uint rv;
+	Dir d;
+
+	if(r->ifcall.offset == 0)
+		start = 0;
+	else
+		start = r->fid->dirindex;
+
+	p = (uchar*)r->ofcall.data;
+	ep = p+r->ifcall.count;
+
+	while(p < ep){
+		memset(&d, 0, sizeof d);
+		if((*gen)(start, &d, aux) < 0)
+			break;
+		rv = convD2M(&d, p, ep-p);
+		free(d.name);
+		free(d.muid);
+		free(d.uid);
+		free(d.gid);
+		if(rv <= BIT16SZ)
+			break;
+		p += rv;
+		start++;
+	}
+	r->fid->dirindex = start;
+	r->ofcall.count = p - (uchar*)r->ofcall.data;
+}
diff --git a/src/lib9p/fid.c b/src/lib9p/fid.c
new file mode 100644
index 0000000..2393f1c
--- /dev/null
+++ b/src/lib9p/fid.c
@@ -0,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include "9p.h"
+
+static void
+incfidref(void *v)
+{
+	Fid *f;
+
+	f = v;
+	if(f)
+		incref(&f->ref);
+}
+
+Fidpool*
+allocfidpool(void (*destroy)(Fid*))
+{
+	Fidpool *f;
+
+	f = emalloc9p(sizeof *f);
+	f->map = allocmap(incfidref);
+	f->destroy = destroy;
+	return f;
+}
+
+void
+freefidpool(Fidpool *p)
+{
+	freemap(p->map, (void(*)(void*))p->destroy);
+	free(p);
+}
+
+Fid*
+allocfid(Fidpool *pool, ulong fid)
+{
+	Fid *f;
+
+	f = emalloc9p(sizeof *f);
+	f->fid = fid;
+	f->omode = -1;
+	f->pool = pool;
+
+	incfidref(f);
+	incfidref(f);
+	if(caninsertkey(pool->map, fid, f) == 0){
+		closefid(f);
+		return nil;
+	}
+
+	return f;
+}
+
+Fid*
+lookupfid(Fidpool *pool, ulong fid)
+{
+	return lookupkey(pool->map, fid);
+}
+
+void
+closefid(Fid *f)
+{
+	if(decref(&f->ref) == 0) {
+		if(f->rdir)
+			closedirfile(f->rdir);
+		if(f->pool->destroy)
+			f->pool->destroy(f);
+		if(f->file)
+			closefile(f->file);
+		free(f->uid);
+		free(f);
+	}
+}
+
+Fid*
+removefid(Fidpool *pool, ulong fid)
+{
+	return deletekey(pool->map, fid);
+}
diff --git a/src/lib9p/file.c b/src/lib9p/file.c
new file mode 100644
index 0000000..79a8a11
--- /dev/null
+++ b/src/lib9p/file.c
@@ -0,0 +1,372 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/*
+ * To avoid deadlock, the following rules must be followed.
+ * Always lock child then parent, never parent then child.
+ * If holding the free file lock, do not lock any Files.
+ */
+struct Filelist {
+	File *f;
+	Filelist *link;
+};
+
+static QLock filelk;
+static File *freefilelist;
+
+static File*
+allocfile(void)
+{
+	int i, a;
+	File *f;
+	enum { N = 16 };
+
+	qlock(&filelk);
+	if(freefilelist == nil){
+		f = emalloc9p(N*sizeof(*f));
+		for(i=0; i<N-1; i++)
+			f[i].aux = &f[i+1];
+		f[N-1].aux = nil;
+		f[0].allocd = 1;
+		freefilelist = f;
+	}
+
+	f = freefilelist;
+	freefilelist = f->aux;
+	qunlock(&filelk);
+
+	a = f->allocd;
+	memset(f, 0, sizeof *f);
+	f->allocd = a;
+	return f;
+}
+
+static void
+freefile(File *f)
+{
+	Filelist *fl, *flnext;
+
+	for(fl=f->filelist; fl; fl=flnext){
+		flnext = fl->link;
+		assert(fl->f == nil);
+		free(fl);
+	}
+
+	free(f->dir.name);
+	free(f->dir.uid);
+	free(f->dir.gid);
+	free(f->dir.muid);
+	qlock(&filelk);
+	assert(f->ref.ref == 0);
+	f->aux = freefilelist;
+	freefilelist = f;
+	qunlock(&filelk);
+}
+
+void
+closefile(File *f)
+{
+	if(decref(&f->ref) == 0){
+		f->tree->destroy(f);
+		freefile(f);
+	}
+}
+
+static void
+nop(File *f)
+{
+	USED(f);
+}
+
+int
+removefile(File *f)
+{
+	File *fp;
+	Filelist *fl;
+	
+	fp = f->parent;
+	if(fp == nil){
+		werrstr("no parent");
+		closefile(f);
+		return -1;
+	}
+
+	if(fp == f){
+		werrstr("cannot remove root");
+		closefile(f);
+		return -1;
+	}
+
+	wlock(&fp->rwlock);
+	wlock(&f->rwlock);
+	if(f->nchild != 0){
+		werrstr("has children");
+		wunlock(&f->rwlock);
+		wunlock(&fp->rwlock);
+		closefile(f);
+		return -1;
+	}
+
+	if(f->parent != fp){
+		werrstr("parent changed underfoot");
+		wunlock(&f->rwlock);
+		wunlock(&fp->rwlock);
+		closefile(f);
+		return -1;
+	}
+
+	for(fl=fp->filelist; fl; fl=fl->link)
+		if(fl->f == f)
+			break;
+	assert(fl != nil && fl->f == f);
+
+	fl->f = nil;
+	fp->nchild--;
+	f->parent = nil;
+	wunlock(&fp->rwlock);
+	wunlock(&f->rwlock);
+
+	closefile(fp);	/* reference from child */
+	closefile(f);	/* reference from tree */
+	closefile(f);
+	return 0;
+}
+
+File*
+createfile(File *fp, char *name, char *uid, ulong perm, void *aux)
+{
+	File *f;
+	Filelist *fl, *freel;
+	Tree *t;
+
+	if((fp->dir.qid.type&QTDIR) == 0){
+		werrstr("create in non-directory");
+		return nil;
+	}
+
+	freel = nil;
+	wlock(&fp->rwlock);
+	for(fl=fp->filelist; fl; fl=fl->link){
+		if(fl->f == nil)
+			freel = fl;
+		else if(strcmp(fl->f->dir.name, name) == 0){
+			wunlock(&fp->rwlock);
+			werrstr("file already exists");
+			return nil;
+		}
+	}
+
+	if(freel == nil){
+		freel = emalloc9p(sizeof *freel);
+		freel->link = fp->filelist;
+		fp->filelist = freel;
+	}
+
+	f = allocfile();
+	f->dir.name = estrdup9p(name);
+	f->dir.uid = estrdup9p(uid ? uid : fp->dir.uid);
+	f->dir.gid = estrdup9p(fp->dir.gid);
+	f->dir.muid = estrdup9p(uid ? uid : "unknown");
+	f->aux = aux;
+	f->dir.mode = perm;
+
+	t = fp->tree;
+	lock(&t->genlock);
+	f->dir.qid.path = t->qidgen++;
+	unlock(&t->genlock);
+	if(perm & DMDIR)
+		f->dir.qid.type |= QTDIR;
+	if(perm & DMAPPEND)
+		f->dir.qid.type |= QTAPPEND;
+	if(perm & DMEXCL)
+		f->dir.qid.type |= QTEXCL;
+
+	f->dir.mode = perm;
+	f->dir.atime = f->dir.mtime = time(0);
+	f->dir.length = 0;
+	f->parent = fp;
+	incref(&fp->ref);
+	f->tree = fp->tree;
+
+	incref(&f->ref);	/* being returned */
+	incref(&f->ref);	/* for the tree */
+	freel->f = f;
+	fp->nchild++;
+	wunlock(&fp->rwlock);
+
+	return f;
+}
+
+static File*
+walkfile1(File *dir, char *elem)
+{
+	File *fp;
+	Filelist *fl;
+
+	rlock(&dir->rwlock);
+	if(strcmp(elem, "..") == 0){
+		fp = dir->parent;
+		incref(&fp->ref);
+		runlock(&dir->rwlock);
+		closefile(dir);
+		return fp;
+	}
+
+	fp = nil;
+	for(fl=dir->filelist; fl; fl=fl->link)
+		if(fl->f && strcmp(fl->f->dir.name, elem)==0){
+			fp = fl->f;
+			incref(&fp->ref);
+			break;
+		}
+
+	runlock(&dir->rwlock);
+	closefile(dir);
+	return fp;
+}
+
+File*
+walkfile(File *f, char *path)
+{
+	char *os, *s, *nexts;
+	File *nf;
+
+	if(strchr(path, '/') == nil)
+		return walkfile1(f, path);	/* avoid malloc */
+
+	os = s = estrdup9p(path);
+	incref(&f->ref);
+	for(; *s; s=nexts){
+		if(nexts = strchr(s, '/'))
+			*nexts++ = '\0';
+		else
+			nexts = s+strlen(s);
+		nf = walkfile1(f, s);
+		decref(&f->ref);
+		f = nf;
+		if(f == nil)
+			break;
+	}
+	free(os);
+	return f;
+}
+			
+Tree*
+alloctree(char *uid, char *gid, ulong mode, void (*destroy)(File*))
+{
+	char *muid;
+	Tree *t;
+	File *f;
+
+	t = emalloc9p(sizeof *t);
+	f = allocfile();
+	f->dir.name = estrdup9p("/");
+	if(uid == nil){
+		if(uid = getuser())
+			uid = estrdup9p(uid);
+	}
+	if(uid == nil)
+		uid = estrdup9p("none");
+	else
+		uid = estrdup9p(uid);
+
+	if(gid == nil)
+		gid = estrdup9p(uid);
+	else
+		gid = estrdup9p(gid);
+
+	muid = estrdup9p(uid);
+
+	f->dir.qid = (Qid){0, 0, QTDIR};
+	f->dir.length = 0;
+	f->dir.atime = f->dir.mtime = time(0);
+	f->dir.mode = DMDIR | mode;
+	f->tree = t;
+	f->parent = f;
+	f->dir.uid = uid;
+	f->dir.gid = gid;
+	f->dir.muid = muid;
+
+	incref(&f->ref);
+	t->root = f;
+	t->qidgen = 0;
+	t->dirqidgen = 1;
+	if(destroy == nil)
+		destroy = nop;
+	t->destroy = destroy;
+
+	return t;
+}
+
+static void
+_freefiles(File *f)
+{
+	Filelist *fl, *flnext;
+
+	for(fl=f->filelist; fl; fl=flnext){
+		flnext = fl->link;
+		_freefiles(fl->f);
+		free(fl);
+	}
+
+	f->tree->destroy(f);
+	freefile(f);
+}
+
+void
+freetree(Tree *t)
+{
+	_freefiles(t->root);
+	free(t);
+}
+
+struct Readdir {
+	Filelist *fl;
+};
+
+Readdir*
+opendirfile(File *dir)
+{
+	Readdir *r;
+
+	rlock(&dir->rwlock);
+	if((dir->dir.mode & DMDIR)==0){
+		runlock(&dir->rwlock);
+		return nil;
+	}
+	r = emalloc9p(sizeof(*r));
+
+	/*
+	 * This reference won't go away while we're using it
+	 * since we are dir->rdir.
+	 */
+	r->fl = dir->filelist;
+	runlock(&dir->rwlock);
+	return r;
+}
+
+long
+readdirfile(Readdir *r, uchar *buf, long n)
+{
+	long x, m;
+	Filelist *fl;
+
+	for(fl=r->fl, m=0; fl && m+2<=n; fl=fl->link, m+=x){
+		if(fl->f == nil)
+			x = 0;
+		else if((x=convD2M(&fl->f->dir, buf+m, n-m)) <= BIT16SZ)
+			break;
+	}
+	r->fl = fl;
+	return m;
+}
+
+void
+closedirfile(Readdir *r)
+{
+	free(r);
+}
diff --git a/src/lib9p/ftest.c b/src/lib9p/ftest.c
new file mode 100644
index 0000000..6692b7f
--- /dev/null
+++ b/src/lib9p/ftest.c
@@ -0,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include "9p.h"
+
+void
+main(void)
+{
+	Tree *t;
+	File *hello, *goodbye, *world;
+
+	t = mktree();
+
+	hello = fcreate(t->root, "hello", CHDIR|0777);
+	assert(hello != nil);
+
+	goodbye = fcreate(t->root, "goodbye", CHDIR|0777);
+	assert(goodbye != nil);
+
+	world = fcreate(hello, "world", 0666);
+	assert(world != nil);
+	world = fcreate(goodbye, "world", 0666);
+	assert(world != nil);
+	fdump(t->root, 0);
+
+	fremove(world);
+	fdump(t->root, 0);
+}
diff --git a/src/lib9p/intmap.c b/src/lib9p/intmap.c
new file mode 100644
index 0000000..4869567
--- /dev/null
+++ b/src/lib9p/intmap.c
@@ -0,0 +1,166 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+enum {
+	NHASH = 128
+};
+
+typedef struct Intlist	Intlist;
+struct Intlist
+{
+	ulong	id;
+	void*	aux;
+	Intlist*	link;
+};
+
+struct Intmap
+{
+	RWLock	rwlock;
+	Intlist*	hash[NHASH];
+	void (*inc)(void*);
+};
+
+static ulong
+hashid(ulong id)
+{
+	return id%NHASH;
+}
+
+static void
+nop(void *v)
+{
+	USED(v);
+}
+
+Intmap*
+allocmap(void (*inc)(void*))
+{
+	Intmap *m;
+
+	m = emalloc9p(sizeof(*m));
+	if(inc == nil)
+		inc = nop;
+	m->inc = inc;
+	return m;
+}
+
+void
+freemap(Intmap *map, void (*destroy)(void*))
+{
+	int i;
+	Intlist *p, *nlink;
+
+	if(destroy == nil)
+		destroy = nop;
+	for(i=0; i<NHASH; i++){
+		for(p=map->hash[i]; p; p=nlink){
+			nlink = p->link;
+			destroy(p->aux);
+			free(p);
+		}
+	}
+			
+	free(map);
+}
+
+static Intlist**
+llookup(Intmap *map, ulong id)
+{
+	Intlist **lf;
+
+	for(lf=&map->hash[hashid(id)]; *lf; lf=&(*lf)->link)
+		if((*lf)->id == id)
+			break;
+	return lf;	
+}
+
+/*
+ * The RWlock is used as expected except that we allow
+ * inc() to be called while holding it.  This is because we're
+ * locking changes to the tree structure, not to the references.
+ * Inc() is expected to have its own locking.
+ */
+void*
+lookupkey(Intmap *map, ulong id)
+{
+	Intlist *f;
+	void *v;
+
+	rlock(&map->rwlock);
+	if(f = *llookup(map, id)){
+		v = f->aux;
+		map->inc(v);
+	}else
+		v = nil;
+	runlock(&map->rwlock);
+	return v;
+}
+
+void*
+insertkey(Intmap *map, ulong id, void *v)
+{
+	Intlist *f;
+	void *ov;
+	ulong h;
+
+	wlock(&map->rwlock);
+	if(f = *llookup(map, id)){
+		/* no decrement for ov because we're returning it */
+		ov = f->aux;
+		f->aux = v;
+	}else{
+		f = emalloc9p(sizeof(*f));
+		f->id = id;
+		f->aux = v;
+		h = hashid(id);
+		f->link = map->hash[h];
+		map->hash[h] = f;
+		ov = nil;
+	}
+	wunlock(&map->rwlock);
+	return ov;	
+}
+
+int
+caninsertkey(Intmap *map, ulong id, void *v)
+{
+	Intlist *f;
+	int rv;
+	ulong h;
+
+	wlock(&map->rwlock);
+	if(*llookup(map, id))
+		rv = 0;
+	else{
+		f = emalloc9p(sizeof *f);
+		f->id = id;
+		f->aux = v;
+		h = hashid(id);
+		f->link = map->hash[h];
+		map->hash[h] = f;
+		rv = 1;
+	}
+	wunlock(&map->rwlock);
+	return rv;	
+}
+
+void*
+deletekey(Intmap *map, ulong id)
+{
+	Intlist **lf, *f;
+	void *ov;
+
+	wlock(&map->rwlock);
+	if(f = *(lf = llookup(map, id))){
+		ov = f->aux;
+		*lf = f->link;
+		free(f);
+	}else
+		ov = nil;
+	wunlock(&map->rwlock);
+	return ov;
+}
diff --git a/src/lib9p/mem.c b/src/lib9p/mem.c
new file mode 100644
index 0000000..b441495
--- /dev/null
+++ b/src/lib9p/mem.c
@@ -0,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include "9p.h"
+
+void*
+emalloc9p(ulong sz)
+{
+	void *v;
+
+	if((v = malloc(sz)) == nil) {
+		fprint(2, "out of memory allocating %lud\n", sz);
+		exits("mem");
+	}
+	memset(v, 0, sz);
+	setmalloctag(v, getcallerpc(&sz));
+	return v;
+}
+
+void*
+erealloc9p(void *v, ulong sz)
+{
+	void *nv;
+
+	if((nv = realloc(v, sz)) == nil) {
+		fprint(2, "out of memory allocating %lud\n", sz);
+		exits("mem");
+	}
+	if(v == nil)
+		setmalloctag(nv, getcallerpc(&v));
+	setrealloctag(nv, getcallerpc(&v));
+	return nv;
+}
+
+char*
+estrdup9p(char *s)
+{
+	char *t;
+
+	if((t = strdup(s)) == nil) {
+		fprint(2, "out of memory in strdup(%.10s)\n", s);
+		exits("mem");
+	}
+	setmalloctag(t, getcallerpc(&s));
+	return t;
+}
+
diff --git a/src/lib9p/mkfile b/src/lib9p/mkfile
new file mode 100644
index 0000000..17a10a3
--- /dev/null
+++ b/src/lib9p/mkfile
@@ -0,0 +1,20 @@
+PLAN9=../..
+<$PLAN9/src/mkhdr
+
+LIB=lib9p.a
+OFILES=\
+	_post.$O\
+	dirread.$O\
+	fid.$O\
+	file.$O\
+	intmap.$O\
+	mem.$O\
+	req.$O\
+	parse.$O\
+	post.$O\
+	srv.$O\
+	tpost.$O\
+	uid.$O\
+	util.$O\
+
+<$PLAN9/src/mksyslib
diff --git a/src/lib9p/parse.c b/src/lib9p/parse.c
new file mode 100644
index 0000000..753ae79
--- /dev/null
+++ b/src/lib9p/parse.c
@@ -0,0 +1,115 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/*
+ * Generous estimate of number of fields, including terminal nil pointer
+ */
+static int
+ncmdfield(char *p, int n)
+{
+	int white, nwhite;
+	char *ep;
+	int nf;
+
+	if(p == nil)
+		return 1;
+
+	nf = 0;
+	ep = p+n;
+	white = 1;	/* first text will start field */
+	while(p < ep){
+		nwhite = (strchr(" \t\r\n", *p++ & 0xFF) != 0);	/* UTF is irrelevant */
+		if(white && !nwhite)	/* beginning of field */
+			nf++;
+		white = nwhite;
+	}
+	return nf+1;	/* +1 for nil */
+}
+
+/*
+ *  parse a command written to a device
+ */
+Cmdbuf*
+parsecmd(char *p, int n)
+{
+	Cmdbuf *cb;
+	int nf;
+	char *sp;
+
+	nf = ncmdfield(p, n);
+
+	/* allocate Cmdbuf plus string pointers plus copy of string including \0 */
+	sp = emalloc9p(sizeof(*cb) + nf * sizeof(char*) + n + 1);
+	cb = (Cmdbuf*)sp;
+	cb->f = (char**)(&cb[1]);
+	cb->buf = (char*)(&cb->f[nf]);
+
+	memmove(cb->buf, p, n);
+
+	/* dump new line and null terminate */
+	if(n > 0 && cb->buf[n-1] == '\n')
+		n--;
+	cb->buf[n] = '\0';
+
+	cb->nf = tokenize(cb->buf, cb->f, nf-1);
+	cb->f[cb->nf] = nil;
+
+	return cb;
+}
+
+/*
+ * Reconstruct original message, for error diagnostic
+ */
+void
+respondcmderror(Req *r, Cmdbuf *cb, char *fmt, ...)
+{
+	int i;
+	va_list arg;
+	char *p, *e;
+	char err[ERRMAX];
+	
+	e = err+ERRMAX-10;
+	va_start(arg, fmt);
+	p = vseprint(err, e, fmt, arg);
+	va_end(arg);
+	p = seprint(p, e, ": \"");
+	for(i=0; i<cb->nf; i++){
+		if(i > 0)
+			p = seprint(p, e, " ");
+		p = seprint(p, e, "%q", cb->f[i]);
+	}
+	strcpy(p, "\"");
+	respond(r, err);
+}
+
+/*
+ * Look up entry in table
+ */
+Cmdtab*
+lookupcmd(Cmdbuf *cb, Cmdtab *ctab, int nctab)
+{
+	int i;
+	Cmdtab *ct;
+
+	if(cb->nf == 0){
+		werrstr("empty control message");
+		return nil;
+	}
+
+	for(ct = ctab, i=0; i<nctab; i++, ct++){
+		if(strcmp(ct->cmd, "*") !=0)	/* wildcard always matches */
+		if(strcmp(ct->cmd, cb->f[0]) != 0)
+			continue;
+		if(ct->narg != 0 && ct->narg != cb->nf){
+			werrstr("bad # args to command");
+			return nil;
+		}
+		return ct;
+	}
+
+	werrstr("unknown control message");
+	return nil;
+}
diff --git a/src/lib9p/post.c b/src/lib9p/post.c
new file mode 100644
index 0000000..09296a8
--- /dev/null
+++ b/src/lib9p/post.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "post.h"
+
+void
+postmountsrv(Srv *s, char *name, char *mtpt, int flag)
+{
+	Postcrud *p;
+
+	p = _post1(s, name, mtpt, flag);
+	switch(rfork(RFPROC|RFNOTEG|RFNAMEG|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		_post2(s);
+		exits(nil);
+	default:
+		_post3(p);
+	}
+}
+
diff --git a/src/lib9p/post.h b/src/lib9p/post.h
new file mode 100644
index 0000000..069a7ce
--- /dev/null
+++ b/src/lib9p/post.h
@@ -0,0 +1,13 @@
+typedef struct Postcrud Postcrud;
+struct Postcrud
+{
+	int fd[2];
+	Srv *s;
+	char *name;
+	char *mtpt;
+	int flag;
+};
+
+Postcrud *_post1(Srv*, char*, char*, int);
+void _post2(void*);
+void _post3(Postcrud*);
diff --git a/src/lib9p/ramfs.c b/src/lib9p/ramfs.c
new file mode 100644
index 0000000..a2f0b3d
--- /dev/null
+++ b/src/lib9p/ramfs.c
@@ -0,0 +1,163 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+static char Ebad[] = "something bad happened";
+static char Enomem[] = "no memory";
+
+typedef struct Ramfile	Ramfile;
+struct Ramfile {
+	char *data;
+	int ndata;
+};
+
+void
+fsread(Req *r)
+{
+	Ramfile *rf;
+	vlong offset;
+	long count;
+
+	rf = r->fid->file->aux;
+	offset = r->ifcall.offset;
+	count = r->ifcall.count;
+
+//print("read %ld %lld\n", *count, offset);
+	if(offset >= rf->ndata){
+		r->ofcall.count = 0;
+		respond(r, nil);
+		return;
+	}
+
+	if(offset+count >= rf->ndata)
+		count = rf->ndata - offset;
+
+	memmove(r->ofcall.data, rf->data+offset, count);
+	r->ofcall.count = count;
+	respond(r, nil);
+}
+
+void
+fswrite(Req *r)
+{
+	void *v;
+	Ramfile *rf;
+	vlong offset;
+	long count;
+
+	rf = r->fid->file->aux;
+	offset = r->ifcall.offset;
+	count = r->ifcall.count;
+
+	if(offset+count >= rf->ndata){
+		v = realloc(rf->data, offset+count);
+		if(v == nil){
+			respond(r, Enomem);
+			return;
+		}
+		rf->data = v;
+		rf->ndata = offset+count;
+		r->fid->file->length = rf->ndata;
+	}
+	memmove(rf->data+offset, r->ifcall.data, count);
+	r->ofcall.count = count;
+	respond(r, nil);
+}
+
+void
+fscreate(Req *r)
+{
+	Ramfile *rf;
+	File *f;
+
+	if(f = createfile(r->fid->file, r->ifcall.name, r->fid->uid, r->ifcall.perm, nil)){
+		rf = emalloc9p(sizeof *rf);
+		f->aux = rf;
+		r->fid->file = f;
+		r->ofcall.qid = f->qid;
+		respond(r, nil);
+		return;
+	}
+	respond(r, Ebad);
+}
+
+void
+fsopen(Req *r)
+{
+	Ramfile *rf;
+
+	rf = r->fid->file->aux;
+
+	if(rf && (r->ifcall.mode&OTRUNC)){
+		rf->ndata = 0;
+		r->fid->file->length = 0;
+	}
+
+	respond(r, nil);
+}
+
+void
+fsdestroyfile(File *f)
+{
+	Ramfile *rf;
+
+//fprint(2, "clunk\n");
+	rf = f->aux;
+	if(rf){
+		free(rf->data);
+		free(rf);
+	}
+}
+
+Srv fs = {
+	.open=	fsopen,
+	.read=	fsread,
+	.write=	fswrite,
+	.create=	fscreate,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: ramfs [-D] [-s srvname] [-m mtpt]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *srvname = nil;
+	char *mtpt = nil;
+	Qid q;
+
+	fs.tree = alloctree(nil, nil, DMDIR|0777, fsdestroyfile);
+	q = fs.tree->root->qid;
+
+	ARGBEGIN{
+	case 'D':
+		chatty9p++;
+		break;
+	case 's':
+		srvname = EARGF(usage());
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc)
+		usage();
+
+	if(chatty9p)
+		fprint(2, "ramsrv.nopipe %d srvname %s mtpt %s\n", fs.nopipe, srvname, mtpt);
+	if(srvname == nil && mtpt == nil)
+		sysfatal("you should at least specify a -s or -m option");
+
+	postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE);
+	exits(0);
+}
diff --git a/src/lib9p/req.c b/src/lib9p/req.c
new file mode 100644
index 0000000..8e1aaab
--- /dev/null
+++ b/src/lib9p/req.c
@@ -0,0 +1,112 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+static void
+increqref(void *v)
+{
+	Req *r;
+
+	r = v;
+	if(r){
+if(chatty9p > 1)
+	fprint(2, "increfreq %p %ld\n", r, r->ref.ref);
+		incref(&r->ref);
+	}
+}
+
+Reqpool*
+allocreqpool(void (*destroy)(Req*))
+{
+	Reqpool *f;
+
+	f = emalloc9p(sizeof *f);
+	f->map = allocmap(increqref);
+	f->destroy = destroy;
+	return f;
+}
+
+void
+freereqpool(Reqpool *p)
+{
+	freemap(p->map, (void(*)(void*))p->destroy);
+	free(p);
+}	
+
+Req*
+allocreq(Reqpool *pool, ulong tag)
+{
+	Req *r;
+
+	r = emalloc9p(sizeof *r);
+	r->tag = tag;
+	r->pool = pool;
+
+	increqref(r);
+	increqref(r);
+	if(caninsertkey(pool->map, tag, r) == 0){
+		closereq(r);
+		return nil;
+	}
+
+	return r;
+}
+
+Req*
+lookupreq(Reqpool *pool, ulong tag)
+{
+if(chatty9p > 1)
+	fprint(2, "lookupreq %lud\n", tag);
+	return lookupkey(pool->map, tag);
+}
+
+void
+closereq(Req *r)
+{
+	int i;
+
+	if(r == nil)
+		return;
+
+if(chatty9p > 1)
+	fprint(2, "closereq %p %ld\n", r, r->ref.ref);
+
+	if(decref(&r->ref) == 0){
+		if(r->fid)
+			closefid(r->fid);
+		if(r->newfid)
+			closefid(r->newfid);
+		if(r->afid)
+			closefid(r->afid);
+		if(r->oldreq)
+			closereq(r->oldreq);
+		for(i=0; i<r->nflush; i++)
+			respond(r->flush[i], nil);
+		free(r->flush);
+		switch(r->ifcall.type){
+		case Tstat:
+			free(r->ofcall.stat);
+			free(r->d.name);
+			free(r->d.uid);
+			free(r->d.gid);
+			free(r->d.muid);
+			break;
+		}
+		if(r->pool->destroy)
+			r->pool->destroy(r);
+		free(r->buf);
+		free(r->rbuf);
+		free(r);
+	}
+}
+
+Req*
+removereq(Reqpool *pool, ulong tag)
+{
+if(chatty9p > 1)
+	fprint(2, "removereq %lud\n", tag);
+	return deletekey(pool->map, tag);
+}
diff --git a/src/lib9p/srv.c b/src/lib9p/srv.c
new file mode 100644
index 0000000..c5262f4
--- /dev/null
+++ b/src/lib9p/srv.c
@@ -0,0 +1,837 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+// static char Ebadattach[] = "unknown specifier in attach";
+static char Ebadoffset[] = "bad offset";
+// static char Ebadcount[] = "bad count";
+static char Ebotch[] = "9P protocol botch";
+static char Ecreatenondir[] = "create in non-directory";
+static char Edupfid[] = "duplicate fid";
+static char Eduptag[] = "duplicate tag";
+static char Eisdir[] = "is a directory";
+static char Enocreate[] = "create prohibited";
+// static char Enomem[] = "out of memory";
+static char Enoremove[] = "remove prohibited";
+static char Enostat[] = "stat prohibited";
+static char Enotfound[] = "file not found";
+// static char Enowrite[] = "write prohibited";
+static char Enowstat[] = "wstat prohibited";
+static char Eperm[] = "permission denied";
+static char Eunknownfid[] = "unknown fid";
+static char Ebaddir[] = "bad directory in wstat";
+static char Ewalknodir[] = "walk in non-directory";
+
+static void
+setfcallerror(Fcall *f, char *err)
+{
+	f->ename = err;
+	f->type = Rerror;
+}
+
+static void
+changemsize(Srv *srv, int msize)
+{
+	if(srv->rbuf && srv->wbuf && srv->msize == msize)
+		return;
+	qlock(&srv->rlock);
+	qlock(&srv->wlock);
+	srv->msize = msize;
+	free(srv->rbuf);
+	free(srv->wbuf);
+	srv->rbuf = emalloc9p(msize);
+	srv->wbuf = emalloc9p(msize);
+	qunlock(&srv->rlock);
+	qunlock(&srv->wlock);
+}
+
+static Req*
+getreq(Srv *s)
+{
+	long n;
+	uchar *buf;
+	Fcall f;
+	Req *r;
+
+	qlock(&s->rlock);
+	if((n = read9pmsg(s->infd, s->rbuf, s->msize)) <= 0){
+		qunlock(&s->rlock);
+		return nil;
+	}
+
+	buf = emalloc9p(n);
+	memmove(buf, s->rbuf, n);
+	qunlock(&s->rlock);
+
+	if(convM2S(buf, n, &f) != n){
+		free(buf);
+		return nil;
+	}
+
+	if((r=allocreq(s->rpool, f.tag)) == nil){	/* duplicate tag: cons up a fake Req */
+		r = emalloc9p(sizeof *r);
+		incref(&r->ref);
+		r->tag = f.tag;
+		r->ifcall = f;
+		r->error = Eduptag;
+		r->buf = buf;
+		r->responded = 0;
+		r->type = 0;
+		r->srv = s;
+		r->pool = nil;
+if(chatty9p)
+	fprint(2, "<-%d- %F: dup tag\n", s->infd, &f);
+		return r;
+	}
+
+	r->srv = s;
+	r->responded = 0;
+	r->buf = buf;
+	r->ifcall = f;
+	memset(&r->ofcall, 0, sizeof r->ofcall);
+	r->type = r->ifcall.type;
+
+if(chatty9p)
+	if(r->error)
+		fprint(2, "<-%d- %F: %s\n", s->infd, &r->ifcall, r->error);
+	else	
+		fprint(2, "<-%d- %F\n", s->infd, &r->ifcall);
+
+	return r;
+}
+
+static void
+filewalk(Req *r)
+{
+	int i;
+	File *f;
+
+	f = r->fid->file;
+	assert(f != nil);
+
+	incref(&f->ref);
+	for(i=0; i<r->ifcall.nwname; i++)
+		if(f = walkfile(f, r->ifcall.wname[i]))
+			r->ofcall.wqid[i] = f->dir.qid;
+		else
+			break;
+
+	r->ofcall.nwqid = i;
+	if(f){
+		r->newfid->file = f;
+		r->newfid->qid = r->newfid->file->dir.qid;
+	}
+	respond(r, nil);
+}
+
+void
+walkandclone(Req *r, char *(*walk1)(Fid*, char*, void*), char *(*clone)(Fid*, Fid*, void*), void *arg)
+{
+	int i;
+	char *e;
+
+	if(r->fid == r->newfid && r->ifcall.nwname > 1){
+		respond(r, "lib9p: unused documented feature not implemented");
+		return;
+	}
+
+	if(r->fid != r->newfid){
+		r->newfid->qid = r->fid->qid;
+		if(clone && (e = clone(r->fid, r->newfid, arg))){
+			respond(r, e);
+			return;
+		}
+	}
+
+	e = nil;
+	for(i=0; i<r->ifcall.nwname; i++){
+		if(e = walk1(r->newfid, r->ifcall.wname[i], arg))
+			break;
+		r->ofcall.wqid[i] = r->newfid->qid;
+	}
+
+	r->ofcall.nwqid = i;
+	if(e && i==0)
+		respond(r, e);
+	else
+		respond(r, nil);
+}
+
+static void
+sversion(Srv *srv, Req *r)
+{
+	USED(srv);
+
+	if(strncmp(r->ifcall.version, "9P", 2) != 0){
+		r->ofcall.version = "unknown";
+		respond(r, nil);
+		return;
+	}
+
+	r->ofcall.version = "9P2000";
+	r->ofcall.msize = r->ifcall.msize;
+	respond(r, nil);
+}
+static void
+rversion(Req *r, char *error)
+{
+	assert(error == nil);
+	changemsize(r->srv, r->ofcall.msize);
+}
+
+static void
+sauth(Srv *srv, Req *r)
+{
+	char e[ERRMAX];
+
+	if((r->afid = allocfid(srv->fpool, r->ifcall.afid)) == nil){
+		respond(r, Edupfid);
+		return;
+	}
+	if(srv->auth)
+		srv->auth(r);
+	else{
+		snprint(e, sizeof e, "%s: authentication not required", argv0);
+		respond(r, e);
+	}
+}
+static void
+rauth(Req *r, char *error)
+{
+	if(error && r->afid)
+		closefid(removefid(r->srv->fpool, r->afid->fid));
+}
+
+static void
+sattach(Srv *srv, Req *r)
+{
+	if((r->fid = allocfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Edupfid);
+		return;
+	}
+	r->afid = nil;
+	if(r->ifcall.afid != NOFID && (r->afid = lookupfid(srv->fpool, r->ifcall.afid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	r->fid->uid = estrdup9p(r->ifcall.uname);
+	if(srv->tree){
+		r->fid->file = srv->tree->root;
+		/* BUG? incref(r->fid->file) ??? */
+		r->ofcall.qid = r->fid->file->dir.qid;
+		r->fid->qid = r->ofcall.qid;
+	}
+	if(srv->attach)
+		srv->attach(r);
+	else
+		respond(r, nil);
+	return;
+}
+static void
+rattach(Req *r, char *error)
+{
+	if(error && r->fid)
+		closefid(removefid(r->srv->fpool, r->fid->fid));
+}
+
+static void
+sflush(Srv *srv, Req *r)
+{
+	r->oldreq = lookupreq(srv->rpool, r->ifcall.oldtag);
+	if(r->oldreq == nil || r->oldreq == r)
+		respond(r, nil);
+	else if(srv->flush)
+		srv->flush(r);
+	else
+		respond(r, nil);
+}
+static int
+rflush(Req *r, char *error)
+{
+	Req *or;
+
+	assert(error == nil);
+	or = r->oldreq;
+	if(or){
+		qlock(&or->lk);
+		if(or->responded == 0){
+			or->flush = erealloc9p(or->flush, (or->nflush+1)*sizeof(or->flush[0]));
+			or->flush[or->nflush++] = r;
+			qunlock(&or->lk);
+			return -1;		/* delay response until or is responded */
+		}
+		qunlock(&or->lk);
+		closereq(or);
+	}
+	r->oldreq = nil;
+	return 0;
+}
+
+static char*
+oldwalk1(Fid *fid, char *name, void *arg)
+{
+	char *e;
+	Qid qid;
+	Srv *srv;
+
+	srv = arg;
+	e = srv->walk1(fid, name, &qid);
+	if(e)
+		return e;
+	fid->qid = qid;
+	return nil;
+}
+
+static char*
+oldclone(Fid *fid, Fid *newfid, void *arg)
+{
+	Srv *srv;
+
+	srv = arg;
+	if(srv->clone == nil)
+		return nil;
+	return srv->clone(fid, newfid);
+}
+
+static void
+swalk(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->fid->omode != -1){
+		respond(r, "cannot clone open fid");
+		return;
+	}
+	if(r->ifcall.nwname && !(r->fid->qid.type&QTDIR)){
+		respond(r, Ewalknodir);
+		return;
+	}
+	if(r->ifcall.fid != r->ifcall.newfid){
+		if((r->newfid = allocfid(srv->fpool, r->ifcall.newfid)) == nil){
+			respond(r, Edupfid);
+			return;
+		}
+		r->newfid->uid = estrdup9p(r->fid->uid);
+	}else{
+		incref(&r->fid->ref);
+		r->newfid = r->fid;
+	}
+	if(r->fid->file){
+		filewalk(r);
+	}else if(srv->walk1)
+		walkandclone(r, oldwalk1, oldclone, srv);
+	else if(srv->walk)
+		srv->walk(r);
+	else
+		sysfatal("no walk function, no file trees");
+}
+static void
+rwalk(Req *r, char *error)
+{
+	if(error || r->ofcall.nwqid < r->ifcall.nwname){
+		if(r->ifcall.fid != r->ifcall.newfid && r->newfid)
+			closefid(removefid(r->srv->fpool, r->newfid->fid));
+		if (r->ofcall.nwqid==0){
+			if(error==nil && r->ifcall.nwname!=0)
+				r->error = Enotfound;
+		}else
+			r->error = nil;	// No error on partial walks
+	}else{
+		if(r->ofcall.nwqid == 0){
+			/* Just a clone */
+			r->newfid->qid = r->fid->qid;
+		}else{
+			/* if file trees are in use, filewalk took care of the rest */
+			r->newfid->qid = r->ofcall.wqid[r->ofcall.nwqid-1];
+		}
+	}
+}
+
+static void
+sopen(Srv *srv, Req *r)
+{
+	int p;
+
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->fid->omode != -1){
+		respond(r, Ebotch);
+		return;
+	}
+	if((r->fid->qid.type&QTDIR) && (r->ifcall.mode&~ORCLOSE) != OREAD){
+		respond(r, Eisdir);
+		return;
+	}
+	r->ofcall.qid = r->fid->qid;
+	switch(r->ifcall.mode&3){
+	default:
+		assert(0);
+	case OREAD:
+		p = AREAD;	
+		break;
+	case OWRITE:
+		p = AWRITE;
+		break;
+	case ORDWR:
+		p = AREAD|AWRITE;
+		break;
+	case OEXEC:
+		p = AEXEC;	
+		break;
+	}
+	if(r->ifcall.mode&OTRUNC)
+		p |= AWRITE;
+	if((r->fid->qid.type&QTDIR) && p!=AREAD){
+		respond(r, Eperm);
+		return;
+	}
+	if(r->fid->file){
+		if(!hasperm(r->fid->file, r->fid->uid, p)){
+			respond(r, Eperm);
+			return;
+		}
+	/* BUG RACE */
+		if((r->ifcall.mode&ORCLOSE)
+		&& !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
+			respond(r, Eperm);
+			return;
+		}
+		r->ofcall.qid = r->fid->file->dir.qid;
+		if((r->ofcall.qid.type&QTDIR)
+		&& (r->fid->rdir = opendirfile(r->fid->file)) == nil){
+			respond(r, "opendirfile failed");
+			return;
+		}
+	}
+	if(srv->open)
+		srv->open(r);
+	else
+		respond(r, nil);
+}
+static void
+ropen(Req *r, char *error)
+{
+	char errbuf[ERRMAX];
+	if(error)
+		return;
+	if(chatty9p){
+		snprint(errbuf, sizeof errbuf, "fid mode is 0x%ux\n", r->ifcall.mode);
+		write(2, errbuf, strlen(errbuf));
+	}
+	r->fid->omode = r->ifcall.mode;
+	r->fid->qid = r->ofcall.qid;
+	if(r->ofcall.qid.type&QTDIR)
+		r->fid->diroffset = 0;
+}
+
+static void
+screate(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil)
+		respond(r, Eunknownfid);
+	else if(r->fid->omode != -1)
+		respond(r, Ebotch);
+	else if(!(r->fid->qid.type&QTDIR))
+		respond(r, Ecreatenondir);
+	else if(r->fid->file && !hasperm(r->fid->file, r->fid->uid, AWRITE))
+		respond(r, Eperm);
+	else if(srv->create)
+		srv->create(r);
+	else
+		respond(r, Enocreate);
+}
+static void
+rcreate(Req *r, char *error)
+{
+	if(error)
+		return;
+	r->fid->omode = r->ifcall.mode;
+	r->fid->qid = r->ofcall.qid;
+}
+
+static void
+sread(Srv *srv, Req *r)
+{
+	int o;
+
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->ifcall.count < 0){
+		respond(r, Ebotch);
+		return;
+	}
+	if(r->ifcall.offset < 0
+	|| ((r->fid->qid.type&QTDIR) && r->ifcall.offset != 0 && r->ifcall.offset != r->fid->diroffset)){
+		respond(r, Ebadoffset);
+		return;
+	}
+
+	if(r->ifcall.count > srv->msize - IOHDRSZ)
+		r->ifcall.count = srv->msize - IOHDRSZ;
+	r->rbuf = emalloc9p(r->ifcall.count);
+	r->ofcall.data = r->rbuf;
+	o = r->fid->omode & 3;
+	if(o != OREAD && o != ORDWR && o != OEXEC){
+		respond(r, Ebotch);
+		return;
+	}
+	if((r->fid->qid.type&QTDIR) && r->fid->file){
+		r->ofcall.count = readdirfile(r->fid->rdir, r->rbuf, r->ifcall.count);
+		respond(r, nil);
+		return;
+	}
+	if(srv->read)
+		srv->read(r);
+	else
+		respond(r, "no srv->read");
+}
+static void
+rread(Req *r, char *error)
+{
+	if(error==nil && (r->fid->qid.type&QTDIR))
+		r->fid->diroffset += r->ofcall.count;
+}
+
+static void
+swrite(Srv *srv, Req *r)
+{
+	int o;
+	char e[ERRMAX];
+
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->ifcall.count < 0){
+		respond(r, Ebotch);
+		return;
+	}
+	if(r->ifcall.offset < 0){
+		respond(r, Ebotch);
+		return;
+	}
+	if(r->ifcall.count > srv->msize - IOHDRSZ)
+		r->ifcall.count = srv->msize - IOHDRSZ;
+	o = r->fid->omode & 3;
+	if(o != OWRITE && o != ORDWR){
+		snprint(e, sizeof e, "write on fid with open mode 0x%ux", r->fid->omode);
+		respond(r, e);
+		return;
+	}
+	if(srv->write)
+		srv->write(r);
+	else
+		respond(r, "no srv->write");
+}
+static void
+rwrite(Req *r, char *error)
+{
+	if(error)
+		return;
+	if(r->fid->file)
+		r->fid->file->dir.qid.vers++;
+}
+
+static void
+sclunk(Srv *srv, Req *r)
+{
+	if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil)
+		respond(r, Eunknownfid);
+	else
+		respond(r, nil);
+}
+static void
+rclunk(Req *r, char *msg)
+{
+	USED(r);
+	USED(msg);
+}
+
+static void
+sremove(Srv *srv, Req *r)
+{
+	if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	/* BUG RACE */
+	if(r->fid->file && !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
+		respond(r, Eperm);
+		return;
+	}
+	if(srv->remove)
+		srv->remove(r);
+	else
+		respond(r, r->fid->file ? nil : Enoremove);
+}
+static void
+rremove(Req *r, char *error, char *errbuf)
+{
+	if(error)
+		return;
+	if(r->fid->file){
+		if(removefile(r->fid->file) < 0){
+			snprint(errbuf, ERRMAX, "remove %s: %r", 
+				r->fid->file->dir.name);
+			r->error = errbuf;
+		}
+		r->fid->file = nil;
+	}
+}
+
+static void
+sstat(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->fid->file){
+		r->d = r->fid->file->dir;
+		if(r->d.name)
+			r->d.name = estrdup9p(r->d.name);
+		if(r->d.uid)
+			r->d.uid = estrdup9p(r->d.uid);
+		if(r->d.gid)
+			r->d.gid = estrdup9p(r->d.gid);
+		if(r->d.muid)
+			r->d.muid = estrdup9p(r->d.muid);
+	}
+	if(srv->stat)	
+		srv->stat(r);	
+	else if(r->fid->file)
+		respond(r, nil);
+	else
+		respond(r, Enostat);
+}
+static void
+rstat(Req *r, char *error)
+{
+	int n;
+	uchar *statbuf;
+	uchar tmp[BIT16SZ];
+
+	if(error)
+		return;
+	if(convD2M(&r->d, tmp, BIT16SZ) != BIT16SZ){
+		r->error = "convD2M(_,_,BIT16SZ) did not return BIT16SZ";
+		return;
+	}
+	n = GBIT16(tmp)+BIT16SZ;
+	statbuf = emalloc9p(n);
+	if(statbuf == nil){
+		r->error = "out of memory";
+		return;
+	}
+	r->ofcall.nstat = convD2M(&r->d, statbuf, n);
+	r->ofcall.stat = statbuf;	/* freed in closereq */
+	if(r->ofcall.nstat <= BIT16SZ){
+		r->error = "convD2M fails";
+		free(statbuf);
+		return;
+	}
+}
+
+static void
+swstat(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(srv->wstat == nil){
+		respond(r, Enowstat);
+		return;
+	}
+	if(convM2D(r->ifcall.stat, r->ifcall.nstat, &r->d, (char*)r->ifcall.stat) != r->ifcall.nstat){
+		respond(r, Ebaddir);
+		return;
+	}
+	if((ushort)~r->d.type){
+		respond(r, "wstat -- attempt to change type");
+		return;
+	}
+	if((uint)~r->d.dev){
+		respond(r, "wstat -- attempt to change dev");
+		return;
+	}
+	if((uchar)~r->d.qid.type || (ulong)~r->d.qid.vers || (uvlong)~r->d.qid.path){
+		respond(r, "wstat -- attempt to change qid");
+		return;
+	}
+	if(r->d.muid && r->d.muid[0]){
+		respond(r, "wstat -- attempt to change muid");
+		return;
+	}
+	if((ulong)~r->d.mode && ((r->d.mode&DMDIR)>>24) != (r->fid->qid.type&QTDIR)){
+		respond(r, "wstat -- attempt to change DMDIR bit");
+		return;
+	}
+	srv->wstat(r);
+}
+static void
+rwstat(Req *r, char *msg)
+{
+	USED(r);
+	USED(msg);
+}
+
+void
+srv(Srv *srv)
+{
+	Req *r;
+
+	fmtinstall('D', dirfmt);
+	fmtinstall('F', fcallfmt);
+
+	if(srv->fpool == nil)
+		srv->fpool = allocfidpool(srv->destroyfid);
+	if(srv->rpool == nil)
+		srv->rpool = allocreqpool(srv->destroyreq);
+	if(srv->msize == 0)
+		srv->msize = 8192+IOHDRSZ;
+
+	changemsize(srv, srv->msize);
+
+	srv->fpool->srv = srv;
+	srv->rpool->srv = srv;
+
+	while(r = getreq(srv)){
+		if(r->error){
+			respond(r, r->error);
+			continue;	
+		}
+		switch(r->ifcall.type){
+		default:
+			respond(r, "unknown message");
+			break;
+		case Tversion:	sversion(srv, r);	break;
+		case Tauth:	sauth(srv, r);	break;
+		case Tattach:	sattach(srv, r);	break;
+		case Tflush:	sflush(srv, r);	break;
+		case Twalk:	swalk(srv, r);	break;
+		case Topen:	sopen(srv, r);	break;
+		case Tcreate:	screate(srv, r);	break;
+		case Tread:	sread(srv, r);	break;
+		case Twrite:	swrite(srv, r);	break;
+		case Tclunk:	sclunk(srv, r);	break;
+		case Tremove:	sremove(srv, r);	break;
+		case Tstat:	sstat(srv, r);	break;
+		case Twstat:	swstat(srv, r);	break;
+		}
+	}
+
+	if(srv->end)
+		srv->end(srv);
+}
+
+void
+respond(Req *r, char *error)
+{
+	int i, m, n;
+	char errbuf[ERRMAX];
+	Srv *srv;
+
+	srv = r->srv;
+	assert(srv != nil);
+
+	assert(r->responded == 0);
+	r->error = error;
+
+	switch(r->ifcall.type){
+	default:
+		assert(0);
+	/*
+	 * Flush is special.  If the handler says so, we return
+	 * without further processing.  Respond will be called
+	 * again once it is safe.
+	 */
+	case Tflush:
+		if(rflush(r, error)<0)
+			return;
+		break;
+	case Tversion:	rversion(r, error);	break;
+	case Tauth:	rauth(r, error);	break;
+	case Tattach:	rattach(r, error);	break;
+	case Twalk:	rwalk(r, error);	break;
+	case Topen:	ropen(r, error);	break;
+	case Tcreate:	rcreate(r, error);	break;
+	case Tread:	rread(r, error);	break;
+	case Twrite:	rwrite(r, error);	break;
+	case Tclunk:	rclunk(r, error);	break;
+	case Tremove:	rremove(r, error, errbuf);	break;
+	case Tstat:	rstat(r, error);	break;
+	case Twstat:	rwstat(r, error);	break;
+	}
+
+	r->ofcall.tag = r->ifcall.tag;
+	r->ofcall.type = r->ifcall.type+1;
+	if(r->error)
+		setfcallerror(&r->ofcall, r->error);
+
+if(chatty9p)
+	fprint(2, "-%d-> %F\n", srv->outfd, &r->ofcall);
+
+	qlock(&srv->wlock);
+	n = convS2M(&r->ofcall, srv->wbuf, srv->msize);
+	if(n <= 0){
+		fprint(2, "n = %d %F\n", n, &r->ofcall);
+		abort();
+	}
+	assert(n > 2);
+	if(r->pool)	/* not a fake */
+		closereq(removereq(r->pool, r->ifcall.tag));
+	m = write(srv->outfd, srv->wbuf, n);
+	if(m != n)
+		sysfatal("lib9p srv: write %d returned %d on fd %d: %r", n, m, srv->outfd);
+	qunlock(&srv->wlock);
+
+	qlock(&r->lk);	/* no one will add flushes now */
+	r->responded = 1;
+	qunlock(&r->lk);
+
+	for(i=0; i<r->nflush; i++)
+		respond(r->flush[i], nil);
+	free(r->flush);
+
+	if(r->pool)
+		closereq(r);
+	else
+		free(r);
+}
+
+int
+postfd(char *name, int pfd)
+{
+	int fd;
+	char buf[80];
+
+	snprint(buf, sizeof buf, "/srv/%s", name);
+	if(chatty9p)
+		fprint(2, "postfd %s\n", buf);
+	fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600);
+	if(fd < 0){
+		if(chatty9p)
+			fprint(2, "create fails: %r\n");
+		return -1;
+	}
+	if(fprint(fd, "%d", pfd) < 0){
+		if(chatty9p)
+			fprint(2, "write fails: %r\n");
+		close(fd);
+		return -1;
+	}
+	if(chatty9p)
+		fprint(2, "postfd successful\n");
+	return 0;
+}
+
diff --git a/src/lib9p/tpost.c b/src/lib9p/tpost.c
new file mode 100644
index 0000000..68da19a
--- /dev/null
+++ b/src/lib9p/tpost.c
@@ -0,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "post.h"
+
+void
+threadpostmountsrv(Srv *s, char *name, char *mtpt, int flag)
+{
+	Postcrud *p;
+
+	p = _post1(s, name, mtpt, flag);
+	if(procrfork(_post2, s, 32*1024, RFNAMEG|RFNOTEG) < 0)
+		sysfatal("procrfork: %r");
+	_post3(p);
+}
+
diff --git a/src/lib9p/uid.c b/src/lib9p/uid.c
new file mode 100644
index 0000000..84b98bd
--- /dev/null
+++ b/src/lib9p/uid.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/*
+ * simplistic permission checking.  assume that
+ * each user is the leader of her own group.
+ */
+int
+hasperm(File *f, char *uid, int p)
+{
+	int m;
+
+	m = f->dir.mode & 7;	/* other */
+	if((p & m) == p)
+		return 1;
+
+	if(strcmp(f->dir.uid, uid) == 0) {
+		m |= (f->dir.mode>>6) & 7;
+		if((p & m) == p)
+			return 1;
+	}
+
+	if(strcmp(f->dir.gid, uid) == 0) {
+		m |= (f->dir.mode>>3) & 7;
+		if((p & m) == p)
+			return 1;
+	}
+
+	return 0;
+}
diff --git a/src/lib9p/util.c b/src/lib9p/util.c
new file mode 100644
index 0000000..17588a9
--- /dev/null
+++ b/src/lib9p/util.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include "9p.h"
+
+void
+readbuf(Req *r, void *s, long n)
+{
+	r->ofcall.count = r->ifcall.count;
+	if(r->ifcall.offset >= n){
+		r->ofcall.count = 0;
+		return;
+	}
+	if(r->ifcall.offset+r->ofcall.count > n)
+		r->ofcall.count = n - r->ifcall.offset;
+	memmove(r->ofcall.data, (char*)s+r->ifcall.offset, r->ofcall.count);
+}
+
+void
+readstr(Req *r, char *s)
+{
+	readbuf(r, s, strlen(s));
+}