Add support for user-level 9P servers/clients and various bug fixes to go with them.
diff --git a/bin/B b/bin/B
index 411bcef..fb45d3e 100755
--- a/bin/B
+++ b/bin/B
@@ -6,6 +6,12 @@
 	exit 1
 fi
 
+for i
+do
+	plumb $i
+done
+exit 0
+
 if [ "x$DISPLAY" = "x" ]
 then
 	sam="/tmp/.sam.$USER"
diff --git a/include/fcall.h b/include/fcall.h
index f85487b..893b050 100644
--- a/include/fcall.h
+++ b/include/fcall.h
@@ -40,6 +40,7 @@
 	char	*data;		/* Twrite, Rread */
 	ushort	nstat;		/* Twstat, Rstat */
 	uchar	*stat;		/* Twstat, Rstat */
+	int	unixfd;		/* Ropenfd */
 } Fcall;
 
 
@@ -100,6 +101,9 @@
 	Twstat =	126,
 	Rwstat,
 	Tmax,
+
+	Topenfd = 	98,
+	Ropenfd,
 };
 
 uint	convM2S(uchar*, uint, Fcall*);
diff --git a/include/fs.h b/include/fs.h
index 219b277..3844270 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -12,7 +12,7 @@
 typedef struct Fid Fid;
 
 Fsys *fsinit(int);
-Fsys *fsmount(int);
+Fsys *fsmount(int, char*);
 
 int fsversion(Fsys*, int, char*, int);
 Fid *fsauth(Fsys*, char*);
@@ -34,6 +34,7 @@
 int fsdirwstat(Fsys*, char*, struct Dir*);
 int fsdirfwstat(Fid*, struct Dir*);
 Fid *fsroot(Fsys*);
+Fsys *nsmount(char*, char*);
 
 #ifdef __cplusplus
 }
diff --git a/include/lib9.h b/include/lib9.h
index ce5187c..de08249 100644
--- a/include/lib9.h
+++ b/include/lib9.h
@@ -194,6 +194,7 @@
  *
 extern	void*	malloc(ulong);
  */
+extern	void*	p9malloc(ulong);
 extern	void*	mallocz(ulong, int);
 /*
 extern	void	free(void*);
@@ -208,6 +209,9 @@
 /*
 extern	void*	malloctopoolblock(void*);
 */
+#ifndef NOPLAN9DEFINES
+#define	malloc	p9malloc
+#endif
 
 /*
  * print routines (provided by <fmt.h>)
@@ -442,6 +446,7 @@
 extern	double	frexp(double, int*);
 extern	ulong	getcallerpc(void*);
 extern	char*	p9getenv(char*);
+extern	int	p9putenv(char*, char*);
 extern	int	getfields(char*, char**, int, int, char*);
 extern	int	gettokens(char *, char **, int, char *);
 extern	char*	getuser(void);
@@ -482,6 +487,7 @@
 #define	longjmp		p9longjmp
 #undef  setjmp
 #define setjmp		p9setjmp
+#define putenv		p9putenv
 #define notejmp		p9notejmp
 #define jmp_buf		p9jmp_buf
 #endif
@@ -728,14 +734,14 @@
 extern	void	_exits(char*);
 
 extern	void	abort(void);
-/* extern	int	access(char*, int); <unistd.h> */
+extern	int	p9access(char*, int);
 extern	long	p9alarm(ulong);
 extern	int	await(char*, int);
 /* extern	int	bind(char*, char*, int); give up */
 /* extern	int	brk(void*); <unistd.h> */
-/* extern	int	chdir(char*); <unistd.h> */
+extern	int	p9chdir(char*);
 extern	int	close(int);
-extern	int	create(char*, int, ulong);
+extern	int	p9create(char*, int, ulong);
 extern	int	p9dup(int, int);
 extern	int	errstr(char*, uint);
 extern	int	p9exec(char*, char*[]);
@@ -752,9 +758,9 @@
 */
 extern	int	noted(int);
 extern	int	notify(void(*)(void*, char*));
-/* extern	int	open(char*, int); <unistd.h> */
+extern	int	p9open(char*, int);
 extern	int	fd2path(int, char*, int);
-extern	int	pipe(int*);
+extern	int	p9pipe(int*);
 /* 
  * use defs from <unistd.h>
 extern	long	pread(int, void*, long, vlong);
@@ -796,6 +802,10 @@
 #define wait		p9wait
 #define waitpid		p9waitpid
 #define rfork		p9rfork
+#define access		p9access
+#define create		p9create
+#define open		p9open
+#define pipe		p9pipe
 #endif
 
 extern	Dir*	dirstat(char*);
@@ -810,6 +820,10 @@
 extern	void	rerrstr(char*, uint);
 extern	char*	sysname(void);
 extern	void	werrstr(char*, ...);
+extern	char*	getns(void);
+extern	int	sendfd(int, int);
+extern	int	recvfd(int);
+extern	int	post9pservice(int, char*);
 
 /* external names that we don't want to step on */
 #ifndef NOPLAN9DEFINES
diff --git a/include/thread.h b/include/thread.h
index e9dc96e..0eb02b5 100644
--- a/include/thread.h
+++ b/include/thread.h
@@ -79,8 +79,8 @@
 int		proccreate(void (*f)(void *arg), void *arg, unsigned int stacksize);
 int		procrfork(void (*f)(void *arg), void *arg, unsigned int stacksize, int flag);
 void**		procdata(void);
-void		procexec(Channel *, char *, char *[]);
-void		procexecl(Channel *, char *, ...);
+void		procexec(Channel *, int[3], char *, char *[]);
+void		procexecl(Channel *, int[3], char *, ...);
 int		recv(Channel *c, void *v);
 void*		recvp(Channel *c);
 unsigned long		recvul(Channel *c);
diff --git a/plumb/basic b/plumb/basic
index a065799..918cc51 100644
--- a/plumb/basic
+++ b/plumb/basic
@@ -38,7 +38,8 @@
 data matches '([a-zA-Z¡-￿0-9_\-./]+)\.(jpe?g|JPE?G|gif|GIF|tiff?|TIFF?|ppm|bit)'
 arg isfile	$0
 plumb to image
-plumb client page -wi
+plumb start qiv -t $1
+# plumb client page -wi
 
 # postscript/pdf/dvi go to page but not over the a plumb port
 # the port is here for reference but is unused
@@ -47,7 +48,8 @@
 data matches '([a-zA-Z¡-￿0-9_\-./]+)\.(ps|PS|eps|EPS|pdf|PDF|dvi|DVI)'
 arg isfile	$0
 plumb to postscript
-plumb start page -w $file
+plumb start gv $file
+# plumb start page -w $file
 
 # existing files, possibly tagged by line number, go to editor
 type is text
@@ -56,8 +58,7 @@
 data set	$file
 attr add	addr=$3
 plumb to edit
-plumb start /usr/local/plan9/bin/B $file:$3
-# plumb client window $editor
+plumb client $editor
 
 # .h files are looked up in /usr/include and passed to edit
 type is text
@@ -66,8 +67,7 @@
 data set	$file
 attr add	addr=$3
 plumb to edit
-plumb start /usr/local/plan9/bin/B $file:$3
-# plumb client window $editor
+plumb client $editor
 
 # .h files are looked up in /usr/local/include and passed to edit
 type is text
@@ -76,8 +76,7 @@
 data set	$file
 attr add	addr=$3
 plumb to edit
-plumb start /usr/local/plan9/bin/B $file:$3
-# plumb client window $editor
+plumb client $editor
 
 # .h files are looked up in /usr/local/plan9/include and passed to edit
 type is text
@@ -86,8 +85,7 @@
 data set	$file
 attr add	addr=$3
 plumb to edit
-plumb start /usr/local/plan9/bin/B $file:$3
-# plumb client window $editor
+plumb client $editor
 
 # .m files are looked up in /sys/module and passed to edit
 type is text
@@ -96,8 +94,7 @@
 data set	$file
 attr add	addr=$3
 plumb to edit
-plumb start /usr/local/plan9/bin/B $file:$3
-# plumb client window $editor
+plumb client window $editor
 
 # faces -> new mail window for message
 type	is	text
@@ -113,13 +110,14 @@
 
 # start rule for images without known suffixes
 dst is image
+arg isfile $data
 plumb to image
-plumb client page -wi
+plumb start qiv -t $data
 
 # start rule for postscript without known suffixes
 dst is postscript
 arg isfile $data
-plumb start page -w $data
+plumb start gv $data
 
 type	is	text
 data	matches	'Local (.*)'
diff --git a/src/cmd/9p.c b/src/cmd/9p.c
index 7c018c3..e948174 100644
--- a/src/cmd/9p.c
+++ b/src/cmd/9p.c
@@ -11,7 +11,9 @@
 	fprint(2, "usage: 9p [-a address] cmd args...\n");
 	fprint(2, "possible cmds:\n");
 	fprint(2, "	read name\n");
+	fprint(2, "	readfd name\n");
 	fprint(2, "	write name\n");
+	fprint(2, "	writefd name\n");
 	fprint(2, "	stat name\n");
 //	fprint(2, "	ls name\n");
 	fprint(2, "without -a, name elem/path means /path on server unix!$ns/elem\n");
@@ -20,6 +22,8 @@
 
 void xread(int, char**);
 void xwrite(int, char**);
+void xreadfd(int, char**);
+void xwritefd(int, char**);
 void xstat(int, char**);
 void xls(int, char**);
 
@@ -29,6 +33,8 @@
 } cmds[] = {
 	"read", xread,
 	"write", xwrite,
+	"readfd", xreadfd,
+	"writefd", xwritefd,
 	"stat", xstat,
 //	"ls", xls,
 };
@@ -64,7 +70,6 @@
 xparse(char *name, char **path)
 {
 	int fd;
-	char *ns;
 	char *p;
 	Fsys *fs;
 
@@ -75,22 +80,17 @@
 		else
 			*p++ = 0;
 		*path = p;
-		if(*name == 0)
-			usage();
-		ns = getenv("ns");
-		if(ns == nil)
-			sysfatal("ns not set");
-		addr = smprint("unix!%s/%s", ns, name);
-		if(addr == nil)
-			sysfatal("out of memory");
-	}else
+		fs = nsmount(name, "");
+		if(fs == nil)
+			sysfatal("mount: %r");
+	}else{
 		*path = name;
-
-	fprint(2, "dial %s...", addr);
-	if((fd = dial(addr, nil, nil, nil)) < 0)
-		sysfatal("dial: %r");
-	if((fs = fsmount(fd)) == nil)
-		sysfatal("fsmount: %r");
+		fprint(2, "dial %s...", addr);
+		if((fd = dial(addr, nil, nil, nil)) < 0)
+			sysfatal("dial: %r");
+		if((fs = fsmount(fd, "")) == nil)
+			sysfatal("fsmount: %r");
+	}
 	return fs;
 }
 
@@ -120,6 +120,15 @@
 	return fid;
 }
 
+int
+xopenfd(char *name, int mode)
+{
+	Fsys *fs;
+
+	fs = xparse(name, &name);
+	return fsopenfd(fs, name, mode);
+}
+
 void
 xread(int argc, char **argv)
 {
@@ -144,6 +153,29 @@
 }
 
 void
+xreadfd(int argc, char **argv)
+{
+	char buf[1024];
+	int n;
+	int fd;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 1)
+		usage();
+
+	fd = xopenfd(argv[0], OREAD);
+	while((n = read(fd, buf, sizeof buf)) > 0)
+		write(1, buf, n);
+	if(n < 0)
+		sysfatal("read error: %r");
+	exits(0);	
+}
+
+void
 xwrite(int argc, char **argv)
 {
 	char buf[1024];
@@ -168,6 +200,30 @@
 }
 
 void
+xwritefd(int argc, char **argv)
+{
+	char buf[1024];
+	int n;
+	int fd;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 1)
+		usage();
+
+	fd = xopenfd(argv[0], OWRITE|OTRUNC);
+	while((n = read(0, buf, sizeof buf)) > 0)
+		if(write(fd, buf, n) != n)
+			sysfatal("write error: %r");
+	if(n < 0)
+		sysfatal("read error: %r");
+	exits(0);	
+}
+
+void
 xstat(int argc, char **argv)
 {
 	Dir *d;
diff --git a/src/cmd/9pserve.c b/src/cmd/9pserve.c
index ad77236..c33beb1 100644
--- a/src/cmd/9pserve.c
+++ b/src/cmd/9pserve.c
@@ -2,6 +2,8 @@
 #include <libc.h>
 #include <fcall.h>
 #include <thread.h>
+#include <poll.h>
+#include <errno.h>
 
 enum
 {
@@ -38,6 +40,7 @@
 	int ref;
 	int ctag;
 	int tag;
+	int isopenfd;
 	Fcall tx;
 	Fcall rx;
 	Fid *fid;
@@ -52,6 +55,8 @@
 struct Conn
 {
 	int fd;
+	int fdmode;
+	Fid *fdfid;
 	int nmsg;
 	int nfid;
 	Channel *inc;
@@ -89,7 +94,7 @@
 Queue *qalloc(void);
 int sendq(Queue*, void*);
 void *recvq(Queue*);
-void selectthread(void*);
+void pollthread(void*);
 void connthread(void*);
 void connoutthread(void*);
 void listenthread(void*);
@@ -100,6 +105,10 @@
 int taccept(int, char*);
 int iolisten(Ioproc*, char*, char*);
 int ioaccept(Ioproc*, int, char*);
+int iorecvfd(Ioproc*, int);
+int iosendfd(Ioproc*, int, int);
+void mainproc(void*);
+int ignorepipe(void*, char*);
 
 void
 usage(void)
@@ -110,14 +119,13 @@
 }
 
 uchar vbuf[128];
-
+extern int _threaddebuglevel;
 void
 threadmain(int argc, char **argv)
 {
 	char *file;
-	int n;
-	Fcall f;
 
+	if(verbose) fprint(2, "9pserve running\n");
 	ARGBEGIN{
 	default:
 		usage();
@@ -142,6 +150,20 @@
 	if((afd = announce(addr, adir)) < 0)
 		sysfatal("announce %s: %r", addr);
 
+	proccreate(mainproc, nil, STACK);
+	threadexits(0);
+}
+
+void
+mainproc(void *v)
+{
+	int n;
+	Fcall f;
+	USED(v);
+
+	yield();	/* let threadmain exit */
+
+	atnotify(ignorepipe, 1);
 	fmtinstall('D', dirfmt);
 	fmtinstall('M', dirmodefmt);
 	fmtinstall('F', fcallfmt);
@@ -150,10 +172,6 @@
 	outq = qalloc();
 	inq = qalloc();
 
-//	threadcreateidle(selectthread, nil, STACK);
-	threadcreate(inputthread, nil, STACK);
-	threadcreate(outputthread, nil, STACK);
-
 	f.type = Tversion;
 	f.version = "9P2000";
 	f.msize = 8192;
@@ -165,7 +183,22 @@
 	if(convM2S(vbuf, n, &f) != n)
 		sysfatal("convM2S failure");
 	if(verbose > 1) fprint(2, "* -> %F\n", &f);
+
+	threadcreate(inputthread, nil, STACK);
+	threadcreate(outputthread, nil, STACK);
 	threadcreate(listenthread, nil, STACK);
+	threadcreateidle(pollthread, nil, STACK);
+	threadexits(0);
+}
+
+int
+ignorepipe(void *v, char *s)
+{
+	USED(v);
+	if(strcmp(s, "sys: write on closed pipe") == 0)
+		return 1;
+	fprint(2, "msg: %s\n", s);
+	return 0;
 }
 
 void
@@ -178,10 +211,6 @@
 	USED(arg);
 	for(;;){
 		c = emalloc(sizeof(Conn));
-		c->inc = chancreate(sizeof(void*), 0);
-		c->internal = chancreate(sizeof(void*), 0);
-		c->inq = qalloc();
-		c->outq = qalloc();
 		c->fd = iolisten(io, adir, c->dir);
 		if(c->fd < 0){
 			if(verbose) fprint(2, "listen: %r\n");
@@ -189,13 +218,17 @@
 			free(c);
 			return;
 		}
+		c->inc = chancreate(sizeof(void*), 0);
+		c->internal = chancreate(sizeof(void*), 0);
+		c->inq = qalloc();
+		c->outq = qalloc();
 		if(verbose) fprint(2, "incoming call on %s\n", c->dir);
 		threadcreate(connthread, c, STACK);
 	}	
 }
 
 void
-sendmsg(Msg *m)
+send9pmsg(Msg *m)
 {
 	int n, nn;
 
@@ -226,7 +259,7 @@
 	m->rx.type = Rerror;
 	m->rx.ename = ename;
 	m->rx.tag = m->tx.tag;
-	sendmsg(m);
+	send9pmsg(m);
 }
 
 void
@@ -250,7 +283,7 @@
 	c->fd = fd;
 	threadcreate(connoutthread, c, STACK);
 	while((m = mread9p(io, c->fd)) != nil){
-		if(verbose > 1) fprint(2, "%s -> %F\n", c->dir, &m->tx);
+		if(verbose > 1) fprint(2, "fd#%d -> %F\n", c->fd, &m->tx);
 		m->c = c;
 		m->ctag = m->tx.tag;
 		c->nmsg++;
@@ -267,13 +300,13 @@
 				m->rx.msize = 8192;
 			m->rx.version = "9P2000";
 			m->rx.type = Rversion;
-			sendmsg(m);
+			send9pmsg(m);
 			continue;
 		case Tflush:
 			if((m->oldm = gethash(c->tag, m->tx.oldtag)) == nil){
 				m->rx.tag = m->tx.tag;
 				m->rx.type = Rflush;
-				sendmsg(m);
+				send9pmsg(m);
 				continue;
 			}
 			m->oldm->ref++;
@@ -318,6 +351,15 @@
 			}
 			m->afid->ref++;
 			break;
+		case Topenfd:
+			if(m->tx.mode != OREAD && (m->tx.mode&~OTRUNC) != OWRITE){
+				err(m, "openfd mode must be OREAD or OWRITE");
+				continue;
+			}
+			m->isopenfd = 1;
+			m->tx.type = Topen;
+			m->tpkt[4] = Topen;
+			/* fall through */
 		case Tcreate:
 		case Topen:
 		case Tclunk:
@@ -363,6 +405,7 @@
 			m = msgnew();
 			m->internal = 1;
 			m->c = c;
+			c->nmsg++;
 			m->tx.type = Tflush;
 			m->tx.tag = m->tag;
 			m->tx.oldtag = om->tag;
@@ -371,7 +414,9 @@
 			m->ref++;	/* for outq */
 			sendomsg(m);
 			recvp(c->internal);
-			msgput(m);
+			msgput(m);	/* got from recvp */
+			msgput(m);	/* got from msgnew */
+			msgput(om);	/* got from hash table */
 		}
 	}
 
@@ -382,6 +427,7 @@
 			m = msgnew();
 			m->internal = 1;
 			m->c = c;
+			c->nmsg++;
 			m->tx.type = Tclunk;
 			m->tx.tag = m->tag;
 			m->tx.fid = f->fid;
@@ -390,7 +436,9 @@
 			m->ref++;
 			sendomsg(m);
 			recvp(c->internal);
-			msgput(m);
+			msgput(m);	/* got from recvp */
+			msgput(m);	/* got from msgnew */
+			fidput(f);	/* got from hash table */
 		}
 	}
 
@@ -398,9 +446,157 @@
 	assert(c->nmsg == 0);
 	assert(c->nfid == 0);
 	close(c->fd);
+	chanfree(c->internal);
+	c->internal = 0;
+	chanfree(c->inc);
+	c->inc = 0;
+	free(c->inq);
+	c->inq = 0;
+	free(c->outq);
+	c->outq = 0;
 	free(c);
 }
 
+static void
+openfdthread(void *v)
+{
+	Conn *c;
+	Fid *fid;
+	Msg *m;
+	int n;
+	vlong tot;
+	Ioproc *io;
+	char buf[1024];
+
+	c = v;
+	fid = c->fdfid;
+	io = ioproc();
+
+	tot = 0;
+	if(c->fdmode == OREAD){
+		for(;;){
+			if(verbose) fprint(2, "tread...");
+			m = msgnew();
+			m->internal = 1;
+			m->c = c;
+			m->tx.type = Tread;
+			m->tx.count = 8192;
+			m->tx.fid = fid->fid;
+			m->tx.tag = m->tag;
+			m->tx.offset = tot;
+			m->fid = fid;
+			fid->ref++;
+			m->ref++;
+			sendomsg(m);
+			recvp(c->internal);
+			if(m->rx.type == Rerror)
+				break;
+			if(m->rx.count == 0)
+				break;
+			tot += m->rx.count;
+			if(iowrite(io, c->fd, m->rx.data, m->rx.count) != m->rx.count)
+				break;
+			msgput(m);
+			msgput(m);
+		}
+	}else{
+		for(;;){
+			if(verbose) fprint(2, "twrite...");
+			if((n=ioread(io, c->fd, buf, sizeof buf)) <= 0){
+				m = nil;
+				break;
+			}
+			m = msgnew();
+			m->internal = 1;
+			m->c = c;
+			m->tx.type = Twrite;
+			m->tx.fid = fid->fid;
+			m->tx.data = buf;
+			m->tx.count = n;
+			m->tx.tag = m->tag;
+			m->tx.offset = tot;
+			m->fid = fid;
+			fid->ref++;
+			m->ref++;
+			sendomsg(m);
+			recvp(c->internal);
+			if(m->rx.type == Rerror)
+				break;
+			tot = n;
+			msgput(m);
+			msgput(m);
+		}
+	}
+	if(verbose) fprint(2, "eof on %d fid %d\n", c->fd, fid->fid);
+	close(c->fd);
+	closeioproc(io);
+	if(m){
+		msgput(m);
+		msgput(m);
+	}
+	m = msgnew();
+	m->internal = 1;
+	m->c = c;
+	m->tx.type = Tclunk;
+	m->tx.fid = fid->fid;
+	m->fid = fid;
+	fid->ref++;
+	m->ref++;
+	sendomsg(m);
+	recvp(c->internal);
+	msgput(m);
+	msgput(m);
+	fidput(fid);
+	c->fdfid = nil;
+	chanfree(c->internal);
+	c->internal = 0;
+	free(c);
+}			
+
+int
+xopenfd(Msg *m)
+{
+	char errs[ERRMAX];
+	int n, p[2];
+	Conn *nc;
+
+	if(pipe(p) < 0){
+		rerrstr(errs, sizeof errs);
+		err(m, errs);
+	}
+	if(verbose) fprint(2, "xopen pipe %d %d...", p[0], p[1]);
+
+	/* now we're committed. */
+
+	/* a new connection for this fid */
+	nc = emalloc(sizeof(Conn));
+	nc->internal = chancreate(sizeof(void*), 0);
+
+	/* a ref for us */
+	nc->fdfid = m->fid;
+	m->fid->ref++;
+	nc->fdmode = m->tx.mode;
+	nc->fd = p[0];
+
+	/* clunk fid from other connection */
+	if(delhash(m->c->fid, m->fid->cfid, m->fid) == 0)
+		fidput(m->fid);
+
+	/* a thread to tend the pipe */
+	threadcreate(openfdthread, nc, STACK);
+
+	/* rewrite as Ropenfd */
+	m->rx.type = Ropenfd;
+	n = GBIT32(m->rpkt);
+	m->rpkt = erealloc(m->rpkt, n+4);
+	PBIT32(m->rpkt+n, p[1]);
+	n += 4;
+	PBIT32(m->rpkt, n);
+	m->rpkt[4] = Ropenfd;
+	m->rx.unixfd = p[1];
+	return 0;
+}
+
 void
 connoutthread(void *arg)
 {
@@ -413,6 +609,9 @@
 	io = ioproc();
 	while((m = recvq(c->outq)) != nil){
 		err = m->tx.type+1 != m->rx.type;
+		if(!err && m->isopenfd)
+			if(xopenfd(m) < 0)
+				continue;
 		switch(m->tx.type){
 		case Tflush:
 			om = m->oldm;
@@ -446,7 +645,7 @@
 		}
 		if(delhash(m->c->tag, m->ctag, m) == 0)
 			msgput(m);
-		if(verbose > 1) fprint(2, "%s <- %F\n", c->dir, &m->rx);
+		if(verbose > 1) fprint(2, "fd#%d <- %F\n", c->fd, &m->rx);
 		rewritehdr(&m->rx, m->rpkt);
 		if(mwrite9p(io, c->fd, m->rpkt) < 0)
 			if(verbose) fprint(2, "write error: %r\n");
@@ -473,6 +672,8 @@
 		msgput(m);
 	}
 	closeioproc(io);
+	fprint(2, "output eof\n");
+	threadexitsall(0);
 }	
 
 void
@@ -483,6 +684,7 @@
 	Msg *m;
 	Ioproc *io;
 
+	if(verbose) fprint(2, "input thread\n");
 	io = ioproc();
 	USED(arg);
 	while((pkt = read9ppkt(io, 0)) != nil){
@@ -514,6 +716,8 @@
 			sendq(m->c->outq, m);
 	}
 	closeioproc(io);
+	fprint(2, "input eof\n");
+	threadexitsall(0);
 }
 
 void*
@@ -626,15 +830,20 @@
 	m->c->nmsg--;
 	m->c = nil;
 	fidput(m->fid);
-	fidput(m->afid);
-	fidput(m->newfid);
-	free(m->tpkt);
-	free(m->rpkt);
 	m->fid = nil;
+	fidput(m->afid);
 	m->afid = nil;
+	fidput(m->newfid);
 	m->newfid = nil;
+	free(m->tpkt);
 	m->tpkt = nil;
+	free(m->rpkt);
 	m->rpkt = nil;
+	if(m->rx.type == Ropenfd)
+		close(m->rx.unixfd);
+	m->rx.unixfd = -1;
+	m->isopenfd = 0;
+	m->internal = 0;
 	m->next = freemsg;
 	freemsg = m;
 }
@@ -649,6 +858,7 @@
 	m = msgtab[n];
 	if(m->ref == 0)
 		return nil;
+	if(verbose) fprint(2, "msgget %d = %p\n", n, m);
 	m->ref++;
 	return m;
 }
@@ -768,6 +978,12 @@
 		free(pkt);
 		return nil;
 	}
+/* would do this if we ever got one of these, but we only generate them
+	if(pkt[4] == Ropenfd){
+		newfd = iorecvfd(io, fd);
+		PBIT32(pkt+n-4, newfd);
+	}
+*/
 	return pkt;
 }
 
@@ -795,7 +1011,7 @@
 int
 mwrite9p(Ioproc *io, int fd, uchar *pkt)
 {
-	int n;
+	int n, nfd;
 
 	n = GBIT32(pkt);
 	if(verbose > 2) fprint(2, "write %d %d %.*H\n", fd, n, n, pkt);
@@ -803,6 +1019,13 @@
 		fprint(2, "write error: %r\n");
 		return -1;
 	}
+	if(pkt[4] == Ropenfd){
+		nfd = GBIT32(pkt+n-4);
+		if(iosendfd(io, fd, nfd) < 0){
+			fprint(2, "send fd error: %r\n");
+			return -1;
+		}
+	}
 	return 0;
 }
 
@@ -871,42 +1094,212 @@
 
 #ifdef _LIB9_H_
 /* unix select-based polling */
+struct Ioproc
+{
+	Channel *c;
+	Ioproc *next;
+	int index;
+};
+
+static struct Ioproc **pio;
+static struct pollfd *pfd;
+static int npfd;
+static struct Ioproc *iofree;
+
 Ioproc*
 ioproc(void)
 {
-	return nil;
+	Ioproc *io;
+
+	if(iofree == nil){
+		pfd = erealloc(pfd, (npfd+1)*sizeof(pfd[0]));
+		pfd[npfd].events = 0;
+		pfd[npfd].fd = -1;
+		iofree = emalloc(sizeof(Ioproc));
+		iofree->index = npfd;
+		iofree->c = chancreate(sizeof(ulong), 1);
+		pio = erealloc(pio, (npfd+1)*sizeof(pio[0]));
+		pio[npfd] = iofree;
+		npfd++;
+	}
+	io = iofree;
+	iofree = io->next;
+	return io;
+}
+
+void
+closeioproc(Ioproc *io)
+{
+	io->next = iofree;
+	iofree = io;
+}
+
+void
+pollthread(void *v)
+{
+	int i, n;
+
+	for(;;){
+		yield();
+		for(i=0; i<npfd; i++)
+			pfd[i].revents = 0;
+		if(verbose){
+			fprint(2, "poll:");
+			for(i=0; i<npfd; i++)
+				if(pfd[i].events)
+					fprint(2, " %d%c", pfd[i].fd, pfd[i].events==POLLIN ? 'r' : pfd[i].events==POLLOUT ? 'w' : '?');
+			fprint(2, "\n");
+		}
+		n = poll(pfd, npfd, -1);
+		if(n <= 0)
+			continue;
+		for(i=0; i<npfd; i++)
+			if(pfd[i].fd != -1 && pfd[i].revents){
+				pfd[i].fd = -1;
+				pfd[i].events = 0;
+				pfd[i].revents = 0;
+				nbsendul(pio[i]->c, 1);
+			}
+	}	
+}
+
+static void
+noblock(int fd)
+{
+	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0)|O_NONBLOCK);
+}
+
+static void
+xwait(Ioproc *io, int fd, int e)
+{
+	if(verbose) fprint(2, "wait for %d%c\n", fd, e==POLLIN ? 'r' : 'w');
+	pfd[io->index].fd = fd;
+	pfd[io->index].events = e;
+	recvul(io->c);
+	if(verbose) fprint(2, "got %d\n", fd);
+}
+
+static void
+rwait(Ioproc *io, int fd)
+{
+	xwait(io, fd, POLLIN);
+}
+
+static void
+wwait(Ioproc *io, int fd)
+{
+	xwait(io, fd, POLLOUT);
 }
 
 long
 ioread(Ioproc *io, int fd, void *v, long n)
 {
+	long r;
 	USED(io);
 
-	xxx;
+	noblock(fd);
+	while((r=read(fd, v, n)) < 0 && errno == EWOULDBLOCK)
+		rwait(io, fd);
+	return r;
+}
+
+long
+ioreadn(Ioproc *io, int fd, void *v, long n)
+{
+	long tot, m;
+	uchar *u;
+
+	u = v;
+	for(tot=0; tot<n; tot+=m){
+		m = ioread(io, fd, u+tot, n-tot);
+		if(m <= 0){
+			if(tot)
+				break;
+			return m;
+		}
+	}
+	return tot;
+}
+
+int
+iorecvfd(Ioproc *io, int fd)
+{
+	int r;
+
+	noblock(fd);
+	while((r=recvfd(fd)) < 0 && errno == EWOULDBLOCK)
+		rwait(io, fd);
+	return r;
+}
+
+int
+iosendfd(Ioproc *io, int s, int fd)
+{
+	int r;
+
+	noblock(s);
+	while((r=sendfd(s, fd)) < 0 && errno == EWOULDBLOCK)
+		wwait(io, s);
+if(r < 0) fprint(2, "sent %d, %d\n", s, fd);
+	return r;
+}
+
+static long
+_iowrite(Ioproc *io, int fd, void *v, long n)
+{
+	long r;
+	USED(io);
+
+	noblock(fd);
+	while((r=write(fd, v, n)) < 0 && errno == EWOULDBLOCK)
+		wwait(io, fd);
+	return r;
 }
 
 long
 iowrite(Ioproc *io, int fd, void *v, long n)
 {
-	USED(io);
+	long tot, m;
+	uchar *u;
 
-	xxx;
+	u = v;
+	for(tot=0; tot<n; tot+=m){
+		m = _iowrite(io, fd, u+tot, n-tot);
+		if(m <= 0){
+			if(tot)
+				break;
+			return m;
+		}
+	}
+	return tot;
 }
 
 int
-iolisten(Ioproc *io, char *a, char *b)
+iolisten(Ioproc *io, char *dir, char *ndir)
 {
+	int fd;
+	int r;
+	extern int _p9netfd(char*);
 	USED(io);
 
-	xxx;
+	if((fd = _p9netfd(dir)) < 0)
+		return -1;
+	noblock(fd);
+	while((r=listen(dir, ndir)) < 0 && errno == EWOULDBLOCK)
+		rwait(io, fd);
+	return r;
 }
 
 int
 ioaccept(Ioproc *io, int fd, char *dir)
 {
+	int r;
 	USED(io);
 
-	xxx;
+	noblock(fd);
+	while((r=accept(fd, dir)) < 0 && errno == EWOULDBLOCK)
+		rwait(io, fd);
+	return r;
 }
 
 #else
diff --git a/src/cmd/9term/9term.c b/src/cmd/9term/9term.c
index ef51d86..cfee001 100644
--- a/src/cmd/9term/9term.c
+++ b/src/cmd/9term/9term.c
@@ -1212,7 +1212,6 @@
 		argv[1] = "-i";
 		argv[2] = 0;
 	}
-
 	/*
 	 * fd0 is slave (tty), fd1 is master (pty)
 	 */
@@ -1222,7 +1221,7 @@
 
         switch(pid = fork()) {
 	case 0:
-		putenv("TERM=9term");
+		putenv("TERM", "9term");
 		close(fd[1]);
 		setsid();
 	//	tcsetpgrp(0, pid);
@@ -1238,6 +1237,7 @@
 		dup(sfd, 2);
 		system("stty tabs -onlcr -echo");
 		execvp(argv[0], argv);
+		fprint(2, "exec %s failed: %r\n", argv[0]);
 		_exits("oops");
 		break;
 	case -1:
@@ -1388,9 +1388,7 @@
 void
 plumbstart(void)
 {
-	char buf[256];
-	snprint(buf, sizeof buf,  "%s/mnt/plumb", getenv("HOME"));
-	if((plumbfd = plumbopen(buf, OWRITE)) < 0)
+	if((plumbfd = plumbopen("send", OWRITE)) < 0)
 		fatal("plumbopen");
 }
 
diff --git a/src/cmd/9term/mkfile b/src/cmd/9term/mkfile
index d7d4a6d..2706dda 100644
--- a/src/cmd/9term/mkfile
+++ b/src/cmd/9term/mkfile
@@ -9,5 +9,5 @@
 
 <$PLAN9/src/mkone
 
-LDFLAGS=-lframe -ldraw -lplumb -lthread -l9 -lfmt -lutf -L$X11/lib -lX11 -lutil
+LDFLAGS=-lframe -ldraw -lplumb -lfs -lmux -lthread -l9 -lfmt -lutf -L$X11/lib -lX11 -lutil
 
diff --git a/src/cmd/dc.c b/src/cmd/dc.c
index 21967de..daff401 100644
--- a/src/cmd/dc.c
+++ b/src/cmd/dc.c
@@ -21,6 +21,7 @@
 #define NE 3
 #define length(p)	((p)->wt-(p)->beg)
 #define rewind(p)	(p)->rd=(p)->beg
+#undef create
 #define create(p)	(p)->rd = (p)->wt = (p)->beg
 #define fsfile(p)	(p)->rd = (p)->wt
 #define truncate(p)	(p)->wt = (p)->rd
diff --git a/src/cmd/dict/dict.c b/src/cmd/dict/dict.c
index dccd303..8cbf6aa 100644
--- a/src/cmd/dict/dict.c
+++ b/src/cmd/dict/dict.c
@@ -59,18 +59,13 @@
 main(int argc, char **argv)
 {
 	int i, cmd, kflag;
-	char *line, *p, *root;
+	char *line, *p;
 
 	Binit(&binbuf, 0, OREAD);
 	Binit(&boutbuf, 1, OWRITE);
 	kflag = 0;
 	line = 0;
 	dict = 0;
-	root = getenv("PLAN9");
-	if(root == nil)
-		root = "/usr/local/plan9";
-	if(chdir(root) < 0)
-		sysfatal("chdir %s: %r", root);
 
 	for(i=0; dicts[i].name; i++){
 		if(access(dicts[i].path, 0)>=0 && access(dicts[i].indexpath, 0)>=0){
@@ -126,12 +121,12 @@
 	}
 	bdict = Bopen(dict->path, OREAD);
 	if(!bdict) {
-		err("can't open dictionary %s/%s", root, dict->path);
+		err("can't open dictionary %s", dict->path);
 		exits("nodict");
 	}
 	bindex = Bopen(dict->indexpath, OREAD);
 	if(!bindex) {
-		err("can't open index %s/%s", root, dict->indexpath);
+		err("can't open index %s", dict->indexpath);
 		exits("noindex");
 	}
 	indextop = Bseek(bindex, 0L, 2);
diff --git a/src/cmd/dict/utils.c b/src/cmd/dict/utils.c
index 8e4db9e..6916d54 100644
--- a/src/cmd/dict/utils.c
+++ b/src/cmd/dict/utils.c
@@ -5,160 +5,160 @@
 
 Dict dicts[] = {
 	{"oed",		"Oxford English Dictionary, 2nd Ed.",
-	 "dict/oed2",	"dict/oed2index",
+	 "#9/dict/oed2",	"#9/dict/oed2index",
 	 oednextoff,	oedprintentry,		oedprintkey},
 	{"ahd",		"American Heritage Dictionary, 2nd College Ed.",
 	 "ahd/DICT.DB",	"ahd/index",
 	 ahdnextoff,	ahdprintentry,		ahdprintkey},
 	{"pgw",		"Project Gutenberg Webster Dictionary",
-	 "dict/pgw",	"dict/pgwindex",
+	 "#9/dict/pgw",	"#9/dict/pgwindex",
 	 pgwnextoff,	pgwprintentry,		pgwprintkey},
 	{"thesaurus",	"Collins Thesaurus",
-	 "dict/thesaurus",	"dict/thesindex",
+	 "#9/dict/thesaurus",	"#9/dict/thesindex",
 	 thesnextoff,	thesprintentry,	thesprintkey},
 
 	{"ce",		"Gendai Chinese->English",
-	 "dict/world/sansdata/sandic24.dat",
-	 "dict/world/sansdata/ceindex",
+	 "#9/dict/world/sansdata/sandic24.dat",
+	 "#9/dict/world/sansdata/ceindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"ceh",		"Gendai Chinese->English (Hanzi index)",
-	 "dict/world/sansdata/sandic24.dat",
-	 "dict/world/sansdata/cehindex",
+	 "#9/dict/world/sansdata/sandic24.dat",
+	 "#9/dict/world/sansdata/cehindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"ec",		"Gendai English->Chinese",
-	 "dict/world/sansdata/sandic24.dat",
-	 "dict/world/sansdata/ecindex",
+	 "#9/dict/world/sansdata/sandic24.dat",
+	 "#9/dict/world/sansdata/ecindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"dae",		"Gyldendal Danish->English",
-	 "dict/world/gylddata/sandic30.dat",
-	 "dict/world/gylddata/daeindex",
+	 "#9/dict/world/gylddata/sandic30.dat",
+	 "#9/dict/world/gylddata/daeindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"eda",		"Gyldendal English->Danish",
-	 "dict/world/gylddata/sandic29.dat",
-	 "dict/world/gylddata/edaindex",
+	 "#9/dict/world/gylddata/sandic29.dat",
+	 "#9/dict/world/gylddata/edaindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"due",		"Wolters-Noordhoff Dutch->English",
-	 "dict/world/woltdata/sandic07.dat",
-	 "dict/world/woltdata/deindex",
+	 "#9/dict/world/woltdata/sandic07.dat",
+	 "#9/dict/world/woltdata/deindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"edu",		"Wolters-Noordhoff English->Dutch",
-	 "dict/world/woltdata/sandic06.dat",
-	 "dict/world/woltdata/edindex",
+	 "#9/dict/world/woltdata/sandic06.dat",
+	 "#9/dict/world/woltdata/edindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"fie",		"WSOY Finnish->English",
-	 "dict/world/werndata/sandic32.dat",
-	 "dict/world/werndata/fieindex",
+	 "#9/dict/world/werndata/sandic32.dat",
+	 "#9/dict/world/werndata/fieindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"efi",		"WSOY English->Finnish",
-	 "dict/world/werndata/sandic31.dat",
-	 "dict/world/werndata/efiindex",
+	 "#9/dict/world/werndata/sandic31.dat",
+	 "#9/dict/world/werndata/efiindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"fe",		"Collins French->English",
-	 "dict/fe",	"dict/feindex",
+	 "#9/dict/fe",	"#9/dict/feindex",
 	 pcollnextoff,	pcollprintentry,	pcollprintkey},
 	{"ef",		"Collins English->French",
-	 "dict/ef",	"dict/efindex",
+	 "#9/dict/ef",	"#9/dict/efindex",
 	 pcollnextoff,	pcollprintentry,	pcollprintkey},
 
 	{"ge",		"Collins German->English",
-	 "dict/ge",	"dict/geindex",
+	 "#9/dict/ge",	"#9/dict/geindex",
 	 pcollgnextoff,	pcollgprintentry,	pcollgprintkey},
 	{"eg",		"Collins English->German",
-	 "dict/eg",	"dict/egindex",
+	 "#9/dict/eg",	"#9/dict/egindex",
 	 pcollgnextoff,	pcollgprintentry,	pcollgprintkey},
 
 	{"ie",		"Collins Italian->English",
-	 "dict/ie",	"dict/ieindex",
+	 "#9/dict/ie",	"#9/dict/ieindex",
 	 pcollnextoff,	pcollprintentry,	pcollprintkey},
 	{"ei",		"Collins English->Italian",
-	 "dict/ei",	"dict/eiindex",
+	 "#9/dict/ei",	"#9/dict/eiindex",
 	 pcollnextoff,	pcollprintentry,	pcollprintkey},
 
 	{"je",		"Sanshusha Japanese->English",
-	 "dict/world/sansdata/sandic18.dat",
-	 "dict/world/sansdata/jeindex",
+	 "#9/dict/world/sansdata/sandic18.dat",
+	 "#9/dict/world/sansdata/jeindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"jek",		"Sanshusha Japanese->English (Kanji index)",
-	 "dict/world/sansdata/sandic18.dat",
-	 "dict/world/sansdata/jekindex",
+	 "#9/dict/world/sansdata/sandic18.dat",
+	 "#9/dict/world/sansdata/jekindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"ej",		"Sanshusha English->Japanese",
-	 "dict/world/sansdata/sandic18.dat",
-	 "dict/world/sansdata/ejindex",
+	 "#9/dict/world/sansdata/sandic18.dat",
+	 "#9/dict/world/sansdata/ejindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"tjeg",	"Sanshusha technical Japanese->English,German",
-	 "dict/world/sansdata/sandic16.dat",
-	 "dict/world/sansdata/tjegindex",
+	 "#9/dict/world/sansdata/sandic16.dat",
+	 "#9/dict/world/sansdata/tjegindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"tjegk",	"Sanshusha technical Japanese->English,German (Kanji index)",
-	 "dict/world/sansdata/sandic16.dat",
-	 "dict/world/sansdata/tjegkindex",
+	 "#9/dict/world/sansdata/sandic16.dat",
+	 "#9/dict/world/sansdata/tjegkindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"tegj",	"Sanshusha technical English->German,Japanese",
-	 "dict/world/sansdata/sandic16.dat",
-	 "dict/world/sansdata/tegjindex",
+	 "#9/dict/world/sansdata/sandic16.dat",
+	 "#9/dict/world/sansdata/tegjindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"tgje",	"Sanshusha technical German->Japanese,English",
-	 "dict/world/sansdata/sandic16.dat",
-	 "dict/world/sansdata/tgjeindex",
+	 "#9/dict/world/sansdata/sandic16.dat",
+	 "#9/dict/world/sansdata/tgjeindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"ne",		"Kunnskapforlaget Norwegian->English",
-	 "dict/world/kunndata/sandic28.dat",
-	 "dict/world/kunndata/neindex",
+	 "#9/dict/world/kunndata/sandic28.dat",
+	 "#9/dict/world/kunndata/neindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"en",		"Kunnskapforlaget English->Norwegian",
-	 "dict/world/kunndata/sandic27.dat",
-	 "dict/world/kunndata/enindex",
+	 "#9/dict/world/kunndata/sandic27.dat",
+	 "#9/dict/world/kunndata/enindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"re",		"Leon Ungier Russian->English",
-	 "dict/re",	"dict/reindex",
+	 "#9/dict/re",	"#9/dict/reindex",
 	 simplenextoff,	simpleprintentry,	simpleprintkey},
 	{"er",		"Leon Ungier English->Russian",
-	 "dict/re",	"dict/erindex",
+	 "#9/dict/re",	"#9/dict/erindex",
 	 simplenextoff,	simpleprintentry,	simpleprintkey},
 
 	{"se",		"Collins Spanish->English",
-	 "dict/se",	"dict/seindex",
+	 "#9/dict/se",	"#9/dict/seindex",
 	 pcollnextoff,	pcollprintentry,	pcollprintkey},
 	{"es",		"Collins English->Spanish",
-	 "dict/es",	"dict/esindex",
+	 "#9/dict/es",	"#9/dict/esindex",
 	 pcollnextoff,	pcollprintentry,	pcollprintkey},
 
 	{"swe",		"Esselte Studium Swedish->English",
-	 "dict/world/essedata/sandic34.dat",
-	 "dict/world/essedata/sweindex",
+	 "#9/dict/world/essedata/sandic34.dat",
+	 "#9/dict/world/essedata/sweindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 	{"esw",		"Esselte Studium English->Swedish",
-	 "dict/world/essedata/sandic33.dat",
-	 "dict/world/essedata/eswindex",
+	 "#9/dict/world/essedata/sandic33.dat",
+	 "#9/dict/world/essedata/eswindex",
 	 worldnextoff,	worldprintentry,	worldprintkey},
 
 	{"movie",	"Movies -- by title",
-	 "movie/data",	"dict/movtindex",
+	 "movie/data",	"#9/dict/movtindex",
 	 movienextoff,	movieprintentry,	movieprintkey},
 	{"moviea",	"Movies -- by actor",
-	 "movie/data",	"dict/movaindex",
+	 "movie/data",	"#9/dict/movaindex",
 	 movienextoff,	movieprintentry,	movieprintkey},
 	{"movied",	"Movies -- by director",
-	 "movie/data",	"dict/movdindex",
+	 "movie/data",	"#9/dict/movdindex",
 	 movienextoff,	movieprintentry,	movieprintkey},
 
 	{"slang",	"English Slang",
-	 "dict/slang",	"dict/slangindex",
+	 "#9/dict/slang",	"#9/dict/slangindex",
 	 slangnextoff,	slangprintentry,	slangprintkey},
 
 	{"robert",	"Robert Électronique",
-	 "dict/robert/_pointers",	"dict/robert/_index",
+	 "#9/dict/robert/_pointers",	"#9/dict/robert/_index",
 	 robertnextoff,	robertindexentry,	robertprintkey},
 	{"robertv",	"Robert Électronique - formes des verbes",
-	 "dict/robert/flex.rob",	"dict/robert/_flexindex",
+	 "#9/dict/robert/flex.rob",	"#9/dict/robert/_flexindex",
 	 robertnextflex,	robertflexentry,	robertprintkey},
 
 	{0, 0, 0, 0, 0}
diff --git a/src/cmd/mkfile b/src/cmd/mkfile
index 8450d94..e2131e7 100644
--- a/src/cmd/mkfile
+++ b/src/cmd/mkfile
@@ -2,11 +2,11 @@
 <$PLAN9/src/mkhdr
 
 TARG=`ls *.c | sed 's/\.c//'`
-LDFLAGS=$LDFLAGS -lthread -lsec -lfs -lmux -lregexp9 -l9 -lbio -lfmt -lutf
+LDFLAGS=$LDFLAGS -lthread -lsec -lfs -lmux -lregexp9 -lbio -l9 -lfmt -lutf
 
 <$PLAN9/src/mkmany
 
-BUGGERED='CVS|oplumb|plumb|plumb2|mk|vac|9term|venti|htmlfmt'
+BUGGERED='CVS|oplumb|plumb2|mk|vac|9term|venti|htmlfmt'
 DIRS=`ls -l |sed -n 's/^d.* //p' |egrep -v "$BUGGERED"`
 
 <$PLAN9/src/mkdirs
diff --git a/src/cmd/plumb/fsys.c b/src/cmd/plumb/fsys.c
index 6f95a23..912e5ca 100644
--- a/src/cmd/plumb/fsys.c
+++ b/src/cmd/plumb/fsys.c
@@ -3,14 +3,13 @@
 #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
+	Stack = 32*1024
 };
 
 typedef struct Dirtab Dirtab;
@@ -73,13 +72,12 @@
 
 struct	/* needed because incref() doesn't return value */
 {
-	Lock;
-	int			ref;
+	Lock	lk;
+	int	ref;
 } rulesref;
 
 enum
 {
-	DEBUG	= 0,
 	NDIR	= 50,
 	Nhash	= 16,
 
@@ -99,13 +97,10 @@
 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*);
@@ -183,54 +178,35 @@
 static ulong
 getclock(void)
 {
-	char buf[32];
-
-	seek(clockfd, 0, 0);
-	read(clockfd, buf, sizeof buf);
-	return atoi(buf);
+	return time(0);
 }
 
 void
 startfsys(void)
 {
-	int p[2], fd;
+	int p[2];
 
 	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");
+	if(post9pservice(p[1], "plumb") < 0)
+		sysfatal("post9pservice plumb: %r");
 	close(p[1]);
+	proccreate(fsysproc, nil, Stack);
 }
 
 static void
-fsysproc(void*)
+fsysproc(void *v)
 {
 	int n;
 	Fcall *t;
 	Fid *f;
 	uchar *buf;
 
-	close(srvclosefd);
-	srvclosefd = -1;
+	USED(v);
 	t = nil;
 	for(;;){
 		buf = malloc(messagesize);	/* avoid memset of emalloc */
@@ -250,7 +226,7 @@
 			t = emalloc(sizeof(Fcall));
 		if(convM2S(buf, n, t) != n)
 			error("convert error in convM2S");
-		if(DEBUG)
+		if(debug)
 			fprint(2, "<= %F\n", t);
 		if(fcall[t->type] == nil)
 			fsysrespond(t, buf, Ebadfcall);
@@ -281,7 +257,7 @@
 		error("convert error in convS2M");
 	if(write(srvfd, buf, n) != n)
 		error("write error in respond");
-	if(DEBUG)
+	if(debug)
 		fprint(2, "=> %F\n", t);
 	free(buf);
 }
@@ -555,8 +531,10 @@
 }
 
 static Fcall*
-fsysversion(Fcall *t, uchar *buf, Fid*)
+fsysversion(Fcall *t, uchar *buf, Fid *fid)
 {
+	USED(fid);
+
 	if(t->msize < 256){
 		fsysrespond(t, buf, "version: message size too small");
 		return t;
@@ -574,8 +552,9 @@
 }
 
 static Fcall*
-fsysauth(Fcall *t, uchar *buf, Fid*)
+fsysauth(Fcall *t, uchar *buf, Fid *fid)
 {
+	USED(fid);
 	fsysrespond(t, buf, "plumber: authentication not required");
 	return t;
 }
@@ -605,10 +584,11 @@
 }
 
 static Fcall*
-fsysflush(Fcall *t, uchar *buf, Fid*)
+fsysflush(Fcall *t, uchar *buf, Fid *fid)
 {
 	int i;
 
+	USED(fid);
 	qlock(&queue);
 	for(i=NQID; i<ndir; i++)
 		flushqueue(&dir[i], t->oldtag);
@@ -729,14 +709,14 @@
 	if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
 		goto Deny;
 	if(f->qid.path==Qrules && (mode==OWRITE || mode==ORDWR)){
-		lock(&rulesref);
+		lock(&rulesref.lk);
 		if(rulesref.ref++ != 0){
 			rulesref.ref--;
-			unlock(&rulesref);
+			unlock(&rulesref.lk);
 			fsysrespond(t, buf, Einuse);
 			return t;
 		}
-		unlock(&rulesref);
+		unlock(&rulesref.lk);
 	}
 	if(clearrules){
 		writerules(nil, 0);
@@ -761,8 +741,9 @@
 }
 
 static Fcall*
-fsyscreate(Fcall *t, uchar *buf, Fid*)
+fsyscreate(Fcall *t, uchar *buf, Fid *fid)
 {
+	USED(fid);
 	fsysrespond(t, buf, Eperm);
 	return t;
 }
@@ -916,15 +897,17 @@
 }
 
 static Fcall*
-fsyswstat(Fcall *t, uchar *buf, Fid*)
+fsyswstat(Fcall *t, uchar *buf, Fid *fid)
 {
+	USED(fid);
 	fsysrespond(t, buf, Eperm);
 	return t;
 }
 
 static Fcall*
-fsysremove(Fcall *t, uchar *buf, Fid*)
+fsysremove(Fcall *t, uchar *buf, Fid *fid)
 {
+	USED(fid);
 	fsysrespond(t, buf, Eperm);
 	return t;
 }
@@ -945,9 +928,9 @@
 			 * unless last write ended with a blank line
 			 */
 			writerules(nil, 0);
-			lock(&rulesref);
+			lock(&rulesref.lk);
 			rulesref.ref--;
-			unlock(&rulesref);
+			unlock(&rulesref.lk);
 		}
 		prev = nil;
 		for(p=d->fopen; p; p=p->nextopen){
diff --git a/src/cmd/plumb/match.c b/src/cmd/plumb/match.c
index 42a9232..dc1abbb 100644
--- a/src/cmd/plumb/match.c
+++ b/src/cmd/plumb/match.c
@@ -6,6 +6,7 @@
 #include <plumb.h>
 #include "plumber.h"
 
+/*
 static char*
 nonnil(char *s)
 {
@@ -13,6 +14,7 @@
 		return "";
 	return s;
 }
+*/
 
 int
 verbis(int obj, Plumbmsg *m, Rule *r)
@@ -44,10 +46,10 @@
 		free(match[i]);
 		match[i] = nil;
 	}
-	for(i=0; i<10 && rs[i].sp!=nil; i++){
-		n = rs[i].ep-rs[i].sp;
+	for(i=0; i<10 && rs[i].s.sp!=nil; i++){
+		n = rs[i].e.ep-rs[i].s.sp;
 		match[i] = emalloc(n+1);
-		memmove(match[i], rs[i].sp, n);
+		memmove(match[i], rs[i].s.sp, n);
 		match[i][n] = '\0';
 	}
 }
@@ -66,7 +68,7 @@
 	for(i=0; i<=click; i++){
 		memset(rs, 0, 10*sizeof(Resub));
 		if(regexec(re, text+i, rs, 10))
-			if(rs[0].sp<=clickp && clickp<=rs[0].ep)
+			if(rs[0].s.sp<=clickp && clickp<=rs[0].e.ep)
 				return 1;
 	}
 	return 0;
@@ -94,8 +96,8 @@
 		}
 		if(!clickmatch(r->regex, m->data, rs, atoi(clickval)))
 			break;
-		p0 = rs[0].sp - m->data;
-		p1 = rs[0].ep - m->data;
+		p0 = rs[0].s.sp - m->data;
+		p1 = rs[0].e.ep - m->data;
 		if(e->p0 >=0 && !(p0==e->p0 && p1==e->p1))
 			break;
 		e->clearclick = 1;
@@ -120,7 +122,7 @@
 		/* must match full text */
 		if(ntext < 0)
 			ntext = strlen(alltext);
-		if(!regexec(r->regex, alltext, rs, 10) || rs[0].sp!=alltext || rs[0].ep!=alltext+ntext)
+		if(!regexec(r->regex, alltext, rs, 10) || rs[0].s.sp!=alltext || rs[0].e.ep!=alltext+ntext)
 			break;
 		setvar(rs, e->match);
 		return 1;
@@ -389,7 +391,7 @@
 {
 	NARGS		= 100,
 	NARGCHAR	= 8*1024,
-	EXECSTACK 	= 4096+(NARGS+1)*sizeof(char*)+NARGCHAR
+	EXECSTACK 	= 32*1024+(NARGS+1)*sizeof(char*)+NARGCHAR
 };
 
 /* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
@@ -419,19 +421,17 @@
 void
 execproc(void *v)
 {
+	int fd[3];
 	char **av;
-	char buf[1024], *args[NARGS+1], argc[NARGCHAR];
+	char *args[NARGS+1], argc[NARGCHAR];
 
-	rfork(RFFDG);
-	close(0);
-	open("/dev/null", OREAD);
+	fd[0] = open("/dev/null", OREAD);
+	fd[1] = dup(1, -1);
+	fd[2] = dup(2, -1);
 	av = v;
 	stackargv(av, args, argc);
 	free(av);
-	procexec(nil, args[0], args);
-	if(args[0][0]!='/' && strncmp(args[0], "./", 2)!=0 && strncmp(args[0], "../", 3)!=0)
-		snprint(buf, sizeof buf, "/bin/%s", args[0]);
-	procexec(nil, buf, args);
+	procexec(nil, fd, args[0], args);
 	threadexits("can't exec");
 }
 
diff --git a/src/cmd/plumb/mkfile b/src/cmd/plumb/mkfile
index d6a1465..6550387 100644
--- a/src/cmd/plumb/mkfile
+++ b/src/cmd/plumb/mkfile
@@ -1,10 +1,9 @@
-</$objtype/mkfile
+PLAN9=../../..
+<$PLAN9/src/mkhdr
 
 TARG=plumber plumb
 
-
-BIN=/$objtype/bin
-</sys/src/cmd/mkmany
+<$PLAN9/src/mkmany
 
 PLUMBER=plumber.$O fsys.$O match.$O rules.$O
 PLUMB=plumb.$O
@@ -15,6 +14,4 @@
 $O.plumb:	$PLUMB
 $O.plumber:	$PLUMBER
 
-syms:V:
-	8c -a plumber.c	>syms
-	8c -aa fsys.c match.c rules.c >>syms
+LDFLAGS=$LDFLAGS -lplumb -lfs -lmux -lthread -lregexp9 -l9 -lbio -lfmt -lutf
diff --git a/src/cmd/plumb/plumber.c b/src/cmd/plumb/plumber.c
index d0bd9c1..424469f 100644
--- a/src/cmd/plumb/plumber.c
+++ b/src/cmd/plumb/plumber.c
@@ -3,10 +3,10 @@
 #include <regexp.h>
 #include <thread.h>
 #include <plumb.h>
-#include <auth.h>
 #include <fcall.h>
 #include "plumber.h"
 
+int debug;
 char	*plumbfile;
 char *user;
 char *home;
@@ -47,13 +47,18 @@
 	progname = "plumber";
 
 	ARGBEGIN{
+	case 'd':
+		debug = 1;
+		break;
 	case 'p':
 		plumbfile = ARGF();
 		break;
 	}ARGEND
 
-	user = getenv("user");
+	user = getuser();
 	home = getenv("home");
+	if(home == nil)
+		home = getenv("HOME");
 	if(user==nil || home==nil)
 		error("can't initialize $user or $home: %r");
 	if(plumbfile == nil){
diff --git a/src/cmd/plumb/plumber.h b/src/cmd/plumb/plumber.h
index 0d1205f..4b9267a 100644
--- a/src/cmd/plumb/plumber.h
+++ b/src/cmd/plumb/plumber.h
@@ -91,3 +91,4 @@
 char		*lasterror;
 char		**ports;
 int		nports;
+int		debug;
diff --git a/src/cmd/plumb/rules.c b/src/cmd/plumb/rules.c
index 262f6d6..b51bb61 100644
--- a/src/cmd/plumb/rules.c
+++ b/src/cmd/plumb/rules.c
@@ -143,7 +143,7 @@
 getline(void)
 {
 	static int n = 0;
-	static char *s, *incl;
+	static char *s /*, *incl*/;
 	int c, i;
 
 	i = 0;
@@ -414,7 +414,7 @@
 	t = args[1];
 	fd = open(t, OREAD);
 	if(fd<0 && t[0]!='/' && strncmp(t, "./", 2)!=0 && strncmp(t, "../", 3)!=0){
-		snprint(buf, sizeof buf, "/sys/lib/plumb/%s", t);
+		snprint(buf, sizeof buf, "#9/plumb/%s", t);
 		t = buf;
 		fd = open(t, OREAD);
 	}
diff --git a/src/cmd/rc/plan9ish.c b/src/cmd/rc/plan9ish.c
index 1b250ba..abb66cc 100644
--- a/src/cmd/rc/plan9ish.c
+++ b/src/cmd/rc/plan9ish.c
@@ -27,6 +27,8 @@
 char*
 Rcmain(void)
 {
+	return "#9/rcmain";
+/*
 	static char buf[256];
 	char *root;
 
@@ -35,9 +37,10 @@
 		root = "/usr/local/plan9";
 	snprint(buf, sizeof buf, "%s/rcmain", root);
 	return buf;
+*/
 }
 
-char Fdprefix[]="/dev/fd/";
+char Fdprefix[]="#d/";
 void execfinit(void);
 void execbind(void);
 void execmount(void);
diff --git a/src/cmd/sam/unix.c b/src/cmd/sam/unix.c
index 45cfc3b..b8a67b9 100644
--- a/src/cmd/sam/unix.c
+++ b/src/cmd/sam/unix.c
@@ -214,92 +214,4 @@
 	return p;
 }
 
-#if 0
-char *
-strdup(const char *s)
-{
-	return strcpy(emalloc(strlen(s)), s);
-}
-#endif
-
-/*
-void exits(const char *s)
-{
-    if (s) fprint(2, "exit: %s\n", s);
-    exit(s != 0);
-}
-
-void
-_exits(const char *s)
-{
-    if (s) fprint(2, "exit: %s\n", s);
-    _exit(s != 0);
-}
-
-int errstr(char *buf, int size)
-{
-    extern int errno;
-                
-    snprint(buf, size, "%s", strerror(errno));
-    return 1;       
-}                       
-*/
-                    
-int
-create(char *name, int omode, ulong perm)
-{
-    int mode;
-    int fd; 
-        
-    if (omode & OWRITE) mode = O_WRONLY;
-    else if (omode & OREAD) mode = O_RDONLY;
-    else mode = O_RDWR;
-
-    if ((fd = open(name, mode|O_CREAT|O_TRUNC, perm)) < 0)
-	return fd;
-
-    if (omode & OCEXEC)
-	fcntl(fd, F_SETFD, fcntl(fd,F_GETFD,0) | FD_CLOEXEC);
-
-    /* SES - not exactly right, but hopefully good enough. */
-    if (omode & ORCLOSE)
-	remove(name);
-
-    return fd;                          
-}
-
-/* SHOULD BE ELSEWHERE */
-#if 0	/* needed on old __APPLE__ */
-#include <lib9.h>
-
-Lock plk;
-
-ulong
-pread(int fd, void *buf, ulong n, ulong off)
-{
-	ulong rv;
-
-	lock(&plk);
-	if (lseek(fd, off, 0) != off)
-		return -1;
-	rv = read(fd, buf, n);
-	unlock(&plk);
-
-	return rv;
-}
-
-ulong
-pwrite(int fd, void *buf, ulong n, ulong off)
-{
-	ulong rv;
-
-	lock(&plk);
-	if (lseek(fd, off, 0) != off)
-		return -1;
-	rv = write(fd, buf, n);
-	unlock(&plk);
-
-	return rv;
-}
-#endif
 
diff --git a/src/cmd/yacc.c b/src/cmd/yacc.c
index 05e9d58..d9e3076 100644
--- a/src/cmd/yacc.c
+++ b/src/cmd/yacc.c
@@ -13,8 +13,8 @@
 #define SETBIT(a,i)	((a)[(i)>>5] |= (1<<((i)&037)))
 #define NWORDS(n)	(((n)+32)/32)
 
-#define PARSER		"lib/yaccpar"
-#define PARSERS		"lib/yaccpars"
+#define PARSER		"#9/lib/yaccpar"
+#define PARSERS		"#9/lib/yaccpars"
 #define TEMPNAME	"y.tmp.XXXXXX"
 #define ACTNAME		"y.acts.XXXXXX"
 #define OFILE		"tab.c"
@@ -398,19 +398,10 @@
 others(void)
 {
 	int c, i, j;
-	char *s, *root;
 
-	root = getenv("PLAN9");
-	if(root == nil)
-		root = "/usr/local/plan9";
-	s = malloc(strlen(root)+1+strlen(parser)+1);
-	strcpy(s, root);
-	strcat(s, "/");
-	strcat(s, parser);
-	finput = Bopen(s, OREAD);
+	finput = Bopen(parser, OREAD);
 	if(finput == 0)
-		error("cannot find parser %s", s);
-	free(s);
+		error("cannot find parser %s", parser);
 	warray("yyr1", levprd, nprod);
 	aryfil(temp1, nprod, 0);
 	PLOOP(1, i)
diff --git a/src/lib9/announce.c b/src/lib9/announce.c
index 04c712d..9f07bd2 100644
--- a/src/lib9/announce.c
+++ b/src/lib9/announce.c
@@ -5,13 +5,14 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <sys/un.h>
+#include <errno.h>
 
 #undef sun
 #define sun sockun
 extern int _p9dialparse(char*, char**, char**, u32int*, int*);
 
-static int
-getfd(char *dir)
+int
+_p9netfd(char *dir)
 {
 	int fd;
 
@@ -83,7 +84,6 @@
 	if(proto == SOCK_STREAM){
 		listen(s, 8);
 		putfd(dir, s);
-print("announce dir: %s\n", dir);
 	}
 	return s;
 
@@ -95,9 +95,21 @@
 		return -1;
 	sn = sizeof sun;
 	if(bind(s, (struct sockaddr*)&sun, sizeof sun) < 0){
+		if(errno == EADDRINUSE
+		&& connect(s, (struct sockaddr*)&sun, sizeof sun) < 0
+		&& errno == ECONNREFUSED){
+			/* dead socket, so remove it */
+			remove(unix);
+			close(s);
+			if((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+				return -1;
+			if(bind(s, (struct sockaddr*)&sun, sizeof sun) >= 0)
+				goto Success;
+		}
 		close(s);
 		return -1;
 	}
+Success:
 	listen(s, 8);
 	putfd(dir, s);
 	return s;
@@ -108,18 +120,15 @@
 {
 	int fd;
 
-	if((fd = getfd(dir)) < 0){
+	if((fd = _p9netfd(dir)) < 0){
 		werrstr("bad 'directory' in listen: %s", dir);
 		return -1;
 	}
 
-print("accept %d", fd);
 	if((fd = accept(fd, nil, nil)) < 0)
 		return -1;
-print(" -> %d\n", fd);
 
 	putfd(newdir, fd);
-print("listen dir: %s\n", newdir);
 	return fd;
 }
 
@@ -128,7 +137,7 @@
 {
 	int fd;
 
-	if((fd = getfd(dir)) < 0){
+	if((fd = _p9netfd(dir)) < 0){
 		werrstr("bad 'directory' in accept");
 		return -1;
 	}
diff --git a/src/lib9/await.c b/src/lib9/await.c
index 89c695a..5f2d58b 100644
--- a/src/lib9/await.c
+++ b/src/lib9/await.c
@@ -45,6 +45,7 @@
 #endif
 	SIGUSR1,		"sys: usr1",
 	SIGUSR2,		"sys: usr2",
+	SIGPIPE,		"sys: write on closed pipe",
 };
 	
 char*
diff --git a/src/lib9/convM2S.c b/src/lib9/convM2S.c
index fcdcd42..920775e 100644
--- a/src/lib9/convM2S.c
+++ b/src/lib9/convM2S.c
@@ -138,6 +138,7 @@
 		break;
 
 	case Topen:
+	case Topenfd:
 		if(p+BIT32SZ+BIT8SZ > ep)
 			return 0;
 		f->fid = GBIT32(p);
@@ -260,6 +261,7 @@
 		break;
 
 	case Ropen:
+	case Ropenfd:
 	case Rcreate:
 		p = gqid(p, ep, &f->qid);
 		if(p == nil)
@@ -268,6 +270,12 @@
 			return 0;
 		f->iounit = GBIT32(p);
 		p += BIT32SZ;
+		if(f->type == Ropenfd){
+			if(p+BIT32SZ > ep)
+				return 0;
+			f->unixfd = GBIT32(p);
+			p += BIT32SZ;
+		}
 		break;
 
 	case Rread:
diff --git a/src/lib9/convS2M.c b/src/lib9/convS2M.c
index 9acdcfa..6e9d271 100644
--- a/src/lib9/convS2M.c
+++ b/src/lib9/convS2M.c
@@ -92,6 +92,7 @@
 		break;
 
 	case Topen:
+	case Topenfd:
 		n += BIT32SZ;
 		n += BIT8SZ;
 		break;
@@ -164,6 +165,12 @@
 		n += BIT32SZ;
 		break;
 
+	case Ropenfd:
+		n += QIDSZ;
+		n += BIT32SZ;
+		n += BIT32SZ;
+		break;
+
 	case Rread:
 		n += BIT32SZ;
 		n += f->count;
@@ -257,6 +264,7 @@
 		break;
 
 	case Topen:
+	case Topenfd:
 		PBIT32(p, f->fid);
 		p += BIT32SZ;
 		PBIT8(p, f->mode);
@@ -347,9 +355,14 @@
 
 	case Ropen:
 	case Rcreate:
+	case Ropenfd:
 		p = pqid(p, &f->qid);
 		PBIT32(p, f->iounit);
 		p += BIT32SZ;
+		if(f->type == Ropenfd){
+			PBIT32(p, f->unixfd);
+			p += BIT32SZ;
+		}
 		break;
 
 	case Rread:
diff --git a/src/lib9/create.c b/src/lib9/create.c
index abef0c3..bdad5f6 100644
--- a/src/lib9/create.c
+++ b/src/lib9/create.c
@@ -1,8 +1,54 @@
 #include <u.h>
+#define NOPLAN9DEFINES
 #include <libc.h>
+#include <sys/stat.h>
+
+extern char *_p9translate(char*);
 
 int
-create(char *path, int mode, ulong perm)
+p9create(char *xpath, int mode, ulong perm)
 {
-	return open(path, mode|O_CREAT|O_TRUNC, perm);
+	int fd, cexec, umode, rclose;
+	char *path;
+
+	if((path = _p9translate(xpath)) == nil)
+		return -1;
+
+	cexec = mode&OCEXEC;
+	rclose = mode&ORCLOSE;
+	mode &= ~(ORCLOSE|OCEXEC);
+
+	/* XXX should get mode mask right? */
+	fd = -1;
+	if(perm&DMDIR){
+		if(mode != OREAD){
+			werrstr("bad mode in directory create");
+			goto out;
+		}
+		if(mkdir(path, perm&0777) < 0)
+			goto out;
+		fd = open(path, O_RDONLY);
+	}else{
+		umode = (mode&3)|O_CREAT|O_TRUNC;
+		mode &= ~(3|OTRUNC);
+		if(mode&OEXCL){
+			umode |= O_EXCL;
+			mode &= ~OEXCL;
+		}
+		if(mode){
+			werrstr("unsupported mode in create");
+			goto out;
+		}
+		fd = open(path, umode, perm);
+	}
+out:
+	if(fd >= 0){
+		if(cexec)
+			fcntl(fd, F_SETFL, FD_CLOEXEC);
+		if(rclose)
+			remove(path);
+	}
+	if(path != xpath)
+		free(path);
+	return fd;
 }
diff --git a/src/lib9/fcallfmt.c b/src/lib9/fcallfmt.c
index 4eef88d..592316f 100644
--- a/src/lib9/fcallfmt.c
+++ b/src/lib9/fcallfmt.c
@@ -74,9 +74,16 @@
 		seprint(buf, e, "Topen tag %ud fid %ud mode %d", tag, fid, f->mode);
 		break;
 	case Ropen:
-		seprint(buf, e, "Ropen tag %ud qid " QIDFMT " iounit %ud ", tag,
+		seprint(buf, e, "Ropen tag %ud qid " QIDFMT " iounit %ud", tag,
 			f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type), f->iounit);
 		break;
+	case Topenfd:	/* 98 */
+		seprint(buf, e, "Topenfd tag %ud fid %ud mode %d", tag, fid, f->mode);
+		break;
+	case Ropenfd:
+		seprint(buf, e, "Ropenfd tag %ud qid " QIDFMT " iounit %ud unixfd %d", tag,
+			f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type), f->iounit, f->unixfd);
+		break;
 	case Tcreate:	/* 114 */
 		seprint(buf, e, "Tcreate tag %ud fid %ud name %s perm %M mode %d", tag, fid, f->name, (ulong)f->perm, f->mode);
 		break;
diff --git a/src/lib9/getenv.c b/src/lib9/getenv.c
index c6ff716..2a2d139 100644
--- a/src/lib9/getenv.c
+++ b/src/lib9/getenv.c
@@ -13,3 +13,15 @@
 	return strdup(t);
 }
 
+int
+p9putenv(char *s, char *v)
+{
+	char *t;
+
+	t = smprint("%s=%s", s, v);
+	if(t == nil)
+		return -1;
+	putenv(t);
+	free(t);
+	return 0;
+}
diff --git a/src/lib9/mkfile b/src/lib9/mkfile
index 14d8c0e..a0e75fc 100644
--- a/src/lib9/mkfile
+++ b/src/lib9/mkfile
@@ -8,6 +8,8 @@
 	_p9dialparse.$O\
 	_p9dir.$O\
 	_p9proc.$O\
+	_p9translate.$O\
+	access.$O\
 	announce.$O\
 	argv0.$O\
 	atexit.$O\
@@ -40,11 +42,13 @@
 	getcallerpc-$OBJTYPE.$O\
 	getenv.$O\
 	getfields.$O\
+	getns.$O\
 	getuser.$O\
 	getwd.$O\
 	jmp.$O\
 	lock.$O\
 	main.$O\
+	malloc.$O\
 	malloctag.$O\
 	mallocz.$O\
 	nan.$O\
@@ -53,13 +57,18 @@
 	notify.$O\
 	nrand.$O\
 	nulldir.$O\
+	open.$O\
+	pipe.$O\
+	post9p.$O\
 	postnote.$O\
 	qlock.$O\
 	quote.$O\
+	read9pmsg.$O\
 	readn.$O\
 	rendez-$SYSNAME.$O\
 	rfork.$O\
 	seek.$O\
+	sendfd.$O\
 	sleep.$O\
 	strecpy.$O\
 	sysfatal.$O\
diff --git a/src/lib9/notify.c b/src/lib9/notify.c
index 460eabf..160755d 100644
--- a/src/lib9/notify.c
+++ b/src/lib9/notify.c
@@ -19,7 +19,7 @@
 #endif
 	SIGFPE,
 	SIGBUS,
-	SIGSEGV,
+/*	SIGSEGV,	*/
 	SIGSYS,
 	SIGPIPE,
 	SIGALRM,
diff --git a/src/lib9/rfork.c b/src/lib9/rfork.c
index 270c3cd..f3a2192 100644
--- a/src/lib9/rfork.c
+++ b/src/lib9/rfork.c
@@ -9,7 +9,7 @@
 	if((flags&(RFPROC|RFFDG|RFMEM)) == (RFPROC|RFFDG)){
 		/* check other flags before we commit */
 		flags &= ~(RFPROC|RFFDG);
-		if(flags & ~(RFNOTEG)){
+		if(flags & ~(RFNOTEG|RFNAMEG)){
 			werrstr("unknown flags %08ux in rfork", flags);
 			return -1;
 		}
@@ -17,11 +17,14 @@
 		if(pid != 0)
 			return pid;
 	}
-
 	if(flags&RFPROC){
-		werrstr("cannot use rfork to fork -- use ffork");
+		werrstr("cannot use rfork for shared memory -- use ffork");
 		return -1;
 	}
+	if(flags&RFNAMEG){
+		/* XXX set $NAMESPACE to a new directory */
+		flags &= ~RFNAMEG;
+	}
 	if(flags&RFNOTEG){
 		setpgid(0, getpid());
 		flags &= ~RFNOTEG;
diff --git a/src/libdraw/openfont.c b/src/libdraw/openfont.c
index a1ff278..0b3dc25 100644
--- a/src/libdraw/openfont.c
+++ b/src/libdraw/openfont.c
@@ -9,21 +9,15 @@
 {
 	Font *fnt;
 	int fd, i, n;
-	char *buf, *nambuf, *root;
+	char *buf, *nambuf;
 
 	nambuf = 0;
 	fd = open(name, OREAD);
 
 	if(fd < 0 && strncmp(name, "/lib/font/bit/", 14) == 0){
-		root = getenv("PLAN9");
-		if(root == nil)
-			return 0;
-		nambuf = malloc(strlen(root)+5+strlen(name+13)+1);
+		nambuf = smprint("#9/font/%s", name+14);
 		if(nambuf == nil)
 			return 0;
-		strcpy(nambuf, root);
-		strcat(nambuf, "/font");
-		strcat(nambuf, name+13);
 		if((fd = open(nambuf, OREAD)) < 0){
 			free(nambuf);
 			return 0;
diff --git a/src/libdraw/x11-alloc.c b/src/libdraw/x11-alloc.c
index 458efc6..9a6585c 100644
--- a/src/libdraw/x11-alloc.c
+++ b/src/libdraw/x11-alloc.c
@@ -10,7 +10,7 @@
  * Allocate a Memimage with an optional pixmap backing on the X server.
  */
 Memimage*
-xallocmemimage(Rectangle r, u32int chan, int pixmap)
+_xallocmemimage(Rectangle r, u32int chan, int pixmap)
 {
 	int d, offset;
 	Memimage *m;
@@ -95,7 +95,7 @@
 Memimage*
 allocmemimage(Rectangle r, u32int chan)
 {
-	return xallocmemimage(r, chan, PMundef);
+	return _xallocmemimage(r, chan, PMundef);
 }
 
 void
diff --git a/src/libdraw/x11-cload.c b/src/libdraw/x11-cload.c
index 25a325c..7a84a98 100644
--- a/src/libdraw/x11-cload.c
+++ b/src/libdraw/x11-cload.c
@@ -13,7 +13,7 @@
 
 	n = _cloadmemimage(i, r, data, ndata);
 	if(n > 0 && i->X)
-		xputxdata(i, r);
+		_xputxdata(i, r);
 	return n;
 }
 
diff --git a/src/libdraw/x11-draw.c b/src/libdraw/x11-draw.c
index 9743874..6373e4e 100644
--- a/src/libdraw/x11-draw.c
+++ b/src/libdraw/x11-draw.c
@@ -24,11 +24,11 @@
 
 	/* only fetch dst data if we need it */
 	if((par->state&(Simplemask|Fullmask)) != (Simplemask|Fullmask))
-		xgetxdata(dst, par->r);
+		_xgetxdata(dst, par->r);
 
 	/* always fetch source and mask */
-	xgetxdata(src, par->sr);
-	xgetxdata(mask, par->mr);
+	_xgetxdata(src, par->sr);
+	_xgetxdata(mask, par->mr);
 
 	/* now can run memimagedraw on the in-memory bits */
 	_memimagedraw(par);
@@ -37,7 +37,7 @@
 		return;
 
 	/* put bits back on x server */
-	xputxdata(dst, par->r);
+	_xputxdata(dst, par->r);
 }
 
 static int
@@ -66,7 +66,7 @@
 	 */
 	m = Simplesrc|Simplemask|Fullmask;
 	if((state&m) == m){
-		xfillcolor(dst, r, par->sdval);
+		_xfillcolor(dst, r, par->sdval);
 	//	xdirtyxdata(dst, r);
 		return 1;
 	}
diff --git a/src/libdraw/x11-event.c b/src/libdraw/x11-event.c
index 408eb41..ba9d031 100644
--- a/src/libdraw/x11-event.c
+++ b/src/libdraw/x11-event.c
@@ -48,7 +48,7 @@
 		xmask |= MouseMask|StructureNotifyMask;
 	if(keys&Ekeyboard){
 		xmask |= KeyPressMask;
-		if((r = xtoplan9kbd(nil)) >= 0){
+		if((r = _xtoplan9kbd(nil)) >= 0){
 			e->kbdc = r;
 			return Ekeyboard;
 		}
@@ -60,24 +60,24 @@
 
 	switch(xevent.type){
 	case Expose:
-		xexpose(&xevent, _x.display);
+		_xexpose(&xevent, _x.display);
 		goto again;
 	case DestroyNotify:
-		if(xdestroy(&xevent, _x.display))
+		if(_xdestroy(&xevent, _x.display))
 			postnote(PNGROUP, getpgrp(), "hangup");
 		goto again;
 	case ConfigureNotify:
-		if(xconfigure(&xevent, _x.display))
+		if(_xconfigure(&xevent, _x.display))
 			eresized(1);
 		goto again;
 	case ButtonPress:
 	case ButtonRelease:
 	case MotionNotify:
-		if(xtoplan9mouse(_x.display, &xevent, &e->mouse) < 0)
+		if(_xtoplan9mouse(_x.display, &xevent, &e->mouse) < 0)
 			goto again;
 		return Emouse;
 	case KeyPress:
-		e->kbdc = xtoplan9kbd(&xevent);
+		e->kbdc = _xtoplan9kbd(&xevent);
 		if(e->kbdc == -1)
 			goto again;
 		return Ekeyboard;
@@ -136,7 +136,7 @@
 	eflush();
 again:
 	if(XCheckWindowEvent(_x.display, _x.drawable, MouseMask, &xe)){
-		if(xtoplan9mouse(_x.display, &xe, &m) < 0)
+		if(_xtoplan9mouse(_x.display, &xe, &m) < 0)
 			goto again;
 		XPutBackEvent(_x.display, &xe);
 		return 1;
@@ -151,13 +151,13 @@
 	int r;
 
 	eflush();
-	if((r = xtoplan9kbd(nil)) >= 0){
-		xtoplan9kbd((XEvent*)-1);
+	if((r = _xtoplan9kbd(nil)) >= 0){
+		_xtoplan9kbd((XEvent*)-1);
 		return 1;
 	}
 again:
 	if(XCheckWindowEvent(_x.display, _x.drawable, KeyPressMask, &xe)){
-		if(xtoplan9kbd(&xe) == -1)
+		if(_xtoplan9kbd(&xe) == -1)
 			goto again;
 		XPutBackEvent(_x.display, &xe);
 		return 1;
@@ -168,12 +168,12 @@
 void
 emoveto(Point p)
 {
-	xmoveto(p);
+	_xmoveto(p);
 }
 
 void
 esetcursor(Cursor *c)
 {
-	xsetcursor(c);
+	_xsetcursor(c);
 }
 
diff --git a/src/libdraw/x11-fill.c b/src/libdraw/x11-fill.c
index ff0b2e8..33fc6a2 100644
--- a/src/libdraw/x11-fill.c
+++ b/src/libdraw/x11-fill.c
@@ -13,13 +13,13 @@
 	if(m->X == nil)
 		return;
 	if((val & 0xFF) == 0xFF)	/* full alpha */
-		xfillcolor(m, m->r, _rgbatoimg(m, val));
+		_xfillcolor(m, m->r, _rgbatoimg(m, val));
 	else
-		xputxdata(m, m->r);
+		_xputxdata(m, m->r);
 }
 
 void
-xfillcolor(Memimage *m, Rectangle r, u32int v)
+_xfillcolor(Memimage *m, Rectangle r, u32int v)
 {
 	Point p;
 	Xmem *xm;
diff --git a/src/libdraw/x11-get.c b/src/libdraw/x11-get.c
index a6d4b12..693b293 100644
--- a/src/libdraw/x11-get.c
+++ b/src/libdraw/x11-get.c
@@ -16,7 +16,7 @@
 }
 
 XImage*
-xgetxdata(Memimage *m, Rectangle r)
+_xgetxdata(Memimage *m, Rectangle r)
 {
 	int x, y;
 	uchar *p;
@@ -55,7 +55,7 @@
 }
 
 void
-xputxdata(Memimage *m, Rectangle r)
+_xputxdata(Memimage *m, Rectangle r)
 {
 	int offset, x, y;
 	uchar *p;
@@ -97,7 +97,7 @@
 }
 
 void
-xdirtyxdata(Memimage *m, Rectangle r)
+_xdirtyxdata(Memimage *m, Rectangle r)
 {
 	Xmem *xm;
 
diff --git a/src/libdraw/x11-init.c b/src/libdraw/x11-init.c
index 6f87b41..fb6a914 100644
--- a/src/libdraw/x11-init.c
+++ b/src/libdraw/x11-init.c
@@ -359,7 +359,7 @@
 	_x.screenr = r;
 	_x.screenpm = XCreatePixmap(_x.display, _x.drawable, Dx(r), Dy(r), _x.depth);
 	_x.nextscreenpm = _x.screenpm;
-	_x.screenimage = xallocmemimage(r, _x.chan, _x.screenpm);
+	_x.screenimage = _xallocmemimage(r, _x.chan, _x.screenpm);
 
 	/*
 	 * Allocate some useful graphics contexts for the future.
@@ -651,7 +651,7 @@
 }
 
 void
-xexpose(XEvent *e, XDisplay *xd)
+_xexpose(XEvent *e, XDisplay *xd)
 {
 	XExposeEvent *xe;
 	Rectangle r;
@@ -673,7 +673,7 @@
 }
 
 int
-xdestroy(XEvent *e, XDisplay *xd)
+_xdestroy(XEvent *e, XDisplay *xd)
 {
 	XDestroyWindowEvent *xe;
 
@@ -686,7 +686,7 @@
 }
 
 int
-xconfigure(XEvent *e, XDisplay *xd)
+_xconfigure(XEvent *e, XDisplay *xd)
 {
 	Rectangle r;
 	XConfigureEvent *xe = (XConfigureEvent*)e;
@@ -719,7 +719,7 @@
 		return 0;
 
 	pixmap = XCreatePixmap(_x.display, _x.drawable, Dx(r), Dy(r), _x.depth);
-	m = xallocmemimage(r, _x.chan, pixmap);
+	m = _xallocmemimage(r, _x.chan, pixmap);
 	if(_x.nextscreenpm != _x.screenpm)
 		XFreePixmap(_x.display, _x.nextscreenpm);
 	_x.nextscreenpm = pixmap;
diff --git a/src/libdraw/x11-itrans.c b/src/libdraw/x11-itrans.c
index 0c99244..8e72b01 100644
--- a/src/libdraw/x11-itrans.c
+++ b/src/libdraw/x11-itrans.c
@@ -12,7 +12,7 @@
 #include "x11-memdraw.h"
 
 static int
-_xtoplan9kbd(XEvent *e)
+__xtoplan9kbd(XEvent *e)
 {
 	int ind, k, md;
 
@@ -125,7 +125,7 @@
 	int n;
 	int r;
 
-	r = _xtoplan9kbd(e);
+	r = __xtoplan9kbd(e);
 	if(r < 0)
 		return nil;
 	if(alting){
@@ -156,7 +156,7 @@
 }
 
 int
-xtoplan9kbd(XEvent *e)
+_xtoplan9kbd(XEvent *e)
 {
 	static Rune *r;
 
@@ -173,7 +173,7 @@
 }
 
 int
-xtoplan9mouse(XDisplay *xd, XEvent *e, Mouse *m)
+_xtoplan9mouse(XDisplay *xd, XEvent *e, Mouse *m)
 {
 	int s;
 	XButtonEvent *be;
@@ -260,7 +260,7 @@
 }
 
 void
-xmoveto(Point p)
+_xmoveto(Point p)
 {
 	XWarpPointer(_x.display, None, _x.drawable, 0, 0, 0, 0, p.x, p.y);
 	XFlush(_x.display);
@@ -296,7 +296,7 @@
 
 
 void
-xsetcursor(Cursor *c)
+_xsetcursor(Cursor *c)
 {
 	XColor fg, bg;
 	XCursor xc;
@@ -335,7 +335,7 @@
 } clip;
 
 char*
-xgetsnarf(XDisplay *xd)
+_xgetsnarf(XDisplay *xd)
 {
 	uchar *data, *xdata;
 	Atom clipboard, type, prop;
@@ -420,7 +420,7 @@
 }
 
 void
-xputsnarf(XDisplay *xd, char *data)
+_xputsnarf(XDisplay *xd, char *data)
 {
 	XButtonEvent e;
 
@@ -445,7 +445,7 @@
 }
 
 int
-xselect(XEvent *e, XDisplay *xd)
+_xselect(XEvent *e, XDisplay *xd)
 {
 	char *name;
 	XEvent r;
@@ -493,12 +493,12 @@
 void
 putsnarf(char *data)
 {
-	xputsnarf(_x.snarfcon, data);
+	_xputsnarf(_x.snarfcon, data);
 }
 
 char*
 getsnarf(void)
 {
-	return xgetsnarf(_x.snarfcon);
+	return _xgetsnarf(_x.snarfcon);
 }
 
diff --git a/src/libdraw/x11-keyboard.c b/src/libdraw/x11-keyboard.c
index 443074e..676027e 100644
--- a/src/libdraw/x11-keyboard.c
+++ b/src/libdraw/x11-keyboard.c
@@ -46,12 +46,12 @@
 		XWindowEvent(_x.kbdcon, _x.drawable, KeyPressMask, &xevent);
 		switch(xevent.type){
 		case KeyPress:
-			i = xtoplan9kbd(&xevent);
+			i = _xtoplan9kbd(&xevent);
 			if(i == -1)
 				continue;
 			r = i;
 			send(kc->c, &r);
-			while((i=xtoplan9kbd(nil)) >= 0){
+			while((i=_xtoplan9kbd(nil)) >= 0){
 				r = i;
 				send(kc->c, &r);
 			}
diff --git a/src/libdraw/x11-load.c b/src/libdraw/x11-load.c
index e4e32bf2..5292275 100644
--- a/src/libdraw/x11-load.c
+++ b/src/libdraw/x11-load.c
@@ -13,7 +13,7 @@
 
 	n = _loadmemimage(i, r, data, ndata);
 	if(n > 0 && i->X)
-		xputxdata(i, r);
+		_xputxdata(i, r);
 	return n;
 }
 
diff --git a/src/libdraw/x11-memdraw.h b/src/libdraw/x11-memdraw.h
index afd47cc..1e84b92 100644
--- a/src/libdraw/x11-memdraw.h
+++ b/src/libdraw/x11-memdraw.h
@@ -76,26 +76,26 @@
 
 extern Xprivate _x;
 
-extern Memimage *xallocmemimage(Rectangle, u32int, int);
-extern XImage	*xallocxdata(Memimage*, Rectangle);
-extern void	xdirtyxdata(Memimage*, Rectangle);
-extern void	xfillcolor(Memimage*, Rectangle, u32int);
-extern void	xfreexdata(Memimage*);
-extern XImage	*xgetxdata(Memimage*, Rectangle);
-extern void	xputxdata(Memimage*, Rectangle);
+extern Memimage *_xallocmemimage(Rectangle, u32int, int);
+extern XImage	*_xallocxdata(Memimage*, Rectangle);
+extern void	_xdirtyxdata(Memimage*, Rectangle);
+extern void	_xfillcolor(Memimage*, Rectangle, u32int);
+extern void	_xfreexdata(Memimage*);
+extern XImage	*_xgetxdata(Memimage*, Rectangle);
+extern void	_xputxdata(Memimage*, Rectangle);
 extern void	_initdisplaymemimage(Display*, Memimage*);
 
 struct Mouse;
-extern int	xtoplan9mouse(XDisplay*, XEvent*, struct Mouse*);
-extern int	xtoplan9kbd(XEvent*);
-extern void	xexpose(XEvent*, XDisplay*);
-extern int	xselect(XEvent*, XDisplay*);
-extern int	xconfigure(XEvent*, XDisplay*);
-extern int	xdestroy(XEvent*, XDisplay*);
-extern void	flushmemscreen(Rectangle);
-extern void	xmoveto(Point);
+extern int	_xtoplan9mouse(XDisplay*, XEvent*, struct Mouse*);
+extern int	_xtoplan9kbd(XEvent*);
+extern void	_xexpose(XEvent*, XDisplay*);
+extern int	_xselect(XEvent*, XDisplay*);
+extern int	_xconfigure(XEvent*, XDisplay*);
+extern int	_xdestroy(XEvent*, XDisplay*);
+extern void	_flushmemscreen(Rectangle);
+extern void	_xmoveto(Point);
 struct Cursor;
-extern void	xsetcursor(struct Cursor*);
+extern void	_xsetcursor(struct Cursor*);
 
 #define MouseMask (\
 	ButtonPressMask|\
diff --git a/src/libdraw/x11-mouse.c b/src/libdraw/x11-mouse.c
index eae2678..9e5143c 100644
--- a/src/libdraw/x11-mouse.c
+++ b/src/libdraw/x11-mouse.c
@@ -11,7 +11,7 @@
 void
 moveto(Mousectl *m, Point pt)
 {
-	xmoveto(pt);
+	_xmoveto(pt);
 }
 
 void
@@ -64,10 +64,10 @@
 		XNextEvent(_x.mousecon, &xevent);
 		switch(xevent.type){
 		case Expose:
-			xexpose(&xevent, _x.mousecon);
+			_xexpose(&xevent, _x.mousecon);
 			continue;
 		case DestroyNotify:
-			if(xdestroy(&xevent, _x.mousecon)){
+			if(_xdestroy(&xevent, _x.mousecon)){
 				/* drain it before sending */
 				/* apps that care can notice we sent a 0 */
 				/* otherwise we'll have getwindow send SIGHUP */
@@ -77,16 +77,16 @@
 			}
 			continue;
 		case ConfigureNotify:
-			if(xconfigure(&xevent, _x.mousecon))
+			if(_xconfigure(&xevent, _x.mousecon))
 				nbsend(mc->resizec, &one);
 			continue;
 		case SelectionRequest:
-			xselect(&xevent, _x.mousecon);
+			_xselect(&xevent, _x.mousecon);
 			continue;
 		case ButtonPress:
 		case ButtonRelease:
 		case MotionNotify:
-			if(xtoplan9mouse(_x.mousecon, &xevent, &m) < 0)
+			if(_xtoplan9mouse(_x.mousecon, &xevent, &m) < 0)
 				continue;
 			send(mc->c, &m);
 			/*
@@ -117,6 +117,6 @@
 void
 setcursor(Mousectl *mc, Cursor *c)
 {
-	xsetcursor(c);
+	_xsetcursor(c);
 }
 
diff --git a/src/libdraw/x11-pixelbits.c b/src/libdraw/x11-pixelbits.c
index 8635b0b..22dfc60 100644
--- a/src/libdraw/x11-pixelbits.c
+++ b/src/libdraw/x11-pixelbits.c
@@ -10,7 +10,7 @@
 pixelbits(Memimage *m, Point p)
 {
 	if(m->X)
-		xgetxdata(m, Rect(p.x, p.y, p.x+1, p.y+1));
+		_xgetxdata(m, Rect(p.x, p.y, p.x+1, p.y+1));
 	return _pixelbits(m, p);
 }
 
diff --git a/src/libdraw/x11-unload.c b/src/libdraw/x11-unload.c
index 3e8a635..471ca88 100644
--- a/src/libdraw/x11-unload.c
+++ b/src/libdraw/x11-unload.c
@@ -10,7 +10,7 @@
 unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
 {
 	if(i->X)
-		xgetxdata(i, r);
+		_xgetxdata(i, r);
 	return _unloadmemimage(i, r, data, ndata);
 }
 
diff --git a/src/libfs/fs.c b/src/libfs/fs.c
index b34df34..91d4af1 100644
--- a/src/libfs/fs.c
+++ b/src/libfs/fs.c
@@ -50,7 +50,7 @@
 }
 
 Fsys*
-fsmount(int fd)
+fsmount(int fd, char *aname)
 {
 	int n;
 	char *user;
@@ -62,13 +62,14 @@
 	strcpy(fs->version, "9P2000");
 	if((n = fsversion(fs, 8192, fs->version, sizeof fs->version)) < 0){
 	Error:
+		fs->fd = -1;
 		fsunmount(fs);
 		return nil;
 	}
 	fs->msize = n;
 
 	user = getuser();
-	if((fs->root = fsattach(fs, nil, getuser(), "")) == nil)
+	if((fs->root = fsattach(fs, nil, getuser(), aname)) == nil)
 		goto Error;
 	return fs;
 }
@@ -76,6 +77,8 @@
 void
 fsunmount(Fsys *fs)
 {
+	fsclose(fs->root);
+	fs->root = nil;
 	_fsdecref(fs);
 }
 
@@ -85,7 +88,9 @@
 	Fid *f, *next;
 
 	qlock(&fs->lk);
-	if(--fs->ref == 0){
+	--fs->ref;
+	//fprint(2, "fsdecref %p to %d\n", fs, fs->ref);
+	if(fs->ref == 0){
 		close(fs->fd);
 		for(f=fs->freefid; f; f=next){
 			next = f->next;
@@ -103,6 +108,7 @@
 	void *freep;
 	Fcall tx, rx;
 
+	tx.tag = 0;
 	tx.type = Tversion;
 	tx.version = version;
 	tx.msize = msize;
@@ -120,9 +126,13 @@
 	Fcall tx, rx;
 	Fid *fid;
 
+	if(aname == nil)
+		aname = "";
+
 	if((fid = _fsgetfid(fs)) == nil)
 		return nil;
 
+	tx.tag = 0;
 	tx.type = Tattach;
 	tx.afid = afid ? afid->fid : NOFID;
 	tx.fid = fid->fid;
@@ -145,12 +155,11 @@
 
 	n = sizeS2M(tx);
 	tpkt = malloc(n);
-fprint(2, "tpkt %p\n", tpkt);
 	if(freep)
 		*freep = nil;
 	if(tpkt == nil)
 		return -1;
-	fprint(2, "<- %F\n", tx);
+	//fprint(2, "<- %F\n", tx);
 	nn = convS2M(tx, tpkt, n);
 	if(nn != n){
 		free(tpkt);
@@ -159,20 +168,18 @@
 		return -1;
 	}
 	rpkt = muxrpc(&fs->mux, tpkt);
-fprint(2, "tpkt %p\n", tpkt);
 	free(tpkt);
-fprint(2, "tpkt freed\n");
 	if(rpkt == nil)
 		return -1;
 	n = GBIT32((uchar*)rpkt);
 	nn = convM2S(rpkt, n, rx);
 	if(nn != n){
 		free(rpkt);
-		werrstr("libfs: convM2S packet size mismatch");
+		werrstr("libfs: convM2S packet size mismatch %d %d", n, nn);
 		fprint(2, "%r\n");
 		return -1;
 	}
-	fprint(2, "-> %F\n", rx);
+	//fprint(2, "-> %F\n", rx);
 	if(rx->type == Rerror){
 		werrstr("%s", rx->ename);
 		free(rpkt);
@@ -208,13 +215,13 @@
 			f[i].fid = fs->nextfid++;
 			f[i].next = &f[i+1];
 			f[i].fs = fs;
-			fs->ref++;
 		}
 		f[i-1].next = nil;
 		fs->freefid = f;
 	}
 	f = fs->freefid;
 	fs->freefid = f->next;
+	fs->ref++;
 	qunlock(&fs->lk);
 	return f;
 }
@@ -259,7 +266,7 @@
 {
 	uchar *pkt;
 	uchar buf[4];
-	int n;
+	int n, nfd;
 	Fsys *fs;
 
 	fs = mux->aux;
@@ -277,11 +284,13 @@
 		free(pkt);
 		return nil;
 	}
-#if 0
 	if(pkt[4] == Ropenfd){
-		/* do unix socket crap */
-		sysfatal("no socket crap implemented");
+		if((nfd=recvfd(fs->fd)) < 0){
+			fprint(2, "recv fd error: %r\n");
+			free(pkt);
+			return nil;
+		}
+		PBIT32(pkt+n-4, nfd);
 	}
-#endif
 	return pkt;
 }
diff --git a/src/libfs/mkfile b/src/libfs/mkfile
index acfb0ae..d4c8b49 100644
--- a/src/libfs/mkfile
+++ b/src/libfs/mkfile
@@ -8,7 +8,9 @@
 	create.$O\
 	dirread.$O\
 	fs.$O\
+	ns.$O\
 	open.$O\
+	openfd.$O\
 	read.$O\
 	stat.$O\
 	walk.$O\
diff --git a/src/libmux/mux.c b/src/libmux/mux.c
index b1fdeb0..bc63260 100644
--- a/src/libmux/mux.c
+++ b/src/libmux/mux.c
@@ -173,6 +173,5 @@
 	mux->nwait--;
 	mux->freetag = i;
 	rwakeup(&mux->tagrend);
-fprint(2, "free %p\n", r);
 	free(r);
 }
diff --git a/src/libplumb/mesg.c b/src/libplumb/mesg.c
index 8678d75..fcade7f 100755
--- a/src/libplumb/mesg.c
+++ b/src/libplumb/mesg.c
@@ -1,5 +1,7 @@
 #include <u.h>
 #include <libc.h>
+#include <fcall.h>
+#include <fs.h>
 #include "plumb.h"
 
 static char attrbuf[4096];
@@ -9,35 +11,15 @@
 int
 plumbopen(char *name, int omode)
 {
-#if 0
-	int fd, f;
-	char *s;
-#endif
-	char buf[256];
+	Fsys *fs;
+	int fd;
 
-	if(name[0] == '/')
-		return open(name, omode);
-	if(home == nil){
-		home = getenv("HOME");
-		if(home == nil)
-			return -1;
-	}
-	snprint(buf, sizeof buf, "%s/mnt/plumb", home);
-#if 0
-	fd = open(buf, omode);
-	if(fd >= 0)
-		return fd;
-	snprint(buf, sizeof buf, "/mnt/term/mnt/plumb/%s", name);
-	fd = open(buf, omode);
-	if(fd >= 0)
-		return fd;
-	/* try mounting service */
-	s = getenv("plumbsrv");
-	if(s == nil)
+	fs = nsmount("plumb", "");
+	if(fs == nil)
 		return -1;
-	snprint(buf, sizeof buf, "/mnt/plumb/%s", name);
-#endif
-	return open(buf, omode);
+	fd = fsopenfd(fs, name, omode);
+	fsunmount(fs);
+	return fd;
 }
 
 static int
diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
index 7cad85c..074556f 100644
--- a/src/libthread/asm-FreeBSD-386.s
+++ b/src/libthread/asm-FreeBSD-386.s
@@ -41,9 +41,9 @@
 	movl 4(%esp), %eax
 	lock decl 0(%eax)
 	jz iszero
-	movl %eax, 1
+	movl $1, %eax
 	ret
 iszero:
-	movl %eax, 0
+	movl $0, %eax
 	ret
 
diff --git a/src/libthread/create.c b/src/libthread/create.c
index 55f6c60..d487e19 100644
--- a/src/libthread/create.c
+++ b/src/libthread/create.c
@@ -1,8 +1,7 @@
 #include "threadimpl.h"
 
 Pqueue _threadpq;
-
-int _threadmultiproc;
+int _threadprocs;
 
 static int nextID(void);
 
@@ -90,7 +89,6 @@
 		werrstr("cannot create procs once there is an idle thread");
 		return -1;
 	}
-	_threadmultiproc = 1;
 	return procrfork(f, arg, stacksize, 0);
 }
 
@@ -125,11 +123,12 @@
 {
 	int id;
 
-	if(_threadmultiproc){
+	if(_threadprocs!=1){
 		werrstr("cannot have idle thread in multi-proc program");
 		return -1;
 	}
 	id = newthread(_threadgetproc(), f, arg, stacksize, nil, threadgetgrp());
+	_threaddebug(DBGSCHED, "idle is %d", id);
 	_threadidle();
 	return id;
 }
@@ -154,6 +153,7 @@
 	else
 		*_threadpq.tail = p;
 	_threadpq.tail = &p->next;
+	_threadprocs++;
 	unlock(&_threadpq.lock);
 	return p;
 }
diff --git a/src/libthread/exec-unix.c b/src/libthread/exec-unix.c
index ef50bf1..97c7560 100644
--- a/src/libthread/exec-unix.c
+++ b/src/libthread/exec-unix.c
@@ -3,7 +3,7 @@
 #include "threadimpl.h"
 
 void
-procexec(Channel *pidc, char *prog, char *args[])
+procexec(Channel *pidc, int fd[3], char *prog, char *args[])
 {
 	int n;
 	Proc *p;
@@ -45,6 +45,7 @@
 	assert(p->needexec==0);
 	p->exec.prog = prog;
 	p->exec.args = args;
+	p->exec.stdfd = fd;
 	p->needexec = 1;
 	_sched();
 
@@ -56,7 +57,11 @@
 		goto Bad;
 	}
 	close(p->exec.fd[0]);
-
+	close(fd[0]);
+	if(fd[1] != fd[0])
+		close(fd[1]);
+	if(fd[2] != fd[1] && fd[2] != fd[0])
+		close(fd[2]);
 	if(pidc)
 		sendul(pidc, t->ret);
 
@@ -66,9 +71,9 @@
 }
 
 void
-procexecl(Channel *pidc, char *f, ...)
+procexecl(Channel *pidc, int fd[3], char *f, ...)
 {
-	procexec(pidc, f, &f+1);
+	procexec(pidc, fd, f, &f+1);
 }
 
 void
@@ -107,10 +112,17 @@
 {
 	char buf[ERRMAX];
 	Execargs *e;
+	int i;
 
 	e = ve;
 	_threaddebug(DBGEXEC, "_schedexec %s -- calling execv", e->prog);
-	execv(e->prog, e->args);
+	dup(e->stdfd[0], 0);
+	dup(e->stdfd[1], 1);
+	dup(e->stdfd[2], 2);
+	for(i=3; i<40; i++)
+		if(i != e->fd[1])
+			close(i);
+	execvp(e->prog, e->args);
 	_threaddebug(DBGEXEC, "_schedexec failed: %r");
 	rerrstr(buf, sizeof buf);
 	if(buf[0]=='\0')
diff --git a/src/libthread/exec.c b/src/libthread/exec.c
index bcf2080..0fb6811 100644
--- a/src/libthread/exec.c
+++ b/src/libthread/exec.c
@@ -3,7 +3,7 @@
 #define PIPEMNT	"/mnt/temp"
 
 void
-procexec(Channel *pidc, char *prog, char *args[])
+procexec(Channel *pidc, int fd[3], char *prog, char *args[])
 {
 	int n;
 	Proc *p;
@@ -50,6 +50,7 @@
 	assert(p->needexec==0);
 	p->exec.prog = prog;
 	p->exec.args = args;
+	p->exec.stdfd = fd;
 	p->needexec = 1;
 	_sched();
 
@@ -61,7 +62,11 @@
 		goto Bad;
 	}
 	close(p->exec.fd[0]);
-
+	close(fd[0]);
+	if(fd[1] != fd[0])
+		close(fd[1]);
+	if(fd[2] != fd[1] && fd[2] != fd[0])
+		close(fd[2]);
 	if(pidc)
 		sendul(pidc, t->ret);
 
@@ -70,8 +75,8 @@
 }
 
 void
-procexecl(Channel *pidc, char *f, ...)
+procexecl(Channel *pidc, int fd[3], char *f, ...)
 {
-	procexec(pidc, f, &f+1);
+	procexec(pidc, fd, f, &f+1);
 }
 
diff --git a/src/libthread/main.c b/src/libthread/main.c
index 06c1293..97a6154 100644
--- a/src/libthread/main.c
+++ b/src/libthread/main.c
@@ -37,7 +37,7 @@
 	_systhreadinit();
 	_qlockinit(_threadrendezvous);
 	_sysfatal = _threadsysfatal;
-//	notify(_threadnote);
+	notify(_threadnote);
 	if(mainstacksize == 0)
 		mainstacksize = 32*1024;
 
@@ -98,6 +98,7 @@
 			break;
 		}
 	}
+	_threadprocs--;
 	unlock(&_threadpq.lock);
 
 	strncpy(ex, p->exitstr, sizeof ex);
diff --git a/src/libthread/note.c b/src/libthread/note.c
index b7f4b13..b25f2b2 100644
--- a/src/libthread/note.c
+++ b/src/libthread/note.c
@@ -2,7 +2,6 @@
 
 int	_threadnopasser;
 
-#ifdef NOTDEF
 #define	NFN		33
 #define	ERRLEN	48
 typedef struct Note Note;
@@ -85,7 +84,7 @@
 	Note *n;
 
 	_threaddebug(DBGNOTE, "Got note %s", s);
-	if(strncmp(s, "sys:", 4) == 0)
+	if(strncmp(s, "sys:", 4) == 0 && strcmp(s, "sys: write on closed pipe") != 0)
 		noted(NDFLT);
 
 //	if(_threadexitsallstatus){
@@ -112,7 +111,6 @@
 		delayednotes(p, v);
 	noted(NCONT);
 }
-#endif
 
 int
 _procsplhi(void)
diff --git a/src/libthread/sched.c b/src/libthread/sched.c
index d6af1c7..d85a76e 100644
--- a/src/libthread/sched.c
+++ b/src/libthread/sched.c
@@ -98,17 +98,18 @@
 	q = &p->ready;
 	lock(&p->readylock);
 	if(q->head == nil){
-		q->asleep = 1;
 		if(p->idle){
 			if(p->idle->state != Ready){
 				fprint(2, "everyone is asleep\n");
 				exits("everyone is asleep");
 			}
 			unlock(&p->readylock);
+			_threaddebug(DBGSCHED, "running idle thread", p->nthreads);
 			return p->idle;
 		}
 
 		_threaddebug(DBGSCHED, "sleeping for more work (%d threads)", p->nthreads);
+		q->asleep = 1;
 		unlock(&p->readylock);
 		while(rendezvous((ulong)q, 0) == ~0){
 			if(_threadexitsallstatus)
@@ -148,7 +149,7 @@
 			_threaddelproc();
 			_schedexit(p);
 		}
-	//	_threaddebug(DBGSCHED, "running %d.%d", t->proc->pid, t->id);
+		_threaddebug(DBGSCHED, "running %d.%d", t->proc->pid, t->id);
 		p->thread = t;
 		if(t->moribund){
 			_threaddebug(DBGSCHED, "%d.%d marked to die");
@@ -176,8 +177,10 @@
 {
 	Tqueue *q;
 
-	if(t == t->proc->idle)
+	if(t == t->proc->idle){
+		_threaddebug(DBGSCHED, "idle thread is ready");
 		return;
+	}
 
 	assert(t->state == Ready);
 	_threaddebug(DBGSCHED, "readying %d.%d", t->proc->pid, t->id);
@@ -206,18 +209,25 @@
 _threadidle(void)
 {
 	Tqueue *q;
-	Thread *t;
+	Thread *t, *idle;
 	Proc *p;
 
 	p = _threadgetproc();
 	q = &p->ready;
 	lock(&p->readylock);
-	assert(q->head);
-	t = q->head;
-	q->head = t->next;
-	if(q->tail == t)
+	assert(q->tail);
+	idle = q->tail;
+	if(q->head == idle){
+		q->head = nil;
 		q->tail = nil;
-	p->idle = t;
+	}else{
+		for(t=q->head; t->next!=q->tail; t=t->next)
+			;
+		t->next = nil;
+		q->tail = t;
+	}
+	p->idle = idle;
+	_threaddebug(DBGSCHED, "p->idle is %d\n", idle->id);
 	unlock(&p->readylock);
 }
 
diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h
index aa69845..0dd1e87 100644
--- a/src/libthread/threadimpl.h
+++ b/src/libthread/threadimpl.h
@@ -105,6 +105,7 @@
 	char		*prog;
 	char		**args;
 	int		fd[2];
+	int		*stdfd;
 };
 
 struct Proc
@@ -214,4 +215,5 @@
 extern int _threadgetpid(void);
 extern void _threadmemset(void*, int, int);
 extern void _threaddebugmemset(void*, int, int);
+extern int _threadprocs;