Commit Diff


commit - ac244f8d287a6119155ea672c8fd13c487c5e4c7
commit + 32f69c36e0eec1227934bbd34854bfebd88686f2
blob - 411bcef2dc8dcdafbe7c838331f4b424d064f9e6
blob + fb45d3e443198facb1a26c88517eea22aaa795b7
--- bin/B
+++ bin/B
@@ -6,6 +6,12 @@ then
 	exit 1
 fi
 
+for i
+do
+	plumb $i
+done
+exit 0
+
 if [ "x$DISPLAY" = "x" ]
 then
 	sam="/tmp/.sam.$USER"
blob - f85487bd7af777d557aab3eaa0a53cb73dbe237d
blob + 893b05044a117aa4299923210118859bd0e49d52
--- include/fcall.h
+++ include/fcall.h
@@ -40,6 +40,7 @@ struct	Fcall
 	char	*data;		/* Twrite, Rread */
 	ushort	nstat;		/* Twstat, Rstat */
 	uchar	*stat;		/* Twstat, Rstat */
+	int	unixfd;		/* Ropenfd */
 } Fcall;
 
 
@@ -100,6 +101,9 @@ enum
 	Twstat =	126,
 	Rwstat,
 	Tmax,
+
+	Topenfd = 	98,
+	Ropenfd,
 };
 
 uint	convM2S(uchar*, uint, Fcall*);
blob - 219b2774a5a98dbd58e8e9b4ee871215fec510fa
blob + 384427091b538e0832f96ee31998a4d1a03bfeda
--- include/fs.h
+++ include/fs.h
@@ -12,7 +12,7 @@ typedef struct Fsys Fsys;
 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 @@ struct Dir *fsdirfstat(Fid*);
 int fsdirwstat(Fsys*, char*, struct Dir*);
 int fsdirfwstat(Fid*, struct Dir*);
 Fid *fsroot(Fsys*);
+Fsys *nsmount(char*, char*);
 
 #ifdef __cplusplus
 }
blob - ce5187cdb5a3f633f69dbf128f9404f342db3e8d
blob + de082499cd29888671616f284a6fea17aa7f7ecf
--- include/lib9.h
+++ include/lib9.h
@@ -194,6 +194,7 @@ extern	int	isupperrune(Rune);
  *
 extern	void*	malloc(ulong);
  */
+extern	void*	p9malloc(ulong);
 extern	void*	mallocz(ulong, int);
 /*
 extern	void	free(void*);
@@ -208,6 +209,9 @@ extern	ulong	getrealloctag(void*);
 /*
 extern	void*	malloctopoolblock(void*);
 */
+#ifndef NOPLAN9DEFINES
+#define	malloc	p9malloc
+#endif
 
 /*
  * print routines (provided by <fmt.h>)
@@ -442,6 +446,7 @@ extern	void	exits(char*);
 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 @@ extern	long	time(long*);
 #define	longjmp		p9longjmp
 #undef  setjmp
 #define setjmp		p9setjmp
+#define putenv		p9putenv
 #define notejmp		p9notejmp
 #define jmp_buf		p9jmp_buf
 #endif
@@ -728,14 +734,14 @@ struct IOchunk
 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	unmount(char*, char*);
 */
 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 @@ extern	ulong	rendezvous(ulong, ulong);
 #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	long	dirreadall(int, Dir**);
 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
blob - e9dc96e031bc83452a1ba63890272997a0a4a26f
blob + 0eb02b5291e09555a3a08f92682fa97b74a4cc1c
--- include/thread.h
+++ include/thread.h
@@ -79,8 +79,8 @@ int		nbsendul(Channel *c, unsigned long v);
 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);
blob - a065799e50b210871a42023198f377a7b91f9c1a
blob + 918cc51b12f20057a0e9fc5d864ed3eb0217c8bd
--- plumb/basic
+++ plumb/basic
@@ -38,7 +38,8 @@ data matches '[a-zA-Z¡-￿0-9_\-./]+'
 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_\-./]+'
 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 @@ arg isfile	$1
 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 @@ arg isfile	/usr/include/$1
 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 @@ arg isfile	/usr/local/include/$1
 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 @@ arg isfile	/usr/local/plan9/include/$1
 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 @@ arg isfile	/sys/module/$1
 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 @@ plumb start rc -c 'man '$2' '$1' >[2=1] | plumb -i -d 
 
 # 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 (.*)'
blob - 7c018c34b542cbfe666ab884688d000234dd6040
blob + e94817460c1546239e58389fd1ed8431ca873adb
--- src/cmd/9p.c
+++ src/cmd/9p.c
@@ -11,7 +11,9 @@ usage(void)
 	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 @@ usage(void)
 
 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 @@ struct {
 } cmds[] = {
 	"read", xread,
 	"write", xwrite,
+	"readfd", xreadfd,
+	"writefd", xwritefd,
 	"stat", xstat,
 //	"ls", xls,
 };
@@ -64,7 +70,6 @@ Fsys*
 xparse(char *name, char **path)
 {
 	int fd;
-	char *ns;
 	char *p;
 	Fsys *fs;
 
@@ -75,22 +80,17 @@ xparse(char *name, char **path)
 		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 @@ xopen(char *name, int mode)
 	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 @@ xread(int argc, char **argv)
 }
 
 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 @@ xwrite(int argc, char **argv)
 }
 
 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;
blob - ad77236e85e4b534256a2917fb0056b00d60c91a
blob + c33beb142926698c48fc59922888ed08c1937136
--- src/cmd/9pserve.c
+++ 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 @@ struct Msg
 	int ref;
 	int ctag;
 	int tag;
+	int isopenfd;
 	Fcall tx;
 	Fcall rx;
 	Fid *fid;
@@ -52,6 +55,8 @@ struct Msg
 struct Conn
 {
 	int fd;
+	int fdmode;
+	Fid *fdfid;
 	int nmsg;
 	int nfid;
 	Channel *inc;
@@ -89,7 +94,7 @@ void *erealloc(void*, int);
 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 tlisten(char*, char*);
 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 @@ usage(void)
 }
 
 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 @@ threadmain(int argc, char **argv)
 	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 @@ threadmain(int argc, char **argv)
 	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,9 +183,24 @@ threadmain(int argc, char **argv)
 	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
 listenthread(void *arg)
 {
@@ -178,10 +211,6 @@ listenthread(void *arg)
 	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 @@ listenthread(void *arg)
 			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 @@ err(Msg *m, char *ename)
 	m->rx.type = Rerror;
 	m->rx.ename = ename;
 	m->rx.tag = m->tx.tag;
-	sendmsg(m);
+	send9pmsg(m);
 }
 
 void
@@ -250,7 +283,7 @@ connthread(void *arg)
 	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 @@ connthread(void *arg)
 				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 @@ connthread(void *arg)
 			}
 			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 @@ connthread(void *arg)
 			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 @@ connthread(void *arg)
 			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 @@ connthread(void *arg)
 			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 @@ connthread(void *arg)
 			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 @@ out:
 	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 @@ connoutthread(void *arg)
 	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 @@ connoutthread(void *arg)
 		}
 		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 @@ outputthread(void *arg)
 		msgput(m);
 	}
 	closeioproc(io);
+	fprint(2, "output eof\n");
+	threadexitsall(0);
 }	
 
 void
@@ -483,6 +684,7 @@ inputthread(void *arg)
 	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 @@ inputthread(void *arg)
 			sendq(m->c->outq, m);
 	}
 	closeioproc(io);
+	fprint(2, "input eof\n");
+	threadexitsall(0);
 }
 
 void*
@@ -626,15 +830,20 @@ msgput(Msg *m)
 	m->c->nmsg--;
 	m->c = nil;
 	fidput(m->fid);
+	m->fid = nil;
 	fidput(m->afid);
-	fidput(m->newfid);
-	free(m->tpkt);
-	free(m->rpkt);
-	m->fid = nil;
 	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 @@ msgget(int n)
 	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 @@ read9ppkt(Ioproc *io, int fd)
 		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 @@ mread9p(Ioproc *io, int fd)
 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 @@ mwrite9p(Ioproc *io, int fd, uchar *pkt)
 		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 @@ rewritehdr(Fcall *f, uchar *pkt)
 
 #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
-iowrite(Ioproc *io, int fd, void *v, long n)
+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);
 
-	xxx;
+	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)
+{
+	long tot, m;
+	uchar *u;
+
+	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
blob - ef51d864b35a4acabeaf6aeb2ecc7ffe8f184d2c
blob + cfee0013b4a7b8b9bca1152733fb20d01d380ed2
--- src/cmd/9term/9term.c
+++ src/cmd/9term/9term.c
@@ -1212,7 +1212,6 @@ rcstart(int fd[2], int argc, char **argv)
 		argv[1] = "-i";
 		argv[2] = 0;
 	}
-
 	/*
 	 * fd0 is slave (tty), fd1 is master (pty)
 	 */
@@ -1222,7 +1221,7 @@ rcstart(int fd[2], int argc, char **argv)
 
         switch(pid = fork()) {
 	case 0:
-		putenv("TERM=9term");
+		putenv("TERM", "9term");
 		close(fd[1]);
 		setsid();
 	//	tcsetpgrp(0, pid);
@@ -1238,6 +1237,7 @@ rcstart(int fd[2], int argc, char **argv)
 		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 @@ scroll(int but)
 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");
 }
 
blob - d7d4a6d0d35270fc7726a6b5c79b78f1a3aea7b4
blob + 2706dda660e8c3e0aa08cfee5fcb0677505f5676
--- src/cmd/9term/mkfile
+++ src/cmd/9term/mkfile
@@ -9,5 +9,5 @@ OFILES=\
 
 <$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
 
blob - 21967de19ed732aa509ce4b4e420870179fcd753
blob + daff401d89bcc7f8b152fbd4505d65a75851bb63
--- src/cmd/dc.c
+++ src/cmd/dc.c
@@ -21,6 +21,7 @@ typedef	void*	pointer;
 #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
blob - dccd3037022755943512cee76eadb33c3947a592
blob + 8cbf6aac077348accfa714b9d871c472b25009f9
--- src/cmd/dict/dict.c
+++ src/cmd/dict/dict.c
@@ -59,18 +59,13 @@ void
 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 @@ main(int argc, char **argv)
 	}
 	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);
blob - 8e4db9e9785de8e42622c245fc7d9b9114939a76
blob + 6916d5492f1222c09953fcefd26ae29428602bdf
--- src/cmd/dict/utils.c
+++ 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}
blob - 8450d94bf5833cd02b3a0b0c36ba7c9494d503f7
blob + e2131e777df8a1e39e9d48c05ffa8ac90bc5b489
--- src/cmd/mkfile
+++ src/cmd/mkfile
@@ -2,11 +2,11 @@ PLAN9=../..
 <$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
blob - 6f95a23a8ab6b9d354f809fe41ca29270e2c510e
blob + 912e5ca0235628a823feeca07457db569c03e0b1
--- src/cmd/plumb/fsys.c
+++ 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 Holdq
 
 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 Dirtab dir[NDIR] =
 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 @@ addport(char *port)
 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 @@ fsysproc(void*)
 			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 @@ fsysrespond(Fcall *t, uchar *buf, char *err)
 		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 @@ dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs
 }
 
 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 @@ fsysversion(Fcall *t, uchar *buf, Fid*)
 }
 
 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 @@ fsysattach(Fcall *t, uchar *buf, Fid *f)
 }
 
 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 @@ fsysopen(Fcall *t, uchar *buf, Fid *f)
 	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 @@ fsysopen(Fcall *t, uchar *buf, Fid *f)
 }
 
 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 @@ fsysstat(Fcall *t, uchar *buf, Fid *f)
 }
 
 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 @@ fsysclunk(Fcall *t, uchar *buf, Fid *f)
 			 * 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){
blob - 42a9232ff77a245681a9e294e09e2f18de8a7504
blob + dc1abbb1554cbb6eabbab288eaa7eb2286e491e0
--- src/cmd/plumb/match.c
+++ src/cmd/plumb/match.c
@@ -6,6 +6,7 @@
 #include <plumb.h>
 #include "plumber.h"
 
+/*
 static char*
 nonnil(char *s)
 {
@@ -13,6 +14,7 @@ nonnil(char *s)
 		return "";
 	return s;
 }
+*/
 
 int
 verbis(int obj, Plumbmsg *m, Rule *r)
@@ -44,10 +46,10 @@ setvar(Resub rs[10], char *match[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 @@ clickmatch(Reprog *re, char *text, Resub rs[10], int c
 	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 @@ verbmatches(int obj, Plumbmsg *m, Rule *r, Exec *e)
 		}
 		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 @@ verbmatches(int obj, Plumbmsg *m, Rule *r, Exec *e)
 		/* 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 @@ enum
 {
 	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 @@ stackargv(char **inargv, char *argv[NARGS+1], char arg
 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");
 }
 
blob - d6a146541af7fba17ccb02ce4329d7bcd78ea783
blob + 6550387f85461e005767459d452ab22f892c9d82
--- src/cmd/plumb/mkfile
+++ src/cmd/plumb/mkfile
@@ -1,11 +1,10 @@
-</$objtype/mkfile
+PLAN9=../../..
+<$PLAN9/src/mkhdr
 
 TARG=plumber plumb
 
+<$PLAN9/src/mkmany
 
-BIN=/$objtype/bin
-</sys/src/cmd/mkmany
-
 PLUMBER=plumber.$O fsys.$O match.$O rules.$O
 PLUMB=plumb.$O
 
@@ -15,6 +14,4 @@ $PLUMB:		$HFILES
 $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
blob - d0bd9c14e33ed9cadeeefc12c3ac6cd5573d1bc1
blob + 424469f2d2ee2405dfbc1e8a71b0a388d5a785bb
--- src/cmd/plumb/plumber.c
+++ 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 @@ threadmain(int argc, char *argv[])
 	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){
blob - 0d1205f988acdf04ae3d7e1ed02b176948ad500d
blob + 4b9267a5fdc6e88a02df6a08a8a8b4144d3d7572
--- src/cmd/plumb/plumber.h
+++ src/cmd/plumb/plumber.h
@@ -91,3 +91,4 @@ jmp_buf	parsejmp;
 char		*lasterror;
 char		**ports;
 int		nports;
+int		debug;
blob - 262f6d67f53928c16aeccc2964e46cc974dc3fbe
blob + b51bb61f013721a49727787ab58810b3c5d88594
--- src/cmd/plumb/rules.c
+++ src/cmd/plumb/rules.c
@@ -143,7 +143,7 @@ char*
 getline(void)
 {
 	static int n = 0;
-	static char *s, *incl;
+	static char *s /*, *incl*/;
 	int c, i;
 
 	i = 0;
@@ -414,7 +414,7 @@ include(char *s)
 	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);
 	}
blob - 1b250ba1143f27c911cda5237b0dec98887fffb0
blob + abb66cc387c221526b83a496130ad3f44f53ed38
--- src/cmd/rc/plan9ish.c
+++ src/cmd/rc/plan9ish.c
@@ -27,6 +27,8 @@ char *syssigname[]={
 char*
 Rcmain(void)
 {
+	return "#9/rcmain";
+/*
 	static char buf[256];
 	char *root;
 
@@ -35,9 +37,10 @@ Rcmain(void)
 		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);
blob - 45cfc3bcaf7d66e6f6414137653ab58e04ef26ca
blob + b8a67b9024ef885d38516e04c370130a801834d2
--- src/cmd/sam/unix.c
+++ src/cmd/sam/unix.c
@@ -212,94 +212,6 @@ erealloc(void *p, ulong n)
 	if(p == 0)
 		panic("realloc fails");
 	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
-
blob - 05e9d5891d7fa3ef644fc88c7e9a60ce8d472120
blob + d9e307619d24cd90cf733442c03487d6f657867b
--- src/cmd/yacc.c
+++ 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 @@ void
 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)
blob - 04c712d9b27b96457928dfe0f9836cd075ffec51
blob + 9f07bd2293c96907db947cf5003832c035491786
--- src/lib9/announce.c
+++ 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 @@ p9announce(char *addr, char *dir)
 	if(proto == SOCK_STREAM){
 		listen(s, 8);
 		putfd(dir, s);
-print("announce dir: %s\n", dir);
 	}
 	return s;
 
@@ -95,9 +95,21 @@ Unix:
 		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 @@ p9listen(char *dir, char *newdir)
 {
 	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 @@ p9accept(int cfd, char *dir)
 {
 	int fd;
 
-	if((fd = getfd(dir)) < 0){
+	if((fd = _p9netfd(dir)) < 0){
 		werrstr("bad 'directory' in accept");
 		return -1;
 	}
blob - 89c695ae172305087ae1801140ee1b19ff13f080
blob + 5f2d58bad578f09233321a9f0b30f42cc650d067
--- src/lib9/await.c
+++ src/lib9/await.c
@@ -45,6 +45,7 @@ static struct {
 #endif
 	SIGUSR1,		"sys: usr1",
 	SIGUSR2,		"sys: usr2",
+	SIGPIPE,		"sys: write on closed pipe",
 };
 	
 char*
blob - fcdcd42d60f6f0d533747aab47b01af7651cf926
blob + 920775ea12606a6f99ea8adf3823349d454cc009
--- src/lib9/convM2S.c
+++ src/lib9/convM2S.c
@@ -138,6 +138,7 @@ convM2S(uchar *ap, uint nap, Fcall *f)
 		break;
 
 	case Topen:
+	case Topenfd:
 		if(p+BIT32SZ+BIT8SZ > ep)
 			return 0;
 		f->fid = GBIT32(p);
@@ -260,6 +261,7 @@ convM2S(uchar *ap, uint nap, Fcall *f)
 		break;
 
 	case Ropen:
+	case Ropenfd:
 	case Rcreate:
 		p = gqid(p, ep, &f->qid);
 		if(p == nil)
@@ -268,6 +270,12 @@ convM2S(uchar *ap, uint nap, Fcall *f)
 			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:
blob - 9acdcfa567fbb744c66980c00bea5397ad895f98
blob + 6e9d2719270832bb2d985b0475197eacc8939290
--- src/lib9/convS2M.c
+++ src/lib9/convS2M.c
@@ -92,6 +92,7 @@ sizeS2M(Fcall *f)
 		break;
 
 	case Topen:
+	case Topenfd:
 		n += BIT32SZ;
 		n += BIT8SZ;
 		break;
@@ -164,6 +165,12 @@ sizeS2M(Fcall *f)
 		n += BIT32SZ;
 		break;
 
+	case Ropenfd:
+		n += QIDSZ;
+		n += BIT32SZ;
+		n += BIT32SZ;
+		break;
+
 	case Rread:
 		n += BIT32SZ;
 		n += f->count;
@@ -257,6 +264,7 @@ convS2M(Fcall *f, uchar *ap, uint nap)
 		break;
 
 	case Topen:
+	case Topenfd:
 		PBIT32(p, f->fid);
 		p += BIT32SZ;
 		PBIT8(p, f->mode);
@@ -347,9 +355,14 @@ convS2M(Fcall *f, uchar *ap, uint nap)
 
 	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:
blob - abef0c3515d2456d073ff03f3eaacde7f161532e
blob + bdad5f6a0c8f8e69a6b0f594255deb43adc6ccf7
--- src/lib9/create.c
+++ 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;
 }
blob - 4eef88dece82b5c102c1a4deb66119bfaff3e2a3
blob + 592316fe7db49a1445e66472a547e79df7448c3a
--- src/lib9/fcallfmt.c
+++ src/lib9/fcallfmt.c
@@ -74,9 +74,16 @@ fcallfmt(Fmt *fmt)
 		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;
blob - c6ff71605ee17216a5d8b68a00c1d3541c53610f
blob + 2a2d1391338543560e7cb5209cfa0016dc0c26b2
--- src/lib9/getenv.c
+++ src/lib9/getenv.c
@@ -13,3 +13,15 @@ p9getenv(char *s)
 	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;
+}
blob - 14d8c0e080846b16b1a2c6870df2eec872cfb99d
blob + a0e75fc36fa1a31463a56aba4f7c2070ca97e24b
--- src/lib9/mkfile
+++ src/lib9/mkfile
@@ -8,6 +8,8 @@ OFILES=\
 	_p9dialparse.$O\
 	_p9dir.$O\
 	_p9proc.$O\
+	_p9translate.$O\
+	access.$O\
 	announce.$O\
 	argv0.$O\
 	atexit.$O\
@@ -40,11 +42,13 @@ OFILES=\
 	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 @@ OFILES=\
 	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\
blob - 460eabfe40a1407a1cf68ac3c00879cf7f7c0532
blob + 160755d0b91ba317d7f5585acc8f94be22da492a
--- src/lib9/notify.c
+++ src/lib9/notify.c
@@ -19,7 +19,7 @@ static int sigs[] = {
 #endif
 	SIGFPE,
 	SIGBUS,
-	SIGSEGV,
+/*	SIGSEGV,	*/
 	SIGSYS,
 	SIGPIPE,
 	SIGALRM,
blob - 270c3cddf04043ca3151ea2bccf28094132b0cca
blob + f3a21928dd0278596dc931e5cd69f8727cb82758
--- src/lib9/rfork.c
+++ src/lib9/rfork.c
@@ -9,7 +9,7 @@ p9rfork(int flags)
 	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 @@ p9rfork(int flags)
 		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;
blob - a1ff278af9f2da9a6613a262f607e780d7a749ea
blob + 0b3dc253a836db332ecb2d7268f20375f5bfb8f2
--- src/libdraw/openfont.c
+++ src/libdraw/openfont.c
@@ -9,21 +9,15 @@ openfont(Display *d, char *name)
 {
 	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;
blob - 458efc61fdee4de8974315802a1ecd8d0fc6a274
blob + 9a6585cbc7a745226798e17d0bd4098a433c9300
--- src/libdraw/x11-alloc.c
+++ 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 @@ xallocmemimage(Rectangle r, u32int chan, int pixmap)
 Memimage*
 allocmemimage(Rectangle r, u32int chan)
 {
-	return xallocmemimage(r, chan, PMundef);
+	return _xallocmemimage(r, chan, PMundef);
 }
 
 void
blob - 25a325c62a620e81793a140fff542176f01b04b5
blob + 7a84a98372349917c3102caad717f84270423b52
--- src/libdraw/x11-cload.c
+++ src/libdraw/x11-cload.c
@@ -13,7 +13,7 @@ cloadmemimage(Memimage *i, Rectangle r, uchar *data, i
 
 	n = _cloadmemimage(i, r, data, ndata);
 	if(n > 0 && i->X)
-		xputxdata(i, r);
+		_xputxdata(i, r);
 	return n;
 }
 
blob - 97438740e899e297802ca550fcf6ef45aa59e4aa
blob + 6373e4e3a537da13ea99a3ad9e2dc87454b7bfb9
--- src/libdraw/x11-draw.c
+++ src/libdraw/x11-draw.c
@@ -24,11 +24,11 @@ memimagedraw(Memimage *dst, Rectangle r, Memimage *src
 
 	/* 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 @@ memimagedraw(Memimage *dst, Rectangle r, Memimage *src
 		return;
 
 	/* put bits back on x server */
-	xputxdata(dst, par->r);
+	_xputxdata(dst, par->r);
 }
 
 static int
@@ -66,7 +66,7 @@ xdraw(Memdrawparam *par)
 	 */
 	m = Simplesrc|Simplemask|Fullmask;
 	if((state&m) == m){
-		xfillcolor(dst, r, par->sdval);
+		_xfillcolor(dst, r, par->sdval);
 	//	xdirtyxdata(dst, r);
 		return 1;
 	}
blob - 408eb4191aba4f1c8d5f257e4bb49d95b407eab6
blob + ba9d031b6914ca710cf29fb432a232c260cec238
--- src/libdraw/x11-event.c
+++ src/libdraw/x11-event.c
@@ -48,7 +48,7 @@ eread(ulong keys, Event *e)
 		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 @@ again:
 
 	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 @@ ecanmouse(void)
 	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 @@ ecankbd(void)
 	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 @@ again:
 void
 emoveto(Point p)
 {
-	xmoveto(p);
+	_xmoveto(p);
 }
 
 void
 esetcursor(Cursor *c)
 {
-	xsetcursor(c);
+	_xsetcursor(c);
 }
 
blob - ff0b2e86527813a32cc37367cbf08b71c2f43600
blob + 33fc6a2ad145cb730ca027728242291c77240174
--- src/libdraw/x11-fill.c
+++ src/libdraw/x11-fill.c
@@ -13,13 +13,13 @@ memfillcolor(Memimage *m, u32int val)
 	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;
blob - a6d4b12ecc727f9c608327bc3dfb26c37433a6bd
blob + 693b2938deb86e4388dd6611c38a3d49ad831da0
--- src/libdraw/x11-get.c
+++ src/libdraw/x11-get.c
@@ -16,7 +16,7 @@ addrect(Rectangle *rp, Rectangle r)
 }
 
 XImage*
-xgetxdata(Memimage *m, Rectangle r)
+_xgetxdata(Memimage *m, Rectangle r)
 {
 	int x, y;
 	uchar *p;
@@ -55,7 +55,7 @@ xgetxdata(Memimage *m, Rectangle r)
 }
 
 void
-xputxdata(Memimage *m, Rectangle r)
+_xputxdata(Memimage *m, Rectangle r)
 {
 	int offset, x, y;
 	uchar *p;
@@ -97,7 +97,7 @@ xputxdata(Memimage *m, Rectangle r)
 }
 
 void
-xdirtyxdata(Memimage *m, Rectangle r)
+_xdirtyxdata(Memimage *m, Rectangle r)
 {
 	Xmem *xm;
 
blob - 6f87b41241a6ba1e1324dbb40a3dfdabf7a614d4
blob + fb6a91449fbfb31e090eb0b1dfd2eda2d6dac777
--- src/libdraw/x11-init.c
+++ src/libdraw/x11-init.c
@@ -359,7 +359,7 @@ xattach(char *label)
 	_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 @@ flushmemscreen(Rectangle r)
 }
 
 void
-xexpose(XEvent *e, XDisplay *xd)
+_xexpose(XEvent *e, XDisplay *xd)
 {
 	XExposeEvent *xe;
 	Rectangle r;
@@ -673,7 +673,7 @@ xexpose(XEvent *e, XDisplay *xd)
 }
 
 int
-xdestroy(XEvent *e, XDisplay *xd)
+_xdestroy(XEvent *e, XDisplay *xd)
 {
 	XDestroyWindowEvent *xe;
 
@@ -686,7 +686,7 @@ xdestroy(XEvent *e, XDisplay *xd)
 }
 
 int
-xconfigure(XEvent *e, XDisplay *xd)
+_xconfigure(XEvent *e, XDisplay *xd)
 {
 	Rectangle r;
 	XConfigureEvent *xe = (XConfigureEvent*)e;
@@ -719,7 +719,7 @@ xreplacescreenimage(void)
 		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;
blob - 0c9924499c38d1708611fe582620a6de315af31d
blob + 8e72b011e66d53e9ea0a2a29a7d27f70adea3384
--- src/libdraw/x11-itrans.c
+++ 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 @@ xtoplan9latin1(XEvent *e)
 	int n;
 	int r;
 
-	r = _xtoplan9kbd(e);
+	r = __xtoplan9kbd(e);
 	if(r < 0)
 		return nil;
 	if(alting){
@@ -156,7 +156,7 @@ xtoplan9latin1(XEvent *e)
 }
 
 int
-xtoplan9kbd(XEvent *e)
+_xtoplan9kbd(XEvent *e)
 {
 	static Rune *r;
 
@@ -173,7 +173,7 @@ xtoplan9kbd(XEvent *e)
 }
 
 int
-xtoplan9mouse(XDisplay *xd, XEvent *e, Mouse *m)
+_xtoplan9mouse(XDisplay *xd, XEvent *e, Mouse *m)
 {
 	int s;
 	XButtonEvent *be;
@@ -260,7 +260,7 @@ xtoplan9mouse(XDisplay *xd, XEvent *e, Mouse *m)
 }
 
 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 @@ xcursorarrow(void)
 
 
 void
-xsetcursor(Cursor *c)
+_xsetcursor(Cursor *c)
 {
 	XColor fg, bg;
 	XCursor xc;
@@ -335,7 +335,7 @@ struct {
 } clip;
 
 char*
-xgetsnarf(XDisplay *xd)
+_xgetsnarf(XDisplay *xd)
 {
 	uchar *data, *xdata;
 	Atom clipboard, type, prop;
@@ -420,7 +420,7 @@ out:
 }
 
 void
-xputsnarf(XDisplay *xd, char *data)
+_xputsnarf(XDisplay *xd, char *data)
 {
 	XButtonEvent e;
 
@@ -445,7 +445,7 @@ xputsnarf(XDisplay *xd, char *data)
 }
 
 int
-xselect(XEvent *e, XDisplay *xd)
+_xselect(XEvent *e, XDisplay *xd)
 {
 	char *name;
 	XEvent r;
@@ -493,12 +493,12 @@ if(0) fprint(2, "xselect target=%d requestor=%d proper
 void
 putsnarf(char *data)
 {
-	xputsnarf(_x.snarfcon, data);
+	_xputsnarf(_x.snarfcon, data);
 }
 
 char*
 getsnarf(void)
 {
-	return xgetsnarf(_x.snarfcon);
+	return _xgetsnarf(_x.snarfcon);
 }
 
blob - 443074e994b4c4c3aa1bda37854f6465bd26d297
blob + 676027e306e4a39cdabadc9358c5a9d2f010eb1b
--- src/libdraw/x11-keyboard.c
+++ src/libdraw/x11-keyboard.c
@@ -46,12 +46,12 @@ _ioproc(void *arg)
 		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);
 			}
blob - e4e32bf2419d2bbb9d58d22c8fd1f40195ea9004
blob + 5292275a13fb531047f2814faa0092c6f7ef7c2a
--- src/libdraw/x11-load.c
+++ src/libdraw/x11-load.c
@@ -13,7 +13,7 @@ loadmemimage(Memimage *i, Rectangle r, uchar *data, in
 
 	n = _loadmemimage(i, r, data, ndata);
 	if(n > 0 && i->X)
-		xputxdata(i, r);
+		_xputxdata(i, r);
 	return n;
 }
 
blob - afd47ccc4d52fa961b239054a0ab4ad6f5f4e01e
blob + 1e84b92638553d829c764a46a9a95480f6c1189a
--- src/libdraw/x11-memdraw.h
+++ src/libdraw/x11-memdraw.h
@@ -76,26 +76,26 @@ struct Xprivate {
 
 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|\
blob - eae26788d834cb330f0def04e26418fbd7a0afb1
blob + 9e5143c65d8e0cbc938fb5f6fd3b5ea3d0b5b050
--- src/libdraw/x11-mouse.c
+++ src/libdraw/x11-mouse.c
@@ -11,7 +11,7 @@
 void
 moveto(Mousectl *m, Point pt)
 {
-	xmoveto(pt);
+	_xmoveto(pt);
 }
 
 void
@@ -64,10 +64,10 @@ _ioproc(void *arg)
 		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 @@ _ioproc(void *arg)
 			}
 			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 @@ initmouse(char *file, Image *i)
 void
 setcursor(Mousectl *mc, Cursor *c)
 {
-	xsetcursor(c);
+	_xsetcursor(c);
 }
 
blob - 8635b0ba456d044c252552f6971bced7df24bf11
blob + 22dfc60634848e17d826195a65799e254b16e8f2
--- src/libdraw/x11-pixelbits.c
+++ src/libdraw/x11-pixelbits.c
@@ -10,7 +10,7 @@ u32int
 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);
 }
 
blob - 3e8a635c1bc04d579c4fab8acb331156a6229fd6
blob + 471ca880952441f5538d748a35badec54d58b791
--- src/libdraw/x11-unload.c
+++ src/libdraw/x11-unload.c
@@ -10,7 +10,7 @@ int
 unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
 {
 	if(i->X)
-		xgetxdata(i, r);
+		_xgetxdata(i, r);
 	return _unloadmemimage(i, r, data, ndata);
 }
 
blob - b34df3406ca2bac4e14f258e9c9086f92efedfa5
blob + 91d4af174d2f2410d8292e1e01538920883e046e
--- src/libfs/fs.c
+++ src/libfs/fs.c
@@ -50,7 +50,7 @@ fsroot(Fsys *fs)
 }
 
 Fsys*
-fsmount(int fd)
+fsmount(int fd, char *aname)
 {
 	int n;
 	char *user;
@@ -62,13 +62,14 @@ fsmount(int fd)
 	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 @@ fsmount(int fd)
 void
 fsunmount(Fsys *fs)
 {
+	fsclose(fs->root);
+	fs->root = nil;
 	_fsdecref(fs);
 }
 
@@ -85,7 +88,9 @@ _fsdecref(Fsys *fs)
 	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 @@ fsversion(Fsys *fs, int msize, char *version, int nver
 	void *freep;
 	Fcall tx, rx;
 
+	tx.tag = 0;
 	tx.type = Tversion;
 	tx.version = version;
 	tx.msize = msize;
@@ -120,9 +126,13 @@ fsattach(Fsys *fs, Fid *afid, char *user, char *aname)
 	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 @@ fsrpc(Fsys *fs, Fcall *tx, Fcall *rx, void **freep)
 
 	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 @@ fprint(2, "tpkt %p\n", tpkt);
 		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 @@ _fsgetfid(Fsys *fs)
 			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 @@ _fsrecv(Mux *mux)
 {
 	uchar *pkt;
 	uchar buf[4];
-	int n;
+	int n, nfd;
 	Fsys *fs;
 
 	fs = mux->aux;
@@ -277,11 +284,13 @@ _fsrecv(Mux *mux)
 		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;
 }
blob - acfb0ae5990ba8fdbdfb4eba69913ff94ec5c756
blob + d4c8b49fe3573074cf606e298951aa860616f2e9
--- src/libfs/mkfile
+++ src/libfs/mkfile
@@ -8,7 +8,9 @@ OFILES=\
 	create.$O\
 	dirread.$O\
 	fs.$O\
+	ns.$O\
 	open.$O\
+	openfd.$O\
 	read.$O\
 	stat.$O\
 	walk.$O\
blob - b1fdeb039b6eb327e7220d6b9431179dc2b1d22c
blob + bc63260249f41bc3b75fd6e1d41d3b3f92f62a69
--- src/libmux/mux.c
+++ src/libmux/mux.c
@@ -173,6 +173,5 @@ puttag(Mux *mux, Muxrpc *r)
 	mux->nwait--;
 	mux->freetag = i;
 	rwakeup(&mux->tagrend);
-fprint(2, "free %p\n", r);
 	free(r);
 }
blob - 8678d753fa954b55073739bc5a4cecf6d72c9be7
blob + fcade7f4c9a0885db50e98668d0582b14e1d6642
--- src/libplumb/mesg.c
+++ 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 @@ char *home;
 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
blob - 7cad85cc70b800972d718bd7e66983e8ee588280
blob + 074556f9b50819c8d8525aefdd419a2851cdbb92
--- src/libthread/asm-FreeBSD-386.s
+++ src/libthread/asm-FreeBSD-386.s
@@ -41,9 +41,9 @@ _xdec:
 	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
 
blob - 55f6c60cfb1ae78885bd52ace003667003cdfea8
blob + d487e1954f41b7fb187d8384770f7e1185f84de4
--- src/libthread/create.c
+++ src/libthread/create.c
@@ -1,9 +1,8 @@
 #include "threadimpl.h"
 
 Pqueue _threadpq;
+int _threadprocs;
 
-int _threadmultiproc;
-
 static int nextID(void);
 
 /*
@@ -90,7 +89,6 @@ proccreate(void (*f)(void*), void *arg, uint stacksize
 		werrstr("cannot create procs once there is an idle thread");
 		return -1;
 	}
-	_threadmultiproc = 1;
 	return procrfork(f, arg, stacksize, 0);
 }
 
@@ -125,11 +123,12 @@ threadcreateidle(void (*f)(void *arg), void *arg, uint
 {
 	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 @@ _newproc(void (*f)(void *arg), void *arg, uint stacksi
 	else
 		*_threadpq.tail = p;
 	_threadpq.tail = &p->next;
+	_threadprocs++;
 	unlock(&_threadpq.lock);
 	return p;
 }
blob - ef50bf198e07766922459e71095027eb28da0f41
blob + 97c756079abe0771ca6d34141a4f2078e49d5a71
--- src/libthread/exec-unix.c
+++ 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 @@ procexec(Channel *pidc, char *prog, char *args[])
 	assert(p->needexec==0);
 	p->exec.prog = prog;
 	p->exec.args = args;
+	p->exec.stdfd = fd;
 	p->needexec = 1;
 	_sched();
 
@@ -56,7 +57,11 @@ procexec(Channel *pidc, char *prog, char *args[])
 		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 @@ procexec(Channel *pidc, char *prog, char *args[])
 }
 
 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 @@ efork(void *ve)
 {
 	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')
blob - bcf2080285eade1f75c0d9cd0ac4b465f2f754b6
blob + 0fb68111f178b479e17723fdf9a15107a29a2ea3
--- src/libthread/exec.c
+++ 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 @@ procexec(Channel *pidc, char *prog, char *args[])
 	assert(p->needexec==0);
 	p->exec.prog = prog;
 	p->exec.args = args;
+	p->exec.stdfd = fd;
 	p->needexec = 1;
 	_sched();
 
@@ -61,7 +62,11 @@ procexec(Channel *pidc, char *prog, char *args[])
 		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 @@ procexec(Channel *pidc, char *prog, char *args[])
 }
 
 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);
 }
 
blob - 06c1293545f5cbeb749dc7baaf36a36f6f499195
blob + 97a6154afdb37d552ccf7ef92860b04ed8654f15
--- src/libthread/main.c
+++ src/libthread/main.c
@@ -37,7 +37,7 @@ main(int argc, char **argv)
 	_systhreadinit();
 	_qlockinit(_threadrendezvous);
 	_sysfatal = _threadsysfatal;
-//	notify(_threadnote);
+	notify(_threadnote);
 	if(mainstacksize == 0)
 		mainstacksize = 32*1024;
 
@@ -98,6 +98,7 @@ _schedexit(Proc *p)
 			break;
 		}
 	}
+	_threadprocs--;
 	unlock(&_threadpq.lock);
 
 	strncpy(ex, p->exitstr, sizeof ex);
blob - b7f4b13752ae11ec0a7327b53553dc5ba1751724
blob + b25f2b23c336de568dfbae2a0d0cfea1a7c17c4b
--- src/libthread/note.c
+++ 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 @@ _threadnote(void *v, char *s)
 	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 @@ _threadnote(void *v, char *s)
 		delayednotes(p, v);
 	noted(NCONT);
 }
-#endif
 
 int
 _procsplhi(void)
blob - d6af1c7c0a9977e243f2fad904acaeaa751648ed
blob + d85a76e21ec195ed692e604809e7a7fdb86d9de8
--- src/libthread/sched.c
+++ src/libthread/sched.c
@@ -98,17 +98,18 @@ runthread(Proc *p)
 	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 @@ Resched:
 			_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 @@ _threadready(Thread *t)
 {
 	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 @@ void
 _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);
 }
 
blob - aa69845cda2fc43adc2afa964b6be25538b9e2f6
blob + 0dd1e8704711fef5f0b05ef39d3f7addf41ffe86
--- src/libthread/threadimpl.h
+++ src/libthread/threadimpl.h
@@ -105,6 +105,7 @@ struct Execargs
 	char		*prog;
 	char		**args;
 	int		fd[2];
+	int		*stdfd;
 };
 
 struct Proc
@@ -214,4 +215,5 @@ extern void _stackfree(void*);
 extern int _threadgetpid(void);
 extern void _threadmemset(void*, int, int);
 extern void _threaddebugmemset(void*, int, int);
+extern int _threadprocs;