tFile system stuff. - plan9port - [fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port
Log
Files
Refs
README
LICENSE
---
commit d3df308747ee4d1fcc063a348dcf1146b390bda7
parent e97ceade5e1bba5787e39429384336fa37797906
Author: rsc 
Date:   Sat,  6 Dec 2003 18:08:52 +0000

File system stuff.

Diffstat:
  A include/fs.h                        |      41 +++++++++++++++++++++++++++++++
  A include/mux.h                       |      54 +++++++++++++++++++++++++++++++
  A src/cmd/9p.c                        |     191 +++++++++++++++++++++++++++++++
  A src/cmd/9pserve.c                   |     597 +++++++++++++++++++++++++++++++
  A src/lib9/convD2M.c                  |      95 ++++++++++++++++++++++++++++++
  A src/lib9/convM2D.c                  |      94 +++++++++++++++++++++++++++++++
  A src/lib9/convM2S.c                  |     315 +++++++++++++++++++++++++++++++
  A src/lib9/convS2M.c                  |     386 +++++++++++++++++++++++++++++++
  A src/lib9/fcallfmt.c                 |     234 +++++++++++++++++++++++++++++++
  A src/lib9/read9pmsg.c                |      31 +++++++++++++++++++++++++++++++
  A src/libfs/COPYRIGHT                 |      27 +++++++++++++++++++++++++++
  A src/libfs/close.c                   |      26 ++++++++++++++++++++++++++
  A src/libfs/create.c                  |      25 +++++++++++++++++++++++++
  A src/libfs/dirread.c                 |     100 +++++++++++++++++++++++++++++++
  A src/libfs/fs.c                      |     276 ++++++++++++++++++++++++++++++
  A src/libfs/fsimpl.h                  |      41 +++++++++++++++++++++++++++++++
  A src/libfs/mkfile                    |      22 ++++++++++++++++++++++
  A src/libfs/open.c                    |      24 ++++++++++++++++++++++++
  A src/libfs/read.c                    |      48 +++++++++++++++++++++++++++++++
  A src/libfs/stat.c                    |      54 +++++++++++++++++++++++++++++++
  A src/libfs/walk.c                    |      73 +++++++++++++++++++++++++++++++
  A src/libfs/write.c                   |      46 +++++++++++++++++++++++++++++++
  A src/libfs/wstat.c                   |      49 +++++++++++++++++++++++++++++++
  A src/libmux/COPYRIGHT                |      27 +++++++++++++++++++++++++++
  A src/libmux/io.c                     |     136 +++++++++++++++++++++++++++++++
  A src/libmux/mkfile                   |      16 ++++++++++++++++
  A src/libmux/mux.c                    |     152 +++++++++++++++++++++++++++++++
  A src/libmux/queue.c                  |     109 +++++++++++++++++++++++++++++++
  A src/libmux/thread.c                 |      27 +++++++++++++++++++++++++++

29 files changed, 3316 insertions(+), 0 deletions(-)
---
diff --git a/include/fs.h b/include/fs.h
t@@ -0,0 +1,41 @@
+#ifndef _FS_H_
+#define _FS_H_ 1
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Simple user-level 9P client.
+ */
+
+typedef struct Fsys Fsys;
+typedef struct Fid Fid;
+
+Fsys *fsinit(int);
+Fsys *fsmount(int);
+
+int fsversion(Fsys*, int, char*, int);
+Fid *fsauth(Fsys*, char*);
+Fid *fsattach(Fsys*, Fid*, char*, char*);
+Fid *fsopen(Fsys*, char*, int);
+int fsopenfd(Fsys*, char*, int);
+long fsread(Fid*, void*, long);
+long fsreadn(Fid*, void*, long);
+long fswrite(Fid*, void*, long);
+void fsclose(Fid*);
+void fsunmount(Fsys*);
+int fsrpc(Fsys*, Fcall*, Fcall*, void**);
+Fid *fswalk(Fid*, char*);
+struct Dir;        /* in case there's no lib9.h */
+long fsdirread(Fid*, struct Dir**);
+long fsdirreadall(Fid*, struct Dir**);
+struct Dir *fsdirstat(Fsys*, char*);
+struct Dir *fsdirfstat(Fid*);
+int fsdirwstat(Fsys*, char*, struct Dir*);
+int fsdirfwstat(Fid*, struct Dir*);
+Fid *fsroot(Fsys*);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/include/mux.h b/include/mux.h
t@@ -0,0 +1,54 @@
+typedef struct Mux Mux;
+typedef struct Muxrpc Muxrpc;
+typedef struct Muxqueue Muxqueue;
+
+struct Muxrpc
+{
+        Muxrpc *next;
+        Muxrpc *prev;
+        Rendez r;
+        uint tag;
+        void *p;
+};
+
+struct Mux
+{
+        uint mintag;        /* to be filled by client */
+        uint maxtag;
+        int (*send)(Mux*, void*);
+        void *(*recv)(Mux*);
+        int (*gettag)(Mux*, void*);
+        int (*settag)(Mux*, void*, uint);
+        void *aux;        /* for private use by client */
+
+/* private */
+        QLock lk;
+        QLock inlk;
+        QLock outlk;
+        Rendez tagrend;
+        Rendez rpcfork;
+        Muxqueue *readq;
+        Muxqueue *writeq;
+        uint nwait;
+        uint mwait;
+        uint freetag;
+        Muxrpc **wait;
+        uint muxer;
+        Muxrpc sleep;
+};
+
+void        muxinit(Mux*);
+void*        muxrpc(Mux*, void*);
+void        muxthreads(Mux*);
+
+/* private */
+int        _muxsend(Mux*, void*);
+void*        _muxrecv(Mux*);
+void        _muxsendproc(void*);
+void        _muxrecvproc(void*);
+Muxqueue *_muxqalloc(void);
+int _muxqsend(Muxqueue*, void*);
+void *_muxqrecv(Muxqueue*);
+void _muxqhangup(Muxqueue*);
+void *_muxnbqrecv(Muxqueue*);
+
diff --git a/src/cmd/9p.c b/src/cmd/9p.c
t@@ -0,0 +1,191 @@
+#include 
+#include 
+#include 
+#include 
+
+char *addr;
+
+void
+usage(void)
+{
+        fprint(2, "usage: 9p [-a address] cmd args...\n");
+        fprint(2, "possible cmds:\n");
+        fprint(2, "        read name\n");
+        fprint(2, "        write 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");
+        exits("usage");
+}
+
+void xread(int, char**);
+void xwrite(int, char**);
+void xstat(int, char**);
+void xls(int, char**);
+
+struct {
+        char *s;
+        void (*f)(int, char**);
+} cmds[] = {
+        "read", xread,
+        "write", xwrite,
+        "stat", xstat,
+//        "ls", xls,
+};
+
+void
+main(int argc, char **argv)
+{
+        char *cmd;
+        int i;
+
+        ARGBEGIN{
+        case 'a':
+                addr = EARGF(usage());
+                break;
+        default:
+                usage();
+        }ARGEND
+
+        if(argc < 1)
+                usage();
+
+        cmd = argv[0];
+        for(i=0; i 0)
+                write(1, buf, n);
+        if(n < 0)
+                sysfatal("read error: %r");
+        exits(0);        
+}
+
+void
+xwrite(int argc, char **argv)
+{
+        char buf[1024];
+        int n;
+        Fid *fid;
+
+        ARGBEGIN{
+        default:
+                usage();
+        }ARGEND
+
+        if(argc != 1)
+                usage();
+
+        fid = xopen(argv[0], OWRITE|OTRUNC);
+        while((n = read(0, buf, sizeof buf)) > 0)
+                if(fswrite(fid, buf, n) != n)
+                        sysfatal("write error: %r");
+        if(n < 0)
+                sysfatal("read error: %r");
+        exits(0);        
+}
+
+void
+xstat(int argc, char **argv)
+{
+        Dir *d;
+        Fid *fid;
+
+        ARGBEGIN{
+        default:
+                usage();
+        }ARGEND
+
+        if(argc != 1)
+                usage();
+
+        fid = xwalk(argv[0]);
+        if((d = fsdirfstat(fid)) < 0)
+                sysfatal("dirfstat: %r");
+        fmtinstall('D', dirfmt);
+        fmtinstall('M', dirmodefmt);
+        print("%D\n", d);
+        exits(0);
+}
diff --git a/src/cmd/9pserve.c b/src/cmd/9pserve.c
t@@ -0,0 +1,597 @@
+#include 
+#include 
+#include 
+#include 
+
+enum
+{
+        STACK = 32768,
+        NHASH = 31,
+        MAXMSG = 64,        /* per connection */
+};
+
+typedef struct Hash Hash;
+typedef struct Fid Fid;
+typedef struct Msg Msg;
+typedef struct Conn Conn;
+typedef struct Queue Queue;
+
+struct Hash
+{
+        Hash *next;
+        uint n;
+        void *v;
+};
+
+struct Fid
+{
+        int fid;
+        int ref;
+        int cfid;
+        Fid *next;
+};
+
+struct Msg
+{
+        Conn *c;
+        int internal;
+        int ref;
+        int ctag;
+        int tag;
+        Fcall tx;
+        Fcall rx;
+        Fid *fid;
+        Fid *newfid;
+        Fid *afid;
+        Msg *oldm;
+        Msg *next;
+        uchar *tpkt;
+        uchar *rpkt;
+};
+
+struct Conn
+{
+        int fd;
+        int nmsg;
+        int nfid;
+        Channel *inc;
+        Channel *internal;
+        int inputstalled;
+        char dir[40];
+        Hash *tag[NHASH];
+        Hash *fid[NHASH];
+        Queue *outq;
+        Queue *inq;
+};
+
+char *addr;
+int afd;
+char adir[40];
+int isunix;
+Queue *outq;
+Queue *inq;
+
+void *gethash(Hash**, uint);
+int puthash(Hash**, uint, void*);
+int delhash(Hash**, uint, void*);
+Msg *mread9p(int);
+int mwrite9p(int, Msg*);
+uchar *read9ppkt(int);
+int write9ppkt(int, uchar*);
+Msg *msgnew(void);
+void msgput(Msg*);
+Msg *msgget(int);
+Fid *fidnew(int);
+void fidput(Fid*);
+void *emalloc(int);
+void *erealloc(void*, int);
+int sendq(Queue*, void*);
+void *recvq(Queue*);
+void selectthread(void*);
+void connthread(void*);
+void listenthread(void*);
+void rewritehdr(Fcall*, uchar*);
+int tlisten(char*, char*);
+int taccept(int, char*);
+
+void
+usage(void)
+{
+        fprint(2, "usage: 9pserve [-u] address\n");
+        fprint(2, "\treads/writes 9P messages on stdin/stdout\n");
+        exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+        ARGBEGIN{
+        default:
+                usage();
+        case 'u':
+                isunix = 1;
+                break;
+        }ARGEND
+
+        if(argc != 1)
+                usage();
+
+        if((afd = announce(addr, adir)) < 0)
+                sysfatal("announce %s: %r", addr);
+
+        threadcreateidle(selectthread, nil, STACK);
+}
+
+void
+listenthread(void *arg)
+{
+        Conn *c;
+
+        USED(arg);
+        for(;;){
+                c = malloc(sizeof(Conn));
+                if(c == nil){
+                        fprint(2, "out of memory\n");
+                        sleep(60*1000);
+                        continue;
+                }
+                c->fd = tlisten(adir, c->dir);
+                if(c->fd < 0){
+                        fprint(2, "listen: %r\n");
+                        close(afd);
+                        free(c);
+                        return;
+                }
+                threadcreate(connthread, c, STACK);
+        }        
+}
+
+void
+err(Msg *m, char *ename)
+{
+        int n, nn;
+
+        m->rx.type = Rerror;
+        m->rx.ename = ename;
+        m->rx.tag = m->ctag;
+        n = sizeS2M(&m->rx);
+        m->rpkt = emalloc(n);
+        nn = convS2M(&m->rx, m->rpkt, n);
+        if(nn != n)
+                sysfatal("sizeS2M + convS2M disagree");
+        sendq(m->c->outq, m);
+}
+
+void
+connthread(void *arg)
+{
+        int i, fd;
+        Conn *c;
+        Hash *h;
+        Msg *m, *om;
+        Fid *f;
+
+        c = arg;
+        fd = taccept(c->fd, c->dir);
+        if(fd < 0){
+                fprint(2, "accept %s: %r\n", c->dir);
+                goto out;
+        }
+        close(c->fd);
+        c->fd = fd;
+        while((m = mread9p(c->fd)) != nil){
+                m->c = c;
+                c->nmsg++;
+                if(puthash(c->tag, m->tx.tag, m) < 0){
+                        err(m, "duplicate tag");
+                        continue;
+                }
+                switch(m->tx.type){
+                case Tflush:
+                        if((m->oldm = gethash(c->tag, m->tx.oldtag)) == nil){
+                                m->rx.tag = Rflush;
+                                sendq(c->outq, m);
+                                continue;
+                        }
+                        break;
+                case Tattach:
+                        m->fid = fidnew(m->tx.fid);
+                        if(puthash(c->fid, m->tx.fid, m->fid) < 0){
+                                err(m, "duplicate fid");
+                                continue;
+                        }
+                        m->fid->ref++;
+                        break;
+                case Twalk:
+                        if((m->fid = gethash(c->fid, m->tx.fid)) == nil){
+                                err(m, "unknown fid");
+                                continue;
+                        }
+                        if(m->tx.newfid == m->tx.fid){
+                                m->fid->ref++;
+                                m->newfid = m->fid;
+                        }else{
+                                m->newfid = fidnew(m->tx.newfid);
+                                if(puthash(c->fid, m->tx.newfid, m->newfid) < 0){
+                                        err(m, "duplicate fid");
+                                        continue;
+                                }
+                                m->newfid->ref++;
+                        }
+                        break;
+                case Tauth:
+                        if((m->afid = gethash(c->fid, m->tx.afid)) == nil){
+                                err(m, "unknown fid");
+                                continue;
+                        }
+                        m->fid = fidnew(m->tx.fid);
+                        if(puthash(c->fid, m->tx.fid, m->fid) < 0){
+                                err(m, "duplicate fid");
+                                continue;
+                        }
+                        m->fid->ref++;
+                        break;
+                case Topen:
+                case Tclunk:
+                case Tread:
+                case Twrite:
+                case Tstat:
+                case Twstat:
+                        if((m->fid = gethash(c->fid, m->tx.fid)) == nil){
+                                err(m, "unknown fid");
+                                continue;
+                        }
+                        m->fid->ref++;
+                        break;
+                }
+
+                /* have everything - translate and send */
+                m->c = c;
+                m->ctag = m->tx.tag;
+                m->tx.tag = m->tag;
+                if(m->fid)
+                        m->tx.fid = m->fid->fid;
+                if(m->newfid)
+                        m->tx.newfid = m->newfid->fid;
+                if(m->afid)
+                        m->tx.afid = m->afid->fid;
+                if(m->oldm)
+                        m->tx.oldtag = m->oldm->tag;
+                rewritehdr(&m->tx, m->tpkt);
+                sendq(outq, m);
+                while(c->nmsg >= MAXMSG){
+                        c->inputstalled = 1;
+                        recvp(c->inc);
+                }
+        }
+
+        /* flush all outstanding messages */
+        for(i=0; itag[i]; h; h=h->next){
+                        om = h->v;
+                        m = msgnew();
+                        m->internal = 1;
+                        m->c = c;
+                        m->tx.type = Tflush;
+                        m->tx.tag = m->tag;
+                        m->tx.oldtag = om->tag;
+                        m->oldm = om;
+                        om->ref++;
+                        sendq(outq, m);
+                        recvp(c->internal);
+                }
+        }
+
+        /* clunk all outstanding fids */
+        for(i=0; ifid[i]; h; h=h->next){
+                        f = h->v;
+                        m = msgnew();
+                        m->internal = 1;
+                        m->c = c;
+                        m->tx.type = Tclunk;
+                        m->tx.tag = m->tag;
+                        m->tx.fid = f->fid;
+                        m->fid = f;
+                        f->ref++;
+                        sendq(outq, m);
+                        recvp(c->internal);
+                }
+        }
+
+out:
+        assert(c->nmsg == 0);
+        assert(c->nfid == 0);
+        close(c->fd);
+        free(c);
+}
+
+void
+connoutthread(void *arg)
+{
+        int err;
+        Conn *c;
+        Msg *m, *om;
+
+        c = arg;
+        while((m = recvq(c->outq)) != nil){
+                err = m->tx.type+1 != m->rx.type;
+                switch(m->tx.type){
+                case Tflush:
+                        om = m->oldm;
+                        if(delhash(om->c->tag, om->ctag, om) == 0)
+                                msgput(om);
+                        break;
+                case Tclunk:
+                        if(delhash(m->c->fid, m->fid->cfid, m->fid) == 0)
+                                fidput(m->fid);
+                        break;
+                case Tauth:
+                        if(err)
+                                if(delhash(m->c->fid, m->afid->cfid, m->fid) == 0)
+                                        fidput(m->fid);
+                case Tattach:
+                        if(err)
+                                if(delhash(m->c->fid, m->fid->cfid, m->fid) == 0)
+                                        fidput(m->fid);
+                        break;
+                case Twalk:
+                        if(err && m->tx.fid != m->tx.newfid)
+                                if(delhash(m->c->fid, m->newfid->cfid, m->newfid) == 0)
+                                        fidput(m->newfid);
+                        break;
+                }
+                if(mwrite9p(c->fd, m) < 0)
+                        fprint(2, "write error: %r\n");
+                if(delhash(m->c->tag, m->ctag, m) == 0)
+                        msgput(m);
+                msgput(m);
+                if(c->inputstalled && c->nmsg < MAXMSG)
+                        nbsendp(c->inc, 0);
+        }
+}
+
+void
+outputthread(void *arg)
+{
+        Msg *m;
+
+        USED(arg);
+
+        while((m = recvq(outq)) != nil){
+                if(mwrite9p(1, m) < 0)
+                        sysfatal("output error: %r");
+                msgput(m);
+        }
+}        
+
+void
+inputthread(void *arg)
+{
+        uchar *pkt;
+        int n, nn, tag;
+        Msg *m;
+
+        while((pkt = read9ppkt(0)) != nil){
+                n = GBIT32(pkt);
+                if(n < 7){
+                        fprint(2, "short 9P packet\n");
+                        free(pkt);
+                        continue;
+                }
+                tag = GBIT16(pkt+5);
+                if((m = msgget(tag)) == nil){
+                        fprint(2, "unexpected 9P response tag %d\n", tag);
+                        free(pkt);
+                        msgput(m);
+                        continue;
+                }
+                if((nn = convM2S(pkt, n, &m->rx)) != n){
+                        fprint(2, "bad packet - convM2S %d but %d\n", nn, n);
+                        free(pkt);
+                        msgput(m);
+                        continue;
+                }
+                m->rpkt = pkt;
+                m->rx.tag = m->ctag;
+                rewritehdr(&m->rx, m->rpkt);
+                sendq(m->c->outq, m);
+        }
+}
+
+void*
+gethash(Hash **ht, uint n)
+{
+        Hash *h;
+
+        for(h=ht[n%NHASH]; h; h=h->next)
+                if(h->n == n)
+                        return h->v;
+        return nil;
+}
+
+int
+delhash(Hash **ht, uint n, void *v)
+{
+        Hash *h, **l;
+
+        for(l=&ht[n%NHASH]; h=*l; l=&h->next)
+                if(h->n == n){
+                        if(h->v != v)
+                                fprint(2, "hash error\n");
+                        *l = h->next;
+                        free(h);
+                        return 0;
+                }
+        return -1;
+}
+
+int
+puthash(Hash **ht, uint n, void *v)
+{
+        Hash *h;
+
+        if(gethash(ht, n))
+                return -1;
+        h = emalloc(sizeof(Hash));
+        h->next = ht[n%NHASH];
+        h->n = n;
+        h->v = v;
+        ht[n%NHASH] = h;
+        return 0;
+}
+
+Fid **fidtab;
+int nfidtab;
+Fid *freefid;
+
+Fid*
+fidnew(int cfid)
+{
+        Fid *f;
+
+        if(freefid == nil){
+                fidtab = erealloc(fidtab, nfidtab*sizeof(fidtab[0]));
+                fidtab[nfidtab] = emalloc(sizeof(Fid));
+                freefid = fidtab[nfidtab++];
+        }
+        f = freefid;
+        freefid = f->next;
+        f->cfid = f->cfid;
+        f->ref = 1;
+        return f;
+}
+
+void
+fidput(Fid *f)
+{
+        assert(f->ref > 0);
+        if(--f->ref > 0)
+                return;
+        f->next = freefid;
+        f->cfid = -1;
+        freefid = f;
+}
+
+Msg **msgtab;
+int nmsgtab;
+Msg *freemsg;
+
+Msg*
+msgnew(void)
+{
+        Msg *m;
+
+        if(freemsg == nil){
+                msgtab = erealloc(msgtab, nmsgtab*sizeof(msgtab[0]));
+                msgtab[nmsgtab] = emalloc(sizeof(Msg));
+                freemsg = msgtab[nmsgtab++];
+        }
+        m = freemsg;
+        freemsg = m->next;
+        m->ref = 1;
+        return m;
+}
+
+void
+msgput(Msg *m)
+{
+        assert(m->ref > 0);
+        if(--m->ref > 0)
+                return;
+        m->next = freemsg;
+        freemsg = m;
+}
+
+void*
+emalloc(int n)
+{
+        void *v;
+
+        v = mallocz(n, 1);
+        if(v == nil)
+                sysfatal("out of memory");
+        return v;
+}
+
+void*
+erealloc(void *v, int n)
+{
+        v = realloc(v, n);
+        if(v == nil)
+                sysfatal("out of memory");
+        return v;
+}
+
+typedef struct Qel Qel;
+struct Qel
+{
+        Qel *next;
+        void *p;
+};
+
+struct Queue
+{
+        int hungup;
+        QLock lk;
+        Rendez r;
+        Qel *head;
+        Qel *tail;
+};
+
+Queue*
+qalloc(void)
+{
+        Queue *q;
+
+        q = mallocz(sizeof(Queue), 1);
+        if(q == nil)
+                return nil;
+        q->r.l = &q->lk;
+        return q;
+}
+
+int
+sendq(Queue *q, void *p)
+{
+        Qel *e;
+
+        e = emalloc(sizeof(Qel));
+        qlock(&q->lk);
+        if(q->hungup){
+                werrstr("hungup queue");
+                qunlock(&q->lk);
+                return -1;
+        }
+        e->p = p;
+        e->next = nil;
+        if(q->head == nil)
+                q->head = e;
+        else
+                q->tail->next = e;
+        q->tail = e;
+        rwakeup(&q->r);
+        qunlock(&q->lk);
+        return 0;
+}
+
+void*
+recvq(Queue *q)
+{
+        void *p;
+        Qel *e;
+
+        qlock(&q->lk);
+        while(q->head == nil && !q->hungup)
+                rsleep(&q->r);
+        if(q->hungup){
+                qunlock(&q->lk);
+                return nil;
+        }
+        e = q->head;
+        q->head = e->next;
+        qunlock(&q->lk);
+        p = e->p;
+        free(e);
+        return p;
+}
diff --git a/src/lib9/convD2M.c b/src/lib9/convD2M.c
t@@ -0,0 +1,95 @@
+#include        
+#include        
+#include        
+
+uint
+sizeD2M(Dir *d)
+{
+        char *sv[4];
+        int i, ns;
+
+        sv[0] = d->name;
+        sv[1] = d->uid;
+        sv[2] = d->gid;
+        sv[3] = d->muid;
+
+        ns = 0;
+        for(i = 0; i < 4; i++)
+                if(sv[i])
+                        ns += strlen(sv[i]);
+
+        return STATFIXLEN + ns;
+}
+
+uint
+convD2M(Dir *d, uchar *buf, uint nbuf)
+{
+        uchar *p, *ebuf;
+        char *sv[4];
+        int i, ns, nsv[4], ss;
+
+        if(nbuf < BIT16SZ)
+                return 0;
+
+        p = buf;
+        ebuf = buf + nbuf;
+
+        sv[0] = d->name;
+        sv[1] = d->uid;
+        sv[2] = d->gid;
+        sv[3] = d->muid;
+
+        ns = 0;
+        for(i = 0; i < 4; i++){
+                if(sv[i])
+                        nsv[i] = strlen(sv[i]);
+                else
+                        nsv[i] = 0;
+                ns += nsv[i];
+        }
+
+        ss = STATFIXLEN + ns;
+
+        /* set size befor erroring, so user can know how much is needed */
+        /* note that length excludes count field itself */
+        PBIT16(p, ss-BIT16SZ);
+        p += BIT16SZ;
+
+        if(ss > nbuf)
+                return BIT16SZ;
+
+        PBIT16(p, d->type);
+        p += BIT16SZ;
+        PBIT32(p, d->dev);
+        p += BIT32SZ;
+        PBIT8(p, d->qid.type);
+        p += BIT8SZ;
+        PBIT32(p, d->qid.vers);
+        p += BIT32SZ;
+        PBIT64(p, d->qid.path);
+        p += BIT64SZ;
+        PBIT32(p, d->mode);
+        p += BIT32SZ;
+        PBIT32(p, d->atime);
+        p += BIT32SZ;
+        PBIT32(p, d->mtime);
+        p += BIT32SZ;
+        PBIT64(p, d->length);
+        p += BIT64SZ;
+
+        for(i = 0; i < 4; i++){
+                ns = nsv[i];
+                if(p + ns + BIT16SZ > ebuf)
+                        return 0;
+                PBIT16(p, ns);
+                p += BIT16SZ;
+                if(ns)
+                        memmove(p, sv[i], ns);
+                p += ns;
+        }
+
+        if(ss != p - buf)
+                return 0;
+
+        return p - buf;
+}
diff --git a/src/lib9/convM2D.c b/src/lib9/convM2D.c
t@@ -0,0 +1,94 @@
+#include        
+#include        
+#include        
+
+int
+statcheck(uchar *buf, uint nbuf)
+{
+        uchar *ebuf;
+        int i;
+
+        ebuf = buf + nbuf;
+
+        if(nbuf < STATFIXLEN || nbuf != BIT16SZ + GBIT16(buf))
+                return -1;
+
+        buf += STATFIXLEN - 4 * BIT16SZ;
+
+        for(i = 0; i < 4; i++){
+                if(buf + BIT16SZ > ebuf)
+                        return -1;
+                buf += BIT16SZ + GBIT16(buf);
+        }
+
+        if(buf != ebuf)
+                return -1;
+
+        return 0;
+}
+
+static char nullstring[] = "";
+
+uint
+convM2D(uchar *buf, uint nbuf, Dir *d, char *strs)
+{
+        uchar *p, *ebuf;
+        char *sv[4];
+        int i, ns;
+
+        if(nbuf < STATFIXLEN)
+                return 0; 
+
+        p = buf;
+        ebuf = buf + nbuf;
+
+        p += BIT16SZ;        /* ignore size */
+        d->type = GBIT16(p);
+        p += BIT16SZ;
+        d->dev = GBIT32(p);
+        p += BIT32SZ;
+        d->qid.type = GBIT8(p);
+        p += BIT8SZ;
+        d->qid.vers = GBIT32(p);
+        p += BIT32SZ;
+        d->qid.path = GBIT64(p);
+        p += BIT64SZ;
+        d->mode = GBIT32(p);
+        p += BIT32SZ;
+        d->atime = GBIT32(p);
+        p += BIT32SZ;
+        d->mtime = GBIT32(p);
+        p += BIT32SZ;
+        d->length = GBIT64(p);
+        p += BIT64SZ;
+
+        for(i = 0; i < 4; i++){
+                if(p + BIT16SZ > ebuf)
+                        return 0;
+                ns = GBIT16(p);
+                p += BIT16SZ;
+                if(p + ns > ebuf)
+                        return 0;
+                if(strs){
+                        sv[i] = strs;
+                        memmove(strs, p, ns);
+                        strs += ns;
+                        *strs++ = '\0';
+                }
+                p += ns;
+        }
+
+        if(strs){
+                d->name = sv[0];
+                d->uid = sv[1];
+                d->gid = sv[2];
+                d->muid = sv[3];
+        }else{
+                d->name = nullstring;
+                d->uid = nullstring;
+                d->gid = nullstring;
+                d->muid = nullstring;
+        }
+        
+        return p - buf;
+}
diff --git a/src/lib9/convM2S.c b/src/lib9/convM2S.c
t@@ -0,0 +1,315 @@
+#include        
+#include        
+#include        
+
+static
+uchar*
+gstring(uchar *p, uchar *ep, char **s)
+{
+        uint n;
+
+        if(p+BIT16SZ > ep)
+                return nil;
+        n = GBIT16(p);
+        p += BIT16SZ - 1;
+        if(p+n+1 > ep)
+                return nil;
+        /* move it down, on top of count, to make room for '\0' */
+        memmove(p, p + 1, n);
+        p[n] = '\0';
+        *s = (char*)p;
+        p += n+1;
+        return p;
+}
+
+static
+uchar*
+gqid(uchar *p, uchar *ep, Qid *q)
+{
+        if(p+QIDSZ > ep)
+                return nil;
+        q->type = GBIT8(p);
+        p += BIT8SZ;
+        q->vers = GBIT32(p);
+        p += BIT32SZ;
+        q->path = GBIT64(p);
+        p += BIT64SZ;
+        return p;
+}
+
+/*
+ * no syntactic checks.
+ * three causes for error:
+ *  1. message size field is incorrect
+ *  2. input buffer too short for its own data (counts too long, etc.)
+ *  3. too many names or qids
+ * gqid() and gstring() return nil if they would reach beyond buffer.
+ * main switch statement checks range and also can fall through
+ * to test at end of routine.
+ */
+uint
+convM2S(uchar *ap, uint nap, Fcall *f)
+{
+        uchar *p, *ep;
+        uint i, size;
+
+        p = ap;
+        ep = p + nap;
+
+        if(p+BIT32SZ+BIT8SZ+BIT16SZ > ep)
+                return 0;
+        size = GBIT32(p);
+        p += BIT32SZ;
+
+        if(size < BIT32SZ+BIT8SZ+BIT16SZ)
+                return 0;
+
+        f->type = GBIT8(p);
+        p += BIT8SZ;
+        f->tag = GBIT16(p);
+        p += BIT16SZ;
+
+        switch(f->type)
+        {
+        default:
+                return 0;
+
+        case Tversion:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->msize = GBIT32(p);
+                p += BIT32SZ;
+                p = gstring(p, ep, &f->version);
+                break;
+
+        case Tflush:
+                if(p+BIT16SZ > ep)
+                        return 0;
+                f->oldtag = GBIT16(p);
+                p += BIT16SZ;
+                break;
+
+        case Tauth:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->afid = GBIT32(p);
+                p += BIT32SZ;
+                p = gstring(p, ep, &f->uname);
+                if(p == nil)
+                        break;
+                p = gstring(p, ep, &f->aname);
+                if(p == nil)
+                        break;
+                break;
+
+        case Tattach:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->afid = GBIT32(p);
+                p += BIT32SZ;
+                p = gstring(p, ep, &f->uname);
+                if(p == nil)
+                        break;
+                p = gstring(p, ep, &f->aname);
+                if(p == nil)
+                        break;
+                break;
+
+        case Twalk:
+                if(p+BIT32SZ+BIT32SZ+BIT16SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                f->newfid = GBIT32(p);
+                p += BIT32SZ;
+                f->nwname = GBIT16(p);
+                p += BIT16SZ;
+                if(f->nwname > MAXWELEM)
+                        return 0;
+                for(i=0; inwname; i++){
+                        p = gstring(p, ep, &f->wname[i]);
+                        if(p == nil)
+                                break;
+                }
+                break;
+
+        case Topen:
+                if(p+BIT32SZ+BIT8SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                f->mode = GBIT8(p);
+                p += BIT8SZ;
+                break;
+
+        case Tcreate:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                p = gstring(p, ep, &f->name);
+                if(p == nil)
+                        break;
+                if(p+BIT32SZ+BIT8SZ > ep)
+                        return 0;
+                f->perm = GBIT32(p);
+                p += BIT32SZ;
+                f->mode = GBIT8(p);
+                p += BIT8SZ;
+                break;
+
+        case Tread:
+                if(p+BIT32SZ+BIT64SZ+BIT32SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                f->offset = GBIT64(p);
+                p += BIT64SZ;
+                f->count = GBIT32(p);
+                p += BIT32SZ;
+                break;
+
+        case Twrite:
+                if(p+BIT32SZ+BIT64SZ+BIT32SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                f->offset = GBIT64(p);
+                p += BIT64SZ;
+                f->count = GBIT32(p);
+                p += BIT32SZ;
+                if(p+f->count > ep)
+                        return 0;
+                f->data = (char*)p;
+                p += f->count;
+                break;
+
+        case Tclunk:
+        case Tremove:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                break;
+
+        case Tstat:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                break;
+
+        case Twstat:
+                if(p+BIT32SZ+BIT16SZ > ep)
+                        return 0;
+                f->fid = GBIT32(p);
+                p += BIT32SZ;
+                f->nstat = GBIT16(p);
+                p += BIT16SZ;
+                if(p+f->nstat > ep)
+                        return 0;
+                f->stat = p;
+                p += f->nstat;
+                break;
+
+/*
+ */
+        case Rversion:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->msize = GBIT32(p);
+                p += BIT32SZ;
+                p = gstring(p, ep, &f->version);
+                break;
+
+        case Rerror:
+                p = gstring(p, ep, &f->ename);
+                break;
+
+        case Rflush:
+                break;
+
+        case Rauth:
+                p = gqid(p, ep, &f->aqid);
+                if(p == nil)
+                        break;
+                break;
+
+        case Rattach:
+                p = gqid(p, ep, &f->qid);
+                if(p == nil)
+                        break;
+                break;
+
+        case Rwalk:
+                if(p+BIT16SZ > ep)
+                        return 0;
+                f->nwqid = GBIT16(p);
+                p += BIT16SZ;
+                if(f->nwqid > MAXWELEM)
+                        return 0;
+                for(i=0; inwqid; i++){
+                        p = gqid(p, ep, &f->wqid[i]);
+                        if(p == nil)
+                                break;
+                }
+                break;
+
+        case Ropen:
+        case Rcreate:
+                p = gqid(p, ep, &f->qid);
+                if(p == nil)
+                        break;
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->iounit = GBIT32(p);
+                p += BIT32SZ;
+                break;
+
+        case Rread:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->count = GBIT32(p);
+                p += BIT32SZ;
+                if(p+f->count > ep)
+                        return 0;
+                f->data = (char*)p;
+                p += f->count;
+                break;
+
+        case Rwrite:
+                if(p+BIT32SZ > ep)
+                        return 0;
+                f->count = GBIT32(p);
+                p += BIT32SZ;
+                break;
+
+        case Rclunk:
+        case Rremove:
+                break;
+
+        case Rstat:
+                if(p+BIT16SZ > ep)
+                        return 0;
+                f->nstat = GBIT16(p);
+                p += BIT16SZ;
+                if(p+f->nstat > ep)
+                        return 0;
+                f->stat = p;
+                p += f->nstat;
+                break;
+
+        case Rwstat:
+                break;
+        }
+
+        if(p==nil || p>ep)
+                return 0;
+        if(ap+size == p)
+                return size;
+        return 0;
+}
diff --git a/src/lib9/convS2M.c b/src/lib9/convS2M.c
t@@ -0,0 +1,386 @@
+#include        
+#include        
+#include        
+
+static
+uchar*
+pstring(uchar *p, char *s)
+{
+        uint n;
+
+        if(s == nil){
+                PBIT16(p, 0);
+                p += BIT16SZ;
+                return p;
+        }
+
+        n = strlen(s);
+        PBIT16(p, n);
+        p += BIT16SZ;
+        memmove(p, s, n);
+        p += n;
+        return p;
+}
+
+static
+uchar*
+pqid(uchar *p, Qid *q)
+{
+        PBIT8(p, q->type);
+        p += BIT8SZ;
+        PBIT32(p, q->vers);
+        p += BIT32SZ;
+        PBIT64(p, q->path);
+        p += BIT64SZ;
+        return p;
+}
+
+static
+uint
+stringsz(char *s)
+{
+        if(s == nil)
+                return BIT16SZ;
+
+        return BIT16SZ+strlen(s);
+}
+
+uint
+sizeS2M(Fcall *f)
+{
+        uint n;
+        int i;
+
+        n = 0;
+        n += BIT32SZ;        /* size */
+        n += BIT8SZ;        /* type */
+        n += BIT16SZ;        /* tag */
+
+        switch(f->type)
+        {
+        default:
+                return 0;
+
+        case Tversion:
+                n += BIT32SZ;
+                n += stringsz(f->version);
+                break;
+
+        case Tflush:
+                n += BIT16SZ;
+                break;
+
+        case Tauth:
+                n += BIT32SZ;
+                n += stringsz(f->uname);
+                n += stringsz(f->aname);
+                break;
+
+        case Tattach:
+                n += BIT32SZ;
+                n += BIT32SZ;
+                n += stringsz(f->uname);
+                n += stringsz(f->aname);
+                break;
+
+        case Twalk:
+                n += BIT32SZ;
+                n += BIT32SZ;
+                n += BIT16SZ;
+                for(i=0; inwname; i++)
+                        n += stringsz(f->wname[i]);
+                break;
+
+        case Topen:
+                n += BIT32SZ;
+                n += BIT8SZ;
+                break;
+
+        case Tcreate:
+                n += BIT32SZ;
+                n += stringsz(f->name);
+                n += BIT32SZ;
+                n += BIT8SZ;
+                break;
+
+        case Tread:
+                n += BIT32SZ;
+                n += BIT64SZ;
+                n += BIT32SZ;
+                break;
+
+        case Twrite:
+                n += BIT32SZ;
+                n += BIT64SZ;
+                n += BIT32SZ;
+                n += f->count;
+                break;
+
+        case Tclunk:
+        case Tremove:
+                n += BIT32SZ;
+                break;
+
+        case Tstat:
+                n += BIT32SZ;
+                break;
+
+        case Twstat:
+                n += BIT32SZ;
+                n += BIT16SZ;
+                n += f->nstat;
+                break;
+/*
+ */
+
+        case Rversion:
+                n += BIT32SZ;
+                n += stringsz(f->version);
+                break;
+
+        case Rerror:
+                n += stringsz(f->ename);
+                break;
+
+        case Rflush:
+                break;
+
+        case Rauth:
+                n += QIDSZ;
+                break;
+
+        case Rattach:
+                n += QIDSZ;
+                break;
+
+        case Rwalk:
+                n += BIT16SZ;
+                n += f->nwqid*QIDSZ;
+                break;
+
+        case Ropen:
+        case Rcreate:
+                n += QIDSZ;
+                n += BIT32SZ;
+                break;
+
+        case Rread:
+                n += BIT32SZ;
+                n += f->count;
+                break;
+
+        case Rwrite:
+                n += BIT32SZ;
+                break;
+
+        case Rclunk:
+                break;
+
+        case Rremove:
+                break;
+
+        case Rstat:
+                n += BIT16SZ;
+                n += f->nstat;
+                break;
+
+        case Rwstat:
+                break;
+        }
+        return n;
+}
+
+uint
+convS2M(Fcall *f, uchar *ap, uint nap)
+{
+        uchar *p;
+        uint i, size;
+
+        size = sizeS2M(f);
+        if(size == 0)
+                return 0;
+        if(size > nap)
+                return 0;
+
+        p = (uchar*)ap;
+
+        PBIT32(p, size);
+        p += BIT32SZ;
+        PBIT8(p, f->type);
+        p += BIT8SZ;
+        PBIT16(p, f->tag);
+        p += BIT16SZ;
+
+        switch(f->type)
+        {
+        default:
+                return 0;
+
+        case Tversion:
+                PBIT32(p, f->msize);
+                p += BIT32SZ;
+                p = pstring(p, f->version);
+                break;
+
+        case Tflush:
+                PBIT16(p, f->oldtag);
+                p += BIT16SZ;
+                break;
+
+        case Tauth:
+                PBIT32(p, f->afid);
+                p += BIT32SZ;
+                p  = pstring(p, f->uname);
+                p  = pstring(p, f->aname);
+                break;
+
+        case Tattach:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                PBIT32(p, f->afid);
+                p += BIT32SZ;
+                p  = pstring(p, f->uname);
+                p  = pstring(p, f->aname);
+                break;
+
+        case Twalk:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                PBIT32(p, f->newfid);
+                p += BIT32SZ;
+                PBIT16(p, f->nwname);
+                p += BIT16SZ;
+                if(f->nwname > MAXWELEM)
+                        return 0;
+                for(i=0; inwname; i++)
+                        p = pstring(p, f->wname[i]);
+                break;
+
+        case Topen:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                PBIT8(p, f->mode);
+                p += BIT8SZ;
+                break;
+
+        case Tcreate:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                p = pstring(p, f->name);
+                PBIT32(p, f->perm);
+                p += BIT32SZ;
+                PBIT8(p, f->mode);
+                p += BIT8SZ;
+                break;
+
+        case Tread:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                PBIT64(p, f->offset);
+                p += BIT64SZ;
+                PBIT32(p, f->count);
+                p += BIT32SZ;
+                break;
+
+        case Twrite:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                PBIT64(p, f->offset);
+                p += BIT64SZ;
+                PBIT32(p, f->count);
+                p += BIT32SZ;
+                memmove(p, f->data, f->count);
+                p += f->count;
+                break;
+
+        case Tclunk:
+        case Tremove:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                break;
+
+        case Tstat:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                break;
+
+        case Twstat:
+                PBIT32(p, f->fid);
+                p += BIT32SZ;
+                PBIT16(p, f->nstat);
+                p += BIT16SZ;
+                memmove(p, f->stat, f->nstat);
+                p += f->nstat;
+                break;
+/*
+ */
+
+        case Rversion:
+                PBIT32(p, f->msize);
+                p += BIT32SZ;
+                p = pstring(p, f->version);
+                break;
+
+        case Rerror:
+                p = pstring(p, f->ename);
+                break;
+
+        case Rflush:
+                break;
+
+        case Rauth:
+                p = pqid(p, &f->aqid);
+                break;
+
+        case Rattach:
+                p = pqid(p, &f->qid);
+                break;
+
+        case Rwalk:
+                PBIT16(p, f->nwqid);
+                p += BIT16SZ;
+                if(f->nwqid > MAXWELEM)
+                        return 0;
+                for(i=0; inwqid; i++)
+                        p = pqid(p, &f->wqid[i]);
+                break;
+
+        case Ropen:
+        case Rcreate:
+                p = pqid(p, &f->qid);
+                PBIT32(p, f->iounit);
+                p += BIT32SZ;
+                break;
+
+        case Rread:
+                PBIT32(p, f->count);
+                p += BIT32SZ;
+                memmove(p, f->data, f->count);
+                p += f->count;
+                break;
+
+        case Rwrite:
+                PBIT32(p, f->count);
+                p += BIT32SZ;
+                break;
+
+        case Rclunk:
+                break;
+
+        case Rremove:
+                break;
+
+        case Rstat:
+                PBIT16(p, f->nstat);
+                p += BIT16SZ;
+                memmove(p, f->stat, f->nstat);
+                p += f->nstat;
+                break;
+
+        case Rwstat:
+                break;
+        }
+        if(size != p-ap)
+                return 0;
+        return size;
+}
diff --git a/src/lib9/fcallfmt.c b/src/lib9/fcallfmt.c
t@@ -0,0 +1,234 @@
+#include 
+#include 
+#include 
+
+static uint dumpsome(char*, char*, char*, long);
+static void fdirconv(char*, char*, Dir*);
+static char *qidtype(char*, uchar);
+
+#define        QIDFMT        "(%.16llux %lud %s)"
+
+int
+fcallfmt(Fmt *fmt)
+{
+        Fcall *f;
+        int fid, type, tag, i;
+        char buf[512], tmp[200];
+        char *p, *e;
+        Dir *d;
+        Qid *q;
+
+        e = buf+sizeof(buf);
+        f = va_arg(fmt->args, Fcall*);
+        type = f->type;
+        fid = f->fid;
+        tag = f->tag;
+        switch(type){
+        case Tversion:        /* 100 */
+                seprint(buf, e, "Tversion tag %ud msize %ud version '%s'", tag, f->msize, f->version);
+                break;
+        case Rversion:
+                seprint(buf, e, "Rversion tag %ud msize %ud version '%s'", tag, f->msize, f->version);
+                break;
+        case Tauth:        /* 102 */
+                seprint(buf, e, "Tauth tag %ud afid %d uname %s aname %s", tag,
+                        f->afid, f->uname, f->aname);
+                break;
+        case Rauth:
+                seprint(buf, e, "Rauth tag %ud qid " QIDFMT, tag,
+                        f->aqid.path, f->aqid.vers, qidtype(tmp, f->aqid.type));
+                break;
+        case Tattach:        /* 104 */
+                seprint(buf, e, "Tattach tag %ud fid %d afid %d uname %s aname %s", tag,
+                        fid, f->afid, f->uname, f->aname);
+                break;
+        case Rattach:
+                seprint(buf, e, "Rattach tag %ud qid " QIDFMT, tag,
+                        f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type));
+                break;
+        case Rerror:        /* 107; 106 (Terror) illegal */
+                seprint(buf, e, "Rerror tag %ud ename %s", tag, f->ename);
+                break;
+        case Tflush:        /* 108 */
+                seprint(buf, e, "Tflush tag %ud oldtag %ud", tag, f->oldtag);
+                break;
+        case Rflush:
+                seprint(buf, e, "Rflush tag %ud", tag);
+                break;
+        case Twalk:        /* 110 */
+                p = seprint(buf, e, "Twalk tag %ud fid %d newfid %d nwname %d ", tag, fid, f->newfid, f->nwname);
+                if(f->nwname <= MAXWELEM)
+                        for(i=0; inwname; i++)
+                                p = seprint(p, e, "%d:%s ", i, f->wname[i]);
+                break;
+        case Rwalk:
+                p = seprint(buf, e, "Rwalk tag %ud nwqid %ud ", tag, f->nwqid);
+                if(f->nwqid <= MAXWELEM)
+                        for(i=0; inwqid; i++){
+                                q = &f->wqid[i];
+                                p = seprint(p, e, "%d:" QIDFMT " ", i,
+                                        q->path, q->vers, qidtype(tmp, q->type));
+                        }
+                break;
+        case Topen:        /* 112 */
+                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,
+                        f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type), f->iounit);
+                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;
+        case Rcreate:
+                seprint(buf, e, "Rcreate tag %ud qid " QIDFMT " iounit %ud ", tag,
+                        f->qid.path, f->qid.vers, qidtype(tmp, f->qid.type), f->iounit);
+                break;
+        case Tread:        /* 116 */
+                seprint(buf, e, "Tread tag %ud fid %d offset %lld count %ud",
+                        tag, fid, f->offset, f->count);
+                break;
+        case Rread:
+                p = seprint(buf, e, "Rread tag %ud count %ud ", tag, f->count);
+                        dumpsome(p, e, f->data, f->count);
+                break;
+        case Twrite:        /* 118 */
+                p = seprint(buf, e, "Twrite tag %ud fid %d offset %lld count %ud ",
+                        tag, fid, f->offset, f->count);
+                dumpsome(p, e, f->data, f->count);
+                break;
+        case Rwrite:
+                seprint(buf, e, "Rwrite tag %ud count %ud", tag, f->count);
+                break;
+        case Tclunk:        /* 120 */
+                seprint(buf, e, "Tclunk tag %ud fid %ud", tag, fid);
+                break;
+        case Rclunk:
+                seprint(buf, e, "Rclunk tag %ud", tag);
+                break;
+        case Tremove:        /* 122 */
+                seprint(buf, e, "Tremove tag %ud fid %ud", tag, fid);
+                break;
+        case Rremove:
+                seprint(buf, e, "Rremove tag %ud", tag);
+                break;
+        case Tstat:        /* 124 */
+                seprint(buf, e, "Tstat tag %ud fid %ud", tag, fid);
+                break;
+        case Rstat:
+                p = seprint(buf, e, "Rstat tag %ud ", tag);
+                if(f->nstat > sizeof tmp)
+                        seprint(p, e, " stat(%d bytes)", f->nstat);
+                else{
+                        d = (Dir*)tmp;
+                        convM2D(f->stat, f->nstat, d, (char*)(d+1));
+                        seprint(p, e, " stat ");
+                        fdirconv(p+6, e, d);
+                }
+                break;
+        case Twstat:        /* 126 */
+                p = seprint(buf, e, "Twstat tag %ud fid %ud", tag, fid);
+                if(f->nstat > sizeof tmp)
+                        seprint(p, e, " stat(%d bytes)", f->nstat);
+                else{
+                        d = (Dir*)tmp;
+                        convM2D(f->stat, f->nstat, d, (char*)(d+1));
+                        seprint(p, e, " stat ");
+                        fdirconv(p+6, e, d);
+                }
+                break;
+        case Rwstat:
+                seprint(buf, e, "Rwstat tag %ud", tag);
+                break;
+        default:
+                seprint(buf, e,  "unknown type %d", type);
+        }
+        return fmtstrcpy(fmt, buf);
+}
+
+static char*
+qidtype(char *s, uchar t)
+{
+        char *p;
+
+        p = s;
+        if(t & QTDIR)
+                *p++ = 'd';
+        if(t & QTAPPEND)
+                *p++ = 'a';
+        if(t & QTEXCL)
+                *p++ = 'l';
+        if(t & QTAUTH)
+                *p++ = 'A';
+        *p = '\0';
+        return s;
+}
+
+int
+dirfmt(Fmt *fmt)
+{
+        char buf[160];
+
+        fdirconv(buf, buf+sizeof buf, va_arg(fmt->args, Dir*));
+        return fmtstrcpy(fmt, buf);
+}
+
+static void
+fdirconv(char *buf, char *e, Dir *d)
+{
+        char tmp[16];
+
+        seprint(buf, e, "'%s' '%s' '%s' '%s' "
+                "q " QIDFMT " m %#luo "
+                "at %ld mt %ld l %lld "
+                "t %d d %d",
+                        d->name, d->uid, d->gid, d->muid,
+                        d->qid.path, d->qid.vers, qidtype(tmp, d->qid.type), d->mode,
+                        d->atime, d->mtime, d->length,
+                        d->type, d->dev);
+}
+
+/*
+ * dump out count (or DUMPL, if count is bigger) bytes from
+ * buf to ans, as a string if they are all printable,
+ * else as a series of hex bytes
+ */
+#define DUMPL 64
+
+static uint
+dumpsome(char *ans, char *e, char *buf, long count)
+{
+        int i, printable;
+        char *p;
+
+        if(buf == nil){
+                seprint(ans, e, "");
+                return strlen(ans);
+        }
+        printable = 1;
+        if(count > DUMPL)
+                count = DUMPL;
+        for(i=0; i127)
+                        printable = 0;
+        p = ans;
+        *p++ = '\'';
+        if(printable){
+                if(count > e-p-2)
+                        count = e-p-2;
+                memmove(p, buf, count);
+                p += count;
+        }else{
+                if(2*count > e-p-2)
+                        count = (e-p-2)/2;
+                for(i=0; i0 && i%4==0)
+                                *p++ = ' ';
+                        sprint(p, "%2.2ux", buf[i]);
+                        p += 2;
+                }
+        }
+        *p++ = '\'';
+        *p = 0;
+        return p - ans;
+}
diff --git a/src/lib9/read9pmsg.c b/src/lib9/read9pmsg.c
t@@ -0,0 +1,31 @@
+#include 
+#include 
+#include 
+
+int
+read9pmsg(int fd, void *abuf, uint n)
+{
+        int m, len;
+        uchar *buf;
+
+        buf = abuf;
+
+        /* read count */
+        m = readn(fd, buf, BIT32SZ);
+        if(m != BIT32SZ){
+                if(m < 0)
+                        return -1;
+                return 0;
+        }
+
+        len = GBIT32(buf);
+        if(len <= BIT32SZ || len > n){
+                werrstr("bad length in 9P2000 message header");
+                return -1;
+        }
+        len -= BIT32SZ;
+        m = readn(fd, buf+BIT32SZ, len);
+        if(m < len)
+                return 0;
+        return BIT32SZ+m;
+}
diff --git a/src/libfs/COPYRIGHT b/src/libfs/COPYRIGHT
t@@ -0,0 +1,27 @@
+
+This software was developed as part of a project at MIT:
+        /sys/src/libfs/* except dirread.c
+        /sys/include/fs.h
+
+Copyright (c) 2003 Russ Cox,
+                   Massachusetts Institute of Technology
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/src/libfs/close.c b/src/libfs/close.c
t@@ -0,0 +1,26 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+static void
+fidclunk(Fid *fid)
+{
+        Fcall tx, rx;
+
+        tx.type = Tclunk;
+        tx.fid = fid->fid;
+        fsrpc(fid->fs, &tx, &rx, 0);
+        _fsputfid(fid);
+}
+
+void
+fsclose(Fid *fid)
+{
+        /* maybe someday there will be a ref count */
+        fidclunk(fid);
+}
diff --git a/src/libfs/create.c b/src/libfs/create.c
t@@ -0,0 +1,25 @@
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+Fid*
+fscreate(Fsys *fs, char *name, int mode, ulong perm)
+{
+        Fid *fid;
+        Fcall tx, rx;
+
+        if((fid = fswalk(fs->root, name)) == nil)
+                return nil;
+        tx.type = Tcreate;
+        tx.fid = fid->fid;
+        tx.mode = mode;
+        tx.perm = perm;
+        if(fsrpc(fs, &tx, &rx, 0) < 0){
+                fsclose(fid);
+                return nil;
+        }
+        fid->mode = mode;
+        return fid;
+}
diff --git a/src/libfs/dirread.c b/src/libfs/dirread.c
t@@ -0,0 +1,100 @@
+/* Mostly copied from Plan 9's libc. */
+
+#include 
+#include 
+#include 
+#include 
+
+static
+long
+dirpackage(uchar *buf, long ts, Dir **d)
+{
+        char *s;
+        long ss, i, n, nn, m;
+
+        *d = nil;
+        if(ts <= 0)
+                return 0;
+
+        /*
+         * first find number of all stats, check they look like stats, & size all associated strings
+         */
+        ss = 0;
+        n = 0;
+        for(i = 0; i < ts; i += m){
+                m = BIT16SZ + GBIT16(&buf[i]);
+                if(statcheck(&buf[i], m) < 0)
+                        break;
+                ss += m;
+                n++;
+        }
+
+        if(i != ts)
+                return -1;
+
+        *d = malloc(n * sizeof(Dir) + ss);
+        if(*d == nil)
+                return -1;
+
+        /*
+         * then convert all buffers
+         */
+        s = (char*)*d + n * sizeof(Dir);
+        nn = 0;
+        for(i = 0; i < ts; i += m){
+                m = BIT16SZ + GBIT16((uchar*)&buf[i]);
+                if(nn >= n || convM2D(&buf[i], m, *d + nn, s) != m){
+                        free(*d);
+                        *d = nil;
+                        return -1;
+                }
+                nn++;
+                s += m;
+        }
+
+        return nn;
+}
+
+long
+fsdirread(Fid *fid, Dir **d)
+{
+        uchar *buf;
+        long ts;
+
+        buf = malloc(DIRMAX);
+        if(buf == nil)
+                return -1;
+        ts = fsread(fid, buf, DIRMAX);
+        if(ts >= 0)
+                ts = dirpackage(buf, ts, d);
+        free(buf);
+        return ts;
+}
+
+long
+fsdirreadall(Fid *fid, Dir **d)
+{
+        uchar *buf, *nbuf;
+        long n, ts;
+
+        buf = nil;
+        ts = 0;
+        for(;;){
+                nbuf = realloc(buf, ts+DIRMAX);
+                if(nbuf == nil){
+                        free(buf);
+                        return -1;
+                }
+                buf = nbuf;
+                n = fsread(fid, buf+ts, DIRMAX);
+                if(n <= 0)
+                        break;
+                ts += n;
+        }
+        if(ts >= 0)
+                ts = dirpackage(buf, ts, d);
+        free(buf);
+        if(ts == 0 && n < 0)
+                return -1;
+        return ts;
+}
diff --git a/src/libfs/fs.c b/src/libfs/fs.c
t@@ -0,0 +1,276 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+static int _fssend(Mux*, void*);
+static void *_fsrecv(Mux*);
+static int _fsgettag(Mux*, void*);
+static int _fssettag(Mux*, void*, uint);
+
+enum
+{
+        Fidchunk = 32
+};
+
+Fsys*
+fsinit(int fd)
+{
+        Fsys *fs;
+
+        fs = mallocz(sizeof(Fsys), 1);
+        if(fs == nil)
+                return nil;
+        fs->fd = fd;
+        fs->ref = 1;
+        fs->mux.aux = fs;
+        fs->mux.mintag = 0;
+        fs->mux.maxtag = 256;
+        fs->mux.send = _fssend;
+        fs->mux.recv = _fsrecv;
+        fs->mux.gettag = _fsgettag;
+        fs->mux.settag = _fssettag;
+        muxinit(&fs->mux);
+        return fs;
+}
+
+Fid*
+fsroot(Fsys *fs)
+{
+        /* N.B. no incref */
+        return fs->root;
+}
+
+Fsys*
+fsmount(int fd)
+{
+        int n;
+        char *user;
+        Fsys *fs;
+
+        fs = fsinit(fd);
+        if(fs == nil)
+                return nil;
+        strcpy(fs->version, "9P2000");
+        if((n = fsversion(fs, 8192, fs->version, sizeof fs->version)) < 0){
+        Error:
+                fsunmount(fs);
+                return nil;
+        }
+        fs->msize = n;
+
+        user = getuser();
+        if((fs->root = fsattach(fs, nil, getuser(), "")) == nil)
+                goto Error;
+        return fs;
+}
+
+void
+fsunmount(Fsys *fs)
+{
+        _fsdecref(fs);
+}
+
+void
+_fsdecref(Fsys *fs)
+{
+        Fid *f, *next;
+
+        qlock(&fs->lk);
+        if(--fs->ref == 0){
+                close(fs->fd);
+                for(f=fs->freefid; f; f=next){
+                        next = f->next;
+                        if(f->fid%Fidchunk == 0)
+                                free(f);
+                }
+                free(fs);
+        }
+        qunlock(&fs->lk);
+}
+
+int
+fsversion(Fsys *fs, int msize, char *version, int nversion)
+{
+        void *freep;
+        Fcall tx, rx;
+
+        tx.type = Tversion;
+        tx.version = version;
+        tx.msize = msize;
+
+        if(fsrpc(fs, &tx, &rx, &freep) < 0)
+                return -1;
+        strecpy(version, version+nversion, rx.version);
+        free(freep);
+        return rx.msize;
+}
+
+Fid*
+fsattach(Fsys *fs, Fid *afid, char *user, char *aname)
+{
+        Fcall tx, rx;
+        Fid *fid;
+
+        if((fid = _fsgetfid(fs)) == nil)
+                return nil;
+
+        tx.type = Tattach;
+        tx.afid = afid ? afid->fid : NOFID;
+        tx.fid = fid->fid;
+        tx.uname = user;
+        tx.aname = aname;
+
+        if(fsrpc(fs, &tx, &rx, 0) < 0){
+                _fsputfid(fid);
+                return nil;
+        }
+        fid->qid = rx.qid;
+        return fid;
+}
+
+int
+fsrpc(Fsys *fs, Fcall *tx, Fcall *rx, void **freep)
+{
+        int n, nn;
+        void *tpkt, *rpkt;
+
+        n = sizeS2M(tx);
+        tpkt = malloc(n);
+        if(tpkt == nil)
+                return -1;
+        nn = convS2M(tx, tpkt, n);
+        if(nn != n){
+                free(tpkt);
+                werrstr("libfs: sizeS2M convS2M mismatch");
+                fprint(2, "%r\n");
+                return -1;
+        }
+        rpkt = muxrpc(&fs->mux, tpkt);
+        free(tpkt);
+        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");
+                fprint(2, "%r\n");
+                return -1;
+        }
+        if(rx->type == Rerror){
+                werrstr("%s", rx->ename);
+                free(rpkt);
+                return -1;
+        }
+        if(rx->type != tx->type+1){
+                werrstr("packet type mismatch -- tx %d rx %d",
+                        tx->type, rx->type);
+                free(rpkt);
+                return -1;
+        }
+        if(freep)
+                *freep = rpkt;
+        else
+                free(rpkt);
+        return 0;
+}
+
+Fid*
+_fsgetfid(Fsys *fs)
+{
+        int i;
+        Fid *f;
+
+        qlock(&fs->lk);
+        if(fs->freefid == nil){
+                f = malloc(sizeof(Fid)*Fidchunk);
+                if(f == nil){
+                        qunlock(&fs->lk);
+                        return nil;
+                }
+                for(i=0; inextfid++;
+                        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;
+        qunlock(&fs->lk);
+        return f;
+}
+
+void
+_fsputfid(Fid *f)
+{
+        Fsys *fs;
+
+        fs = f->fs;
+        qlock(&fs->lk);
+        f->next = fs->freefid;
+        fs->freefid = f;
+        qunlock(&fs->lk);
+        _fsdecref(fs);
+}
+
+static int
+_fsgettag(Mux *mux, void *pkt)
+{
+        return GBIT16((uchar*)pkt+5);
+}
+
+static int
+_fssettag(Mux *mux, void *pkt, uint tag)
+{
+        PBIT16((uchar*)pkt+5, tag);
+        return 0;
+}
+
+static int
+_fssend(Mux *mux, void *pkt)
+{
+        Fsys *fs;
+
+        fs = mux->aux;
+        return write(fs->fd, pkt, GBIT32((uchar*)pkt));
+}
+
+static void*
+_fsrecv(Mux *mux)
+{
+        uchar *pkt;
+        uchar buf[4];
+        int n;
+        Fsys *fs;
+
+        fs = mux->aux;
+        n = readn(fs->fd, buf, 4);
+        if(n != 4)
+                return nil;
+        n = GBIT32(buf);
+        pkt = malloc(n+4);
+        if(pkt == nil){
+                fprint(2, "libfs out of memory reading 9p packet; here comes trouble\n");
+                return nil;
+        }
+        PBIT32(buf, n);
+        if(readn(fs->fd, pkt+4, n-4) != n-4){
+                free(pkt);
+                return nil;
+        }
+#if 0
+        if(pkt[4] == Ropenfd){
+                /* do unix socket crap */
+                sysfatal("no socket crap implemented");
+        }
+#endif
+        return pkt;
+}
diff --git a/src/libfs/fsimpl.h b/src/libfs/fsimpl.h
t@@ -0,0 +1,41 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+typedef struct Queue Queue;
+Queue *_fsqalloc(void);
+int _fsqsend(Queue*, void*);
+void *_fsqrecv(Queue*);
+void _fsqhangup(Queue*);
+void *_fsnbqrecv(Queue*);
+
+#include 
+struct Fsys
+{
+        char version[20];
+        int msize;
+        QLock lk;
+        int fd;
+        int ref;
+        Mux mux;
+        Fid *root;
+        Queue *txq;
+        Queue *rxq;
+        Fid *freefid;
+        int nextfid;
+};
+
+struct Fid
+{
+        int fid;
+        int mode;
+        Fid *next;
+        QLock lk;
+        Fsys *fs;
+        Qid qid;
+        vlong offset;
+};
+
+void _fsdecref(Fsys*);
+void _fsputfid(Fid*);
+Fid *_fsgetfid(Fsys*);
+
diff --git a/src/libfs/mkfile b/src/libfs/mkfile
t@@ -0,0 +1,22 @@
+PLAN9=../..
+<$PLAN9/src/mkhdr
+
+LIB=libfs.a
+
+OFILES=\
+        close.$O\
+        create.$O\
+        dirread.$O\
+        fs.$O\
+        open.$O\
+        read.$O\
+        stat.$O\
+        walk.$O\
+        write.$O\
+        wstat.$O\
+
+HFILES=\
+        $PLAN9/include/fs.h\
+        $PLAN9/include/mux.h\
+
+<$PLAN9/src/mksyslib
diff --git a/src/libfs/open.c b/src/libfs/open.c
t@@ -0,0 +1,24 @@
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+Fid*
+fsopen(Fsys *fs, char *name, int mode)
+{
+        Fid *fid;
+        Fcall tx, rx;
+
+        if((fid = fswalk(fs->root, name)) == nil)
+                return nil;
+        tx.type = Topen;
+        tx.fid = fid->fid;
+        tx.mode = mode;
+        if(fsrpc(fs, &tx, &rx, 0) < 0){
+                fsclose(fid);
+                return nil;
+        }
+        fid->mode = mode;
+        return fid;
+}
diff --git a/src/libfs/read.c b/src/libfs/read.c
t@@ -0,0 +1,48 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+long
+fspread(Fid *fid, void *buf, long n, vlong offset)
+{
+        Fcall tx, rx;
+        void *freep;
+
+        tx.type = Tread;
+        if(offset == -1){
+                qlock(&fid->lk);
+                tx.offset = fid->offset;
+                qunlock(&fid->lk);
+        }else
+                tx.offset = offset;
+        tx.count = n;
+
+        fsrpc(fid->fs, &tx, &rx, &freep);
+        if(rx.type == Rerror){
+                werrstr("%s", rx.ename);
+                free(freep);
+                return -1;
+        }
+        if(rx.count){
+                memmove(buf, rx.data, rx.count);
+                if(offset == -1){
+                        qlock(&fid->lk);
+                        tx.offset += n;
+                        qunlock(&fid->lk);
+                }
+        }
+        free(freep);
+        
+        return rx.count;
+}
+
+long
+fsread(Fid *fid, void *buf, long n)
+{
+        return fspread(fid, buf, n, -1);
+}
diff --git a/src/libfs/stat.c b/src/libfs/stat.c
t@@ -0,0 +1,54 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+Dir*
+fsdirstat(Fsys *fs, char *name)
+{
+        Dir *d;
+        Fid *fid;
+
+        if((fid = fswalk(fs->root, name)) == nil)
+                return nil;
+        
+        d = fsdirfstat(fid);
+        fsclose(fid);
+        return d;
+}
+
+Dir*
+fsdirfstat(Fid *fid)
+{
+        Dir *d;
+        Fsys *fs;
+        Fcall tx, rx;
+        void *freep;
+        int n;
+
+        fs = fid->fs;
+        tx.type = Tstat;
+        tx.fid = fid->fid;
+
+        if(fsrpc(fs, &tx, &rx, &freep) < 0)
+                return nil;
+
+        d = malloc(sizeof(Dir)+rx.nstat);
+        if(d == nil){
+                free(freep);
+                return nil;
+        }
+        n = convM2D(rx.stat, rx.nstat, d, (char*)&d[1]);
+        free(freep);
+        if(n != rx.nstat){
+                free(d);
+                werrstr("rx.nstat and convM2D disagree about dir length");
+                return nil;
+        }
+        return d;
+}
+
diff --git a/src/libfs/walk.c b/src/libfs/walk.c
t@@ -0,0 +1,73 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+Fid*
+fswalk(Fid *fid, char *oname)
+{
+        char *freep, *name;
+        int i, nwalk;
+        char *p;
+        Fid *wfid;
+        Fcall tx, rx;
+
+        freep = nil;
+        name = oname;
+        if(name){
+                freep = malloc(strlen(name)+1);
+                if(freep == nil)
+                        return nil;
+                strcpy(freep, name);
+                name = freep;
+        }
+
+        if((wfid = _fsgetfid(fid->fs)) == nil){
+                free(freep);
+                return nil;
+        }
+
+        nwalk = 0;
+        do{
+                /* collect names */
+                for(i=0; name && *name && i < MAXWELEM; ){
+                        p = name;
+                        name = strchr(name, '/');
+                        if(name)
+                                *name++ = 0;
+                        if(*p == 0)
+                                continue;
+                        tx.wname[i++] = p;
+                }
+
+                /* do a walk */
+                tx.type = Twalk;
+                tx.fid = nwalk ? wfid->fid : fid->fid;
+                tx.newfid = wfid->fid;
+                tx.nwname = i;
+                if(fsrpc(fid->fs, &tx, &rx, 0) < 0){
+                Error:
+                        free(freep);
+                        if(nwalk)
+                                fsclose(wfid);
+                        else
+                                _fsputfid(wfid);
+                        return nil;
+                }
+                if(rx.nwqid != tx.nwname){
+                        /* XXX lame error */
+                        werrstr("file '%s' not found", oname);
+                        goto Error;
+                }
+                if(rx.nwqid == 0)
+                        wfid->qid = fid->qid;
+                else
+                        wfid->qid = rx.wqid[rx.nwqid-1];
+                nwalk++;
+        }while(name && *name);
+        return wfid;
+}
diff --git a/src/libfs/write.c b/src/libfs/write.c
t@@ -0,0 +1,46 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+long
+fspwrite(Fid *fd, void *buf, long n, vlong offset)
+{
+        Fcall tx, rx;
+        void *freep;
+
+        tx.type = Tread;
+        if(offset == -1){
+                qlock(&fd->lk);
+                tx.offset = fd->offset;
+                fd->offset += n;
+                qunlock(&fd->lk);
+        }else
+                tx.offset = offset;
+        tx.count = n;
+        tx.data = buf;
+
+        fsrpc(fd->fs, &tx, &rx, &freep);
+        if(rx.type == Rerror){
+                if(offset == -1){
+                        qlock(&fd->lk);
+                        fd->offset -= n;
+                        qunlock(&fd->lk);
+                }
+                werrstr("%s", rx.ename);
+                free(freep);
+                return -1;
+        }
+        free(freep);
+        return rx.count;
+}
+
+long
+fswrite(Fid *fd, void *buf, long n)
+{
+        return fspwrite(fd, buf, n, -1);
+}
diff --git a/src/libfs/wstat.c b/src/libfs/wstat.c
t@@ -0,0 +1,49 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+#include "fsimpl.h"
+
+int
+fsdirwstat(Fsys *fs, char *name, Dir *d)
+{
+        int n;
+        Fid *fid;
+
+        if((fid = fswalk(fs->root, name)) == nil)
+                return -1;
+        
+        n = fsdirfwstat(fid, d);
+        fsclose(fid);
+        return n;
+}
+
+int
+fsdirfwstat(Fid *fid, Dir *d)
+{
+        char *a;
+        int n, nn;
+        Fcall tx, rx;
+
+        n = sizeD2M(d);
+        a = malloc(n);
+        if(a == nil)
+                return -1;
+        nn = convD2M(d, a, n);
+        if(n != nn){
+                werrstr("convD2M and sizeD2M disagree");
+                free(a);
+                return -1;
+        }
+
+        tx.type = Twstat;
+        tx.fid = fid->fid;
+        tx.stat = a;
+        tx.nstat = n;
+        n = fsrpc(fid->fs, &tx, &rx, 0);
+        free(a);
+        return n;
+}
diff --git a/src/libmux/COPYRIGHT b/src/libmux/COPYRIGHT
t@@ -0,0 +1,27 @@
+
+This software was developed as part of a project at MIT:
+        /sys/src/libmux/*
+        /sys/include/mux.h
+
+Copyright (c) 2003 Russ Cox,
+                   Massachusetts Institute of Technology
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/src/libmux/io.c b/src/libmux/io.c
t@@ -0,0 +1,136 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+
+/*
+ * If you fork off two procs running muxrecvproc and muxsendproc,
+ * then muxrecv/muxsend (and thus muxrpc) will never block except on 
+ * rendevouses, which is nice when it's running in one thread of many.
+ */
+void
+_muxrecvproc(void *v)
+{
+        void *p;
+        Mux *mux;
+        Muxqueue *q;
+
+        mux = v;
+        q = _muxqalloc();
+
+        qlock(&mux->lk);
+        mux->readq = q;
+        qlock(&mux->inlk);
+        rwakeup(&mux->rpcfork);
+        qunlock(&mux->lk);
+
+        while((p = mux->recv(mux)) != nil)
+                if(_muxqsend(q, p) < 0){
+                        free(p);
+                        break;
+                }
+        qunlock(&mux->inlk);
+        qlock(&mux->lk);
+        _muxqhangup(q);
+        while((p = _muxnbqrecv(q)) != nil)
+                free(p);
+        free(q);
+        mux->readq = nil;
+        rwakeup(&mux->rpcfork);
+        qunlock(&mux->lk);
+}
+
+void
+_muxsendproc(void *v)
+{
+        Muxqueue *q;
+        void *p;
+        Mux *mux;
+
+        mux = v;
+        q = _muxqalloc();
+
+        qlock(&mux->lk);
+        mux->writeq = q;
+        qlock(&mux->outlk);
+        rwakeup(&mux->rpcfork);
+        qunlock(&mux->lk);
+
+        while((p = _muxqrecv(q)) != nil)
+                if(mux->send(mux, p) < 0)
+                        break;
+        qunlock(&mux->outlk);
+        qlock(&mux->lk);
+        _muxqhangup(q);
+        while((p = _muxnbqrecv(q)) != nil)
+                free(p);
+        free(q);
+        mux->writeq = nil;
+        rwakeup(&mux->rpcfork);
+        qunlock(&mux->lk);
+        return;
+}
+
+void*
+_muxrecv(Mux *mux)
+{
+        void *p;
+
+        qlock(&mux->lk);
+/*
+        if(mux->state != VtStateConnected){
+                werrstr("not connected");
+                qunlock(&mux->lk);
+                return nil;
+        }
+*/
+        if(mux->readq){
+                qunlock(&mux->lk);
+                return _muxqrecv(mux->readq);
+        }
+
+        qlock(&mux->inlk);
+        qunlock(&mux->lk);
+        p = mux->recv(mux);
+        qunlock(&mux->inlk);
+/*
+        if(!p)
+                vthangup(mux);
+*/
+        return p;
+}
+
+int
+_muxsend(Mux *mux, void *p)
+{
+        qlock(&mux->lk);
+/*
+        if(mux->state != VtStateConnected){
+                packetfree(p);
+                werrstr("not connected");
+                qunlock(&mux->lk);
+                return -1;
+        }
+*/
+        if(mux->writeq){
+                qunlock(&mux->lk);
+                if(_muxqsend(mux->writeq, p) < 0){
+                        free(p);
+                        return -1;
+                }
+                return 0;
+        }
+
+        qlock(&mux->outlk);
+        qunlock(&mux->lk);
+        if(mux->send(mux, p) < 0){
+                qunlock(&mux->outlk);
+                /* vthangup(mux); */
+                return -1;        
+        }
+        qunlock(&mux->outlk);
+        return 0;
+}
+
diff --git a/src/libmux/mkfile b/src/libmux/mkfile
t@@ -0,0 +1,16 @@
+PLAN9=../..
+<$PLAN9/src/mkhdr
+
+LIB=libmux.a
+
+OFILES=\
+        io.$O\
+        mux.$O\
+        queue.$O\
+        thread.$O\
+
+HFILES=\
+        $PLAN9/include/mux.h\
+
+<$PLAN9/src/mksyslib
+
diff --git a/src/libmux/mux.c b/src/libmux/mux.c
t@@ -0,0 +1,152 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+/*
+ * Generic RPC packet multiplexor.  Inspired by but not derived from
+ * Plan 9 kernel.  Originally developed as part of Tra, later used in
+ * libnventi, and then finally split out into a generic library.
+ */
+
+#include 
+#include 
+#include 
+
+static int gettag(Mux*, Muxrpc*);
+static void puttag(Mux*, Muxrpc*);
+static void enqueue(Mux*, Muxrpc*);
+static void dequeue(Mux*, Muxrpc*);
+
+void
+muxinit(Mux *mux)
+{
+        mux->tagrend.l = &mux->lk;
+        mux->sleep.next = &mux->sleep;
+        mux->sleep.prev = &mux->sleep;
+}
+
+void*
+muxrpc(Mux *mux, void *tx)
+{
+        uint tag;
+        Muxrpc *r, *r2;
+        void *p;
+
+        /* must malloc because stack could be private */
+        r = mallocz(sizeof(Muxrpc), 1);
+        if(r == nil)
+                return nil;
+        r->r.l = &mux->lk;
+
+        /* assign the tag */
+        tag = gettag(mux, r);
+        if(mux->settag(mux, tx, tag) < 0){
+                puttag(mux, r);
+                free(r);
+                return nil;
+        }
+
+        /* send the packet */
+        if(_muxsend(mux, tx) < 0){
+                puttag(mux, r);
+                free(r);
+                return nil;
+        }
+
+        /* add ourselves to sleep queue */
+        qlock(&mux->lk);
+        enqueue(mux, r);
+
+        /* wait for our packet */
+        while(mux->muxer && !r->p)
+                rsleep(&r->r);
+
+        /* if not done, there's no muxer: start muxing */
+        if(!r->p){
+                if(mux->muxer)
+                        abort();
+                mux->muxer = 1;
+                while(!r->p){
+                        qunlock(&mux->lk);
+                        p = _muxrecv(mux);
+                        if(p)
+                                tag = mux->gettag(mux, p);
+                        else
+                                tag = ~0;
+                        qlock(&mux->lk);
+                        if(p == nil){        /* eof -- just give up and pass the buck */
+                                dequeue(mux, r);
+                                break;
+                        }
+                        /* hand packet to correct sleeper */
+                        if(tag < 0 || tag >= mux->mwait){
+                                fprint(2, "%s: bad rpc tag %ux\n", argv0, tag);
+                                /* must leak packet! don't know how to free it! */
+                                continue;
+                        }
+                        r2 = mux->wait[tag];
+                        r2->p = p;
+                        rwakeup(&r2->r);
+                }
+                mux->muxer = 0;
+
+                /* if there is anyone else sleeping, wake them to mux */
+                if(mux->sleep.next != &mux->sleep)
+                        rwakeup(&mux->sleep.next->r);
+        }
+        p = r->p;
+        puttag(mux, r);
+        free(r);
+        qunlock(&mux->lk);
+        return p;
+}
+
+static void
+enqueue(Mux *mux, Muxrpc *r)
+{
+        r->next = mux->sleep.next;
+        r->prev = &mux->sleep;
+        r->next->prev = r;
+        r->prev->next = r;
+}
+
+static void
+dequeue(Mux *mux, Muxrpc *r)
+{
+        r->next->prev = r->prev;
+        r->prev->next = r->next;
+        r->prev = nil;
+        r->next = nil;
+}
+
+static int 
+gettag(Mux *mux, Muxrpc *r)
+{
+        int i;
+
+Again:
+        while(mux->nwait == mux->mwait)
+                rsleep(&mux->tagrend);
+        i=mux->freetag;
+        if(mux->wait[i] == 0)
+                goto Found;
+        for(i=0; imwait; i++)
+                if(mux->wait[i] == 0){
+                Found:
+                        mux->nwait++;
+                        mux->wait[i] = r;
+                        r->tag = i;
+                        return i;
+                }
+        fprint(2, "libfs: nwait botch\n");
+        goto Again;
+}
+
+static void
+puttag(Mux *mux, Muxrpc *r)
+{
+        assert(mux->wait[r->tag] == r);
+        mux->wait[r->tag] = nil;
+        mux->nwait--;
+        mux->freetag = r->tag;
+        rwakeup(&mux->tagrend);
+}
diff --git a/src/libmux/queue.c b/src/libmux/queue.c
t@@ -0,0 +1,109 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+
+typedef struct Qel Qel;
+struct Qel
+{
+        Qel *next;
+        void *p;
+};
+
+struct Muxqueue
+{
+        int hungup;
+        QLock lk;
+        Rendez r;
+        Qel *head;
+        Qel *tail;
+};
+
+Muxqueue*
+_muxqalloc(void)
+{
+        Muxqueue *q;
+
+        q = mallocz(sizeof(Muxqueue), 1);
+        if(q == nil)
+                return nil;
+        q->r.l = &q->lk;
+        return q;
+}
+
+int
+_muxqsend(Muxqueue *q, void *p)
+{
+        Qel *e;
+
+        e = malloc(sizeof(Qel));
+        if(e == nil)
+                return -1;
+        qlock(&q->lk);
+        if(q->hungup){
+                werrstr("hungup queue");
+                qunlock(&q->lk);
+                return -1;
+        }
+        e->p = p;
+        e->next = nil;
+        if(q->head == nil)
+                q->head = e;
+        else
+                q->tail->next = e;
+        q->tail = e;
+        rwakeup(&q->r);
+        qunlock(&q->lk);
+        return 0;
+}
+
+void*
+_muxqrecv(Muxqueue *q)
+{
+        void *p;
+        Qel *e;
+
+        qlock(&q->lk);
+        while(q->head == nil && !q->hungup)
+                rsleep(&q->r);
+        if(q->hungup){
+                qunlock(&q->lk);
+                return nil;
+        }
+        e = q->head;
+        q->head = e->next;
+        qunlock(&q->lk);
+        p = e->p;
+        free(e);
+        return p;
+}
+
+void*
+_muxnbqrecv(Muxqueue *q)
+{
+        void *p;
+        Qel *e;
+
+        qlock(&q->lk);
+        if(q->head == nil){
+                qunlock(&q->lk);
+                return nil;
+        }
+        e = q->head;
+        q->head = e->next;
+        qunlock(&q->lk);
+        p = e->p;
+        free(e);
+        return p;
+}
+
+void
+_muxqhangup(Muxqueue *q)
+{
+        qlock(&q->lk);
+        q->hungup = 1;
+        rwakeupall(&q->r);
+        qunlock(&q->lk);
+}
diff --git a/src/libmux/thread.c b/src/libmux/thread.c
t@@ -0,0 +1,27 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include 
+#include 
+#include 
+#include 
+
+enum
+{
+        STACK = 32768
+};
+
+void
+muxthreads(Mux *mux)
+{
+        proccreate(_muxrecvproc, mux, STACK);
+        qlock(&mux->lk);
+        while(!mux->writeq)
+                rsleep(&mux->rpcfork);
+        qunlock(&mux->lk);
+        proccreate(_muxsendproc, mux, STACK);
+        qlock(&mux->lk);
+        while(!mux->writeq)
+                rsleep(&mux->rpcfork);
+        qunlock(&mux->lk);
+}