tAdd support for user-level 9P servers/clients and various bug fixes to go with them. - plan9port - [fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port
Log
Files
Refs
README
LICENSE
---
commit 32f69c36e0eec1227934bbd34854bfebd88686f2
parent ac244f8d287a6119155ea672c8fd13c487c5e4c7
Author: rsc 
Date:   Thu, 11 Dec 2003 17:48:38 +0000

Add support for user-level 9P servers/clients and various bug fixes to go with them.

Diffstat:
  M bin/B                               |       6 ++++++
  M include/fcall.h                     |       4 ++++
  M include/fs.h                        |       3 ++-
  M include/lib9.h                      |      24 +++++++++++++++++++-----
  M include/thread.h                    |       4 ++--
  M plumb/basic                         |      26 ++++++++++++--------------
  M src/cmd/9p.c                        |      88 +++++++++++++++++++++++++------
  M src/cmd/9pserve.c                   |     457 ++++++++++++++++++++++++++++---
  M src/cmd/9term/9term.c               |       8 +++-----
  M src/cmd/9term/mkfile                |       2 +-
  M src/cmd/dc.c                        |       1 +
  M src/cmd/dict/dict.c                 |      11 +++--------
  M src/cmd/dict/utils.c                |     118 ++++++++++++++++----------------
  M src/cmd/mkfile                      |       4 ++--
  M src/cmd/plumb/fsys.c                |      77 ++++++++++++-------------------
  M src/cmd/plumb/match.c               |      32 ++++++++++++++++----------------
  M src/cmd/plumb/mkfile                |      11 ++++-------
  M src/cmd/plumb/plumber.c             |       9 +++++++--
  M src/cmd/plumb/plumber.h             |       1 +
  M src/cmd/plumb/rules.c               |       4 ++--
  M src/cmd/rc/plan9ish.c               |       5 ++++-
  M src/cmd/sam/unix.c                  |      88 -------------------------------
  M src/cmd/yacc.c                      |      19 +++++--------------
  M src/lib9/announce.c                 |      25 +++++++++++++++++--------
  M src/lib9/await.c                    |       1 +
  M src/lib9/convM2S.c                  |       8 ++++++++
  M src/lib9/convS2M.c                  |      13 +++++++++++++
  M src/lib9/create.c                   |      50 +++++++++++++++++++++++++++++--
  M src/lib9/fcallfmt.c                 |       9 ++++++++-
  M src/lib9/getenv.c                   |      12 ++++++++++++
  M src/lib9/mkfile                     |       9 +++++++++
  M src/lib9/notify.c                   |       2 +-
  M src/lib9/rfork.c                    |       9 ++++++---
  M src/libdraw/openfont.c              |      10 ++--------
  M src/libdraw/x11-alloc.c             |       4 ++--
  M src/libdraw/x11-cload.c             |       2 +-
  M src/libdraw/x11-draw.c              |      10 +++++-----
  M src/libdraw/x11-event.c             |      24 ++++++++++++------------
  M src/libdraw/x11-fill.c              |       6 +++---
  M src/libdraw/x11-get.c               |       6 +++---
  M src/libdraw/x11-init.c              |      10 +++++-----
  M src/libdraw/x11-itrans.c            |      22 +++++++++++-----------
  M src/libdraw/x11-keyboard.c          |       4 ++--
  M src/libdraw/x11-load.c              |       2 +-
  M src/libdraw/x11-memdraw.h           |      32 ++++++++++++++++----------------
  M src/libdraw/x11-mouse.c             |      14 +++++++-------
  M src/libdraw/x11-pixelbits.c         |       2 +-
  M src/libdraw/x11-unload.c            |       2 +-
  M src/libfs/fs.c                      |      39 +++++++++++++++++++------------
  M src/libfs/mkfile                    |       2 ++
  M src/libmux/mux.c                    |       1 -
  M src/libplumb/mesg.c                 |      36 ++++++++-----------------------
  M src/libthread/asm-FreeBSD-386.s     |       4 ++--
  M src/libthread/create.c              |       8 ++++----
  M src/libthread/exec-unix.c           |      22 +++++++++++++++++-----
  M src/libthread/exec.c                |      13 +++++++++----
  M src/libthread/main.c                |       3 ++-
  M src/libthread/note.c                |       4 +---
  M src/libthread/sched.c               |      28 +++++++++++++++++++---------
  M src/libthread/threadimpl.h          |       2 ++

60 files changed, 966 insertions(+), 486 deletions(-)
---
diff --git a/bin/B b/bin/B
t@@ -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"
diff --git a/include/fcall.h b/include/fcall.h
t@@ -40,6 +40,7 @@ struct        Fcall
         char        *data;                /* Twrite, Rread */
         ushort        nstat;                /* Twstat, Rstat */
         uchar        *stat;                /* Twstat, Rstat */
+        int        unixfd;                /* Ropenfd */
 } Fcall;
 
 
t@@ -100,6 +101,9 @@ enum
         Twstat =        126,
         Rwstat,
         Tmax,
+
+        Topenfd =         98,
+        Ropenfd,
 };
 
 uint        convM2S(uchar*, uint, Fcall*);
diff --git a/include/fs.h b/include/fs.h
t@@ -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*);
t@@ -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
 }
diff --git a/include/lib9.h b/include/lib9.h
t@@ -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*);
t@@ -208,6 +209,9 @@ extern        ulong        getrealloctag(void*);
 /*
 extern        void*        malloctopoolblock(void*);
 */
+#ifndef NOPLAN9DEFINES
+#define        malloc        p9malloc
+#endif
 
 /*
  * print routines (provided by )
t@@ -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);
t@@ -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
t@@ -728,14 +734,14 @@ struct IOchunk
 extern        void        _exits(char*);
 
 extern        void        abort(void);
-/* extern        int        access(char*, int);  */
+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*);  */
-/* extern        int        chdir(char*);  */
+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*[]);
t@@ -752,9 +758,9 @@ extern        int        unmount(char*, char*);
 */
 extern        int        noted(int);
 extern        int        notify(void(*)(void*, char*));
-/* extern        int        open(char*, int);  */
+extern        int        p9open(char*, int);
 extern        int        fd2path(int, char*, int);
-extern        int        pipe(int*);
+extern        int        p9pipe(int*);
 /* 
  * use defs from 
 extern        long        pread(int, void*, long, vlong);
t@@ -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*);
t@@ -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
diff --git a/include/thread.h b/include/thread.h
t@@ -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);
diff --git a/plumb/basic b/plumb/basic
t@@ -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
t@@ -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
t@@ -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
t@@ -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
t@@ -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
t@@ -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
t@@ -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
t@@ -113,13 +110,14 @@ plumb start rc -c 'man '$2' '$1' >[2=1] | plumb -i -d edit -a ''action=showdata 
 
 # 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
t@@ -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");
t@@ -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**);
 
t@@ -29,6 +33,8 @@ struct {
 } cmds[] = {
         "read", xread,
         "write", xwrite,
+        "readfd", xreadfd,
+        "writefd", xwritefd,
         "stat", xstat,
 //        "ls", xls,
 };
t@@ -64,7 +70,6 @@ Fsys*
 xparse(char *name, char **path)
 {
         int fd;
-        char *ns;
         char *p;
         Fsys *fs;
 
t@@ -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;
 }
 
t@@ -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)
 {
t@@ -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];
t@@ -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;
diff --git a/src/cmd/9pserve.c b/src/cmd/9pserve.c
t@@ -2,6 +2,8 @@
 #include 
 #include 
 #include 
+#include 
+#include 
 
 enum
 {
t@@ -38,6 +40,7 @@ struct Msg
         int ref;
         int ctag;
         int tag;
+        int isopenfd;
         Fcall tx;
         Fcall rx;
         Fid *fid;
t@@ -52,6 +55,8 @@ struct Msg
 struct Conn
 {
         int fd;
+        int fdmode;
+        Fid *fdfid;
         int nmsg;
         int nfid;
         Channel *inc;
t@@ -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*);
t@@ -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)
t@@ -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();
t@@ -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);
t@@ -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;
t@@ -165,7 +183,22 @@ 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
t@@ -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");
t@@ -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;
 
t@@ -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
t@@ -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++;
t@@ -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++;
t@@ -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:
t@@ -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;
t@@ -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 */
                 }
         }
 
t@@ -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;
t@@ -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 */
                 }
         }
 
t@@ -398,7 +446,155 @@ 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
t@@ -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;
t@@ -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");
t@@ -473,6 +672,8 @@ outputthread(void *arg)
                 msgput(m);
         }
         closeioproc(io);
+        fprint(2, "output eof\n");
+        threadexitsall(0);
 }        
 
 void
t@@ -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){
t@@ -514,6 +716,8 @@ inputthread(void *arg)
                         sendq(m->c->outq, m);
         }
         closeioproc(io);
+        fprint(2, "input eof\n");
+        threadexitsall(0);
 }
 
 void*
t@@ -626,15 +830,20 @@ msgput(Msg *m)
         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;
 }
t@@ -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;
 }
t@@ -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;
 }
 
t@@ -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);
t@@ -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;
 }
 
t@@ -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; ic, 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
diff --git a/src/cmd/9term/9term.c b/src/cmd/9term/9term.c
t@@ -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)
          */
t@@ -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);
t@@ -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:
t@@ -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");
 }
 
diff --git a/src/cmd/9term/mkfile b/src/cmd/9term/mkfile
t@@ -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
 
diff --git a/src/cmd/dc.c b/src/cmd/dc.c
t@@ -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
diff --git a/src/cmd/dict/dict.c b/src/cmd/dict/dict.c
t@@ -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){
t@@ -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);
diff --git a/src/cmd/dict/utils.c b/src/cmd/dict/utils.c
t@@ -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
t@@ -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
diff --git a/src/cmd/plumb/fsys.c b/src/cmd/plumb/fsys.c
t@@ -3,14 +3,13 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include "plumber.h"
 
 enum
 {
-        Stack = 8*1024
+        Stack = 32*1024
 };
 
 typedef struct Dirtab Dirtab;
t@@ -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,
 
t@@ -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*);
t@@ -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 */
t@@ -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);
t@@ -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);
 }
t@@ -555,8 +531,10 @@ dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
 }
 
 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;
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;
 }
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; ioldtag);
t@@ -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);
t@@ -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;
 }
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;
 }
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){
diff --git a/src/cmd/plumb/match.c b/src/cmd/plumb/match.c
t@@ -6,6 +6,7 @@
 #include 
 #include "plumber.h"
 
+/*
 static char*
 nonnil(char *s)
 {
t@@ -13,6 +14,7 @@ nonnil(char *s)
                 return "";
         return s;
 }
+*/
 
 int
 verbis(int obj, Plumbmsg *m, Rule *r)
t@@ -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';
         }
 }
t@@ -66,7 +68,7 @@ clickmatch(Reprog *re, char *text, Resub rs[10], int click)
         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;
t@@ -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;
t@@ -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;
t@@ -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 */
t@@ -419,19 +421,17 @@ stackargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
 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
t@@ -1,10 +1,9 @@
-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
t@@ -3,10 +3,10 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include "plumber.h"
 
+int debug;
 char        *plumbfile;
 char *user;
 char *home;
t@@ -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){
diff --git a/src/cmd/plumb/plumber.h b/src/cmd/plumb/plumber.h
t@@ -91,3 +91,4 @@ jmp_buf        parsejmp;
 char                *lasterror;
 char                **ports;
 int                nports;
+int                debug;
diff --git a/src/cmd/plumb/rules.c b/src/cmd/plumb/rules.c
t@@ -143,7 +143,7 @@ char*
 getline(void)
 {
         static int n = 0;
-        static char *s, *incl;
+        static char *s /*, *incl*/;
         int c, i;
 
         i = 0;
t@@ -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);
         }
diff --git a/src/cmd/rc/plan9ish.c b/src/cmd/rc/plan9ish.c
t@@ -27,6 +27,8 @@ char *syssigname[]={
 char*
 Rcmain(void)
 {
+        return "#9/rcmain";
+/*
         static char buf[256];
         char *root;
 
t@@ -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);
diff --git a/src/cmd/sam/unix.c b/src/cmd/sam/unix.c
t@@ -214,92 +214,4 @@ erealloc(void *p, ulong n)
         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 
-
-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
t@@ -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"
t@@ -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)
diff --git a/src/lib9/announce.c b/src/lib9/announce.c
t@@ -5,13 +5,14 @@
 #include 
 #include 
 #include 
+#include 
 
 #undef sun
 #define sun sockun
 extern int _p9dialparse(char*, char**, char**, u32int*, int*);
 
-static int
-getfd(char *dir)
+int
+_p9netfd(char *dir)
 {
         int fd;
 
t@@ -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;
 
t@@ -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;
t@@ -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;
 }
 
t@@ -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;
         }
diff --git a/src/lib9/await.c b/src/lib9/await.c
t@@ -45,6 +45,7 @@ static struct {
 #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
t@@ -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);
t@@ -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)
t@@ -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:
diff --git a/src/lib9/convS2M.c b/src/lib9/convS2M.c
t@@ -92,6 +92,7 @@ sizeS2M(Fcall *f)
                 break;
 
         case Topen:
+        case Topenfd:
                 n += BIT32SZ;
                 n += BIT8SZ;
                 break;
t@@ -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;
t@@ -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);
t@@ -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:
diff --git a/src/lib9/create.c b/src/lib9/create.c
t@@ -1,8 +1,54 @@
 #include 
+#define NOPLAN9DEFINES
 #include 
+#include 
+
+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
t@@ -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;
diff --git a/src/lib9/getenv.c b/src/lib9/getenv.c
t@@ -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;
+}
diff --git a/src/lib9/mkfile b/src/lib9/mkfile
t@@ -8,6 +8,8 @@ OFILES=\
         _p9dialparse.$O\
         _p9dir.$O\
         _p9proc.$O\
+        _p9translate.$O\
+        access.$O\
         announce.$O\
         argv0.$O\
         atexit.$O\
t@@ -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\
t@@ -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\
diff --git a/src/lib9/notify.c b/src/lib9/notify.c
t@@ -19,7 +19,7 @@ static int sigs[] = {
 #endif
         SIGFPE,
         SIGBUS,
-        SIGSEGV,
+/*        SIGSEGV,        */
         SIGSYS,
         SIGPIPE,
         SIGALRM,
diff --git a/src/lib9/rfork.c b/src/lib9/rfork.c
t@@ -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;
                 }
t@@ -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;
diff --git a/src/libdraw/openfont.c b/src/libdraw/openfont.c
t@@ -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;
diff --git a/src/libdraw/x11-alloc.c b/src/libdraw/x11-alloc.c
t@@ -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;
t@@ -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
diff --git a/src/libdraw/x11-cload.c b/src/libdraw/x11-cload.c
t@@ -13,7 +13,7 @@ cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
 
         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
t@@ -24,11 +24,11 @@ memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp,
 
         /* 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);
t@@ -37,7 +37,7 @@ memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp,
                 return;
 
         /* put bits back on x server */
-        xputxdata(dst, par->r);
+        _xputxdata(dst, par->r);
 }
 
 static int
t@@ -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;
         }
diff --git a/src/libdraw/x11-event.c b/src/libdraw/x11-event.c
t@@ -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;
                 }
t@@ -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;
t@@ -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;
t@@ -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;
t@@ -168,12 +168,12 @@ again:
 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
t@@ -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;
diff --git a/src/libdraw/x11-get.c b/src/libdraw/x11-get.c
t@@ -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;
t@@ -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;
t@@ -97,7 +97,7 @@ xputxdata(Memimage *m, Rectangle r)
 }
 
 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
t@@ -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.
t@@ -651,7 +651,7 @@ flushmemscreen(Rectangle r)
 }
 
 void
-xexpose(XEvent *e, XDisplay *xd)
+_xexpose(XEvent *e, XDisplay *xd)
 {
         XExposeEvent *xe;
         Rectangle r;
t@@ -673,7 +673,7 @@ xexpose(XEvent *e, XDisplay *xd)
 }
 
 int
-xdestroy(XEvent *e, XDisplay *xd)
+_xdestroy(XEvent *e, XDisplay *xd)
 {
         XDestroyWindowEvent *xe;
 
t@@ -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;
t@@ -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;
diff --git a/src/libdraw/x11-itrans.c b/src/libdraw/x11-itrans.c
t@@ -12,7 +12,7 @@
 #include "x11-memdraw.h"
 
 static int
-_xtoplan9kbd(XEvent *e)
+__xtoplan9kbd(XEvent *e)
 {
         int ind, k, md;
 
t@@ -125,7 +125,7 @@ xtoplan9latin1(XEvent *e)
         int n;
         int r;
 
-        r = _xtoplan9kbd(e);
+        r = __xtoplan9kbd(e);
         if(r < 0)
                 return nil;
         if(alting){
t@@ -156,7 +156,7 @@ xtoplan9latin1(XEvent *e)
 }
 
 int
-xtoplan9kbd(XEvent *e)
+_xtoplan9kbd(XEvent *e)
 {
         static Rune *r;
 
t@@ -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;
t@@ -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);
t@@ -296,7 +296,7 @@ xcursorarrow(void)
 
 
 void
-xsetcursor(Cursor *c)
+_xsetcursor(Cursor *c)
 {
         XColor fg, bg;
         XCursor xc;
t@@ -335,7 +335,7 @@ struct {
 } clip;
 
 char*
-xgetsnarf(XDisplay *xd)
+_xgetsnarf(XDisplay *xd)
 {
         uchar *data, *xdata;
         Atom clipboard, type, prop;
t@@ -420,7 +420,7 @@ out:
 }
 
 void
-xputsnarf(XDisplay *xd, char *data)
+_xputsnarf(XDisplay *xd, char *data)
 {
         XButtonEvent e;
 
t@@ -445,7 +445,7 @@ xputsnarf(XDisplay *xd, char *data)
 }
 
 int
-xselect(XEvent *e, XDisplay *xd)
+_xselect(XEvent *e, XDisplay *xd)
 {
         char *name;
         XEvent r;
t@@ -493,12 +493,12 @@ if(0) fprint(2, "xselect target=%d requestor=%d property=%d selection=%d\n",
 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
t@@ -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);
                         }
diff --git a/src/libdraw/x11-load.c b/src/libdraw/x11-load.c
t@@ -13,7 +13,7 @@ loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata)
 
         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
t@@ -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|\
diff --git a/src/libdraw/x11-mouse.c b/src/libdraw/x11-mouse.c
t@@ -11,7 +11,7 @@
 void
 moveto(Mousectl *m, Point pt)
 {
-        xmoveto(pt);
+        _xmoveto(pt);
 }
 
 void
t@@ -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 */
t@@ -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);
                         /*
t@@ -117,6 +117,6 @@ initmouse(char *file, Image *i)
 void
 setcursor(Mousectl *mc, Cursor *c)
 {
-        xsetcursor(c);
+        _xsetcursor(c);
 }
 
diff --git a/src/libdraw/x11-pixelbits.c b/src/libdraw/x11-pixelbits.c
t@@ -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);
 }
 
diff --git a/src/libdraw/x11-unload.c b/src/libdraw/x11-unload.c
t@@ -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);
 }
 
diff --git a/src/libfs/fs.c b/src/libfs/fs.c
t@@ -50,7 +50,7 @@ fsroot(Fsys *fs)
 }
 
 Fsys*
-fsmount(int fd)
+fsmount(int fd, char *aname)
 {
         int n;
         char *user;
t@@ -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;
 }
t@@ -76,6 +77,8 @@ fsmount(int fd)
 void
 fsunmount(Fsys *fs)
 {
+        fsclose(fs->root);
+        fs->root = nil;
         _fsdecref(fs);
 }
 
t@@ -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;
t@@ -103,6 +108,7 @@ fsversion(Fsys *fs, int msize, char *version, int nversion)
         void *freep;
         Fcall tx, rx;
 
+        tx.tag = 0;
         tx.type = Tversion;
         tx.version = version;
         tx.msize = msize;
t@@ -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;
t@@ -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);
t@@ -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);
t@@ -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;
 }
t@@ -259,7 +266,7 @@ _fsrecv(Mux *mux)
 {
         uchar *pkt;
         uchar buf[4];
-        int n;
+        int n, nfd;
         Fsys *fs;
 
         fs = mux->aux;
t@@ -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;
 }
diff --git a/src/libfs/mkfile b/src/libfs/mkfile
t@@ -8,7 +8,9 @@ OFILES=\
         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
t@@ -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);
 }
diff --git a/src/libplumb/mesg.c b/src/libplumb/mesg.c
t@@ -1,5 +1,7 @@
 #include 
 #include 
+#include 
+#include 
 #include "plumb.h"
 
 static char attrbuf[4096];
t@@ -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
diff --git a/src/libthread/asm-FreeBSD-386.s b/src/libthread/asm-FreeBSD-386.s
t@@ -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
 
diff --git a/src/libthread/create.c b/src/libthread/create.c
t@@ -1,8 +1,7 @@
 #include "threadimpl.h"
 
 Pqueue _threadpq;
-
-int _threadmultiproc;
+int _threadprocs;
 
 static int nextID(void);
 
t@@ -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);
 }
 
t@@ -125,11 +123,12 @@ threadcreateidle(void (*f)(void *arg), void *arg, uint stacksize)
 {
         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;
 }
t@@ -154,6 +153,7 @@ _newproc(void (*f)(void *arg), void *arg, uint stacksize, char *name, int grp, i
         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
t@@ -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;
t@@ -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();
 
t@@ -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);
 
t@@ -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
t@@ -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')
diff --git a/src/libthread/exec.c b/src/libthread/exec.c
t@@ -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;
t@@ -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();
 
t@@ -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);
 
t@@ -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);
 }
 
diff --git a/src/libthread/main.c b/src/libthread/main.c
t@@ -37,7 +37,7 @@ main(int argc, char **argv)
         _systhreadinit();
         _qlockinit(_threadrendezvous);
         _sysfatal = _threadsysfatal;
-//        notify(_threadnote);
+        notify(_threadnote);
         if(mainstacksize == 0)
                 mainstacksize = 32*1024;
 
t@@ -98,6 +98,7 @@ _schedexit(Proc *p)
                         break;
                 }
         }
+        _threadprocs--;
         unlock(&_threadpq.lock);
 
         strncpy(ex, p->exitstr, sizeof ex);
diff --git a/src/libthread/note.c b/src/libthread/note.c
t@@ -2,7 +2,6 @@
 
 int        _threadnopasser;
 
-#ifdef NOTDEF
 #define        NFN                33
 #define        ERRLEN        48
 typedef struct Note Note;
t@@ -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){
t@@ -112,7 +111,6 @@ _threadnote(void *v, char *s)
                 delayednotes(p, v);
         noted(NCONT);
 }
-#endif
 
 int
 _procsplhi(void)
diff --git a/src/libthread/sched.c b/src/libthread/sched.c
t@@ -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)
t@@ -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");
t@@ -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);
t@@ -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);
 }
 
diff --git a/src/libthread/threadimpl.h b/src/libthread/threadimpl.h
t@@ -105,6 +105,7 @@ struct Execargs
         char                *prog;
         char                **args;
         int                fd[2];
+        int                *stdfd;
 };
 
 struct Proc
t@@ -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;