Commit Diff


commit - e97ceade5e1bba5787e39429384336fa37797906
commit + d3df308747ee4d1fcc063a348dcf1146b390bda7
blob - /dev/null
blob + 219b2774a5a98dbd58e8e9b4ee871215fec510fa (mode 644)
--- /dev/null
+++ include/fs.h
@@ -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
blob - /dev/null
blob + c43ce797a7cf70932f07e59d547e60dd37d6b299 (mode 644)
--- /dev/null
+++ include/mux.h
@@ -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*);
+
blob - /dev/null
blob + 7c018c34b542cbfe666ab884688d000234dd6040 (mode 644)
--- /dev/null
+++ src/cmd/9p.c
@@ -0,0 +1,191 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+
+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<nelem(cmds); i++){
+		if(strcmp(cmds[i].s, cmd) == 0){
+			cmds[i].f(argc, argv);
+			exits(0);
+		}
+	}
+	usage();	
+}
+
+Fsys*
+xparse(char *name, char **path)
+{
+	int fd;
+	char *ns;
+	char *p;
+	Fsys *fs;
+
+	if(addr == nil){
+		p = strchr(name, '/');
+		if(p == nil)
+			p = name+strlen(name);
+		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
+		*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");
+	return fs;
+}
+
+Fid*
+xwalk(char *name)
+{
+	Fid *fid;
+	Fsys *fs;
+
+	fs = xparse(name, &name);
+	fid = fswalk(fsroot(fs), name);
+	if(fid == nil)
+		sysfatal("fswalk %s: %r", name);
+	return fid;
+}
+
+Fid*
+xopen(char *name, int mode)
+{
+	Fid *fid;
+	Fsys *fs;
+
+	fs = xparse(name, &name);
+	fid = fsopen(fs, name, mode);
+	if(fid == nil)
+		sysfatal("fsopen %s: %r", name);
+	return fid;
+}
+
+void
+xread(int argc, char **argv)
+{
+	char buf[1024];
+	int n;
+	Fid *fid;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 1)
+		usage();
+
+	fid = xopen(argv[0], OREAD);
+	while((n = fsread(fid, 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];
+	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);
+}
blob - /dev/null
blob + 40db17e52f8d9338317ec8f96b283f1ab0809947 (mode 644)
--- /dev/null
+++ src/cmd/9pserve.c
@@ -0,0 +1,597 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+
+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; i<NHASH; i++){
+		for(h=c->tag[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; i<NHASH; i++){
+		for(h=c->fid[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;
+}
blob - /dev/null
blob + 5acee7e5c19f947dbab2084fd43532f6d613a9b3 (mode 644)
--- /dev/null
+++ src/lib9/convD2M.c
@@ -0,0 +1,95 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+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;
+}
blob - /dev/null
blob + 6f4b4bd935bef68910883fe15680609ff24489f0 (mode 644)
--- /dev/null
+++ src/lib9/convM2D.c
@@ -0,0 +1,94 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+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;
+}
blob - /dev/null
blob + fcdcd42d60f6f0d533747aab47b01af7651cf926 (mode 644)
--- /dev/null
+++ src/lib9/convM2S.c
@@ -0,0 +1,315 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+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; i<f->nwname; 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; i<f->nwqid; 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;
+}
blob - /dev/null
blob + 9acdcfa567fbb744c66980c00bea5397ad895f98 (mode 644)
--- /dev/null
+++ src/lib9/convS2M.c
@@ -0,0 +1,386 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+
+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; i<f->nwname; 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; i<f->nwname; 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; i<f->nwqid; 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;
+}
blob - /dev/null
blob + 4eef88dece82b5c102c1a4deb66119bfaff3e2a3 (mode 644)
--- /dev/null
+++ src/lib9/fcallfmt.c
@@ -0,0 +1,234 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+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; i<f->nwname; 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; i<f->nwqid; 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, "<no data>");
+		return strlen(ans);
+	}
+	printable = 1;
+	if(count > DUMPL)
+		count = DUMPL;
+	for(i=0; i<count && printable; i++)
+		if((buf[i]<32 && buf[i] !='\n' && buf[i] !='\t') || (uchar)buf[i]>127)
+			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; i<count; i++){
+			if(i>0 && i%4==0)
+				*p++ = ' ';
+			sprint(p, "%2.2ux", buf[i]);
+			p += 2;
+		}
+	}
+	*p++ = '\'';
+	*p = 0;
+	return p - ans;
+}
blob - /dev/null
blob + 9e90ec5d6c668675519d2dd4f000580839be8066 (mode 644)
--- /dev/null
+++ src/lib9/read9pmsg.c
@@ -0,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+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;
+}
blob - /dev/null
blob + de348d126b2bed47cefa2f493e03f8f1e23c5b19 (mode 644)
--- /dev/null
+++ src/libfs/COPYRIGHT
@@ -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.
+
blob - /dev/null
blob + 23388ae3e74836774f8d27b8309836abb03e0ce2 (mode 644)
--- /dev/null
+++ src/libfs/close.c
@@ -0,0 +1,26 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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);
+}
blob - /dev/null
blob + ef52a2a8ca4bee13d65e028f533b22591ff30c65 (mode 644)
--- /dev/null
+++ src/libfs/create.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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;
+}
blob - /dev/null
blob + 0ca4064542ef8c8f41aa98d736f11e005eb87d80 (mode 644)
--- /dev/null
+++ src/libfs/dirread.c
@@ -0,0 +1,100 @@
+/* Mostly copied from Plan 9's libc. */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+
+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;
+}
blob - /dev/null
blob + 985071c1c69ce4a6f45e996f624889f647ffea85 (mode 644)
--- /dev/null
+++ src/libfs/fs.c
@@ -0,0 +1,276 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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; i<Fidchunk; i++){
+			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;
+	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;
+}
blob - /dev/null
blob + fbcc37771b5ea281353c117d6b0ab37c36184f5b (mode 644)
--- /dev/null
+++ src/libfs/fsimpl.h
@@ -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 <mux.h>
+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*);
+
blob - /dev/null
blob + acfb0ae5990ba8fdbdfb4eba69913ff94ec5c756 (mode 644)
--- /dev/null
+++ src/libfs/mkfile
@@ -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
blob - /dev/null
blob + 458c5dac3dc0fbb80a934984540cea2d8b402e0b (mode 644)
--- /dev/null
+++ src/libfs/open.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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;
+}
blob - /dev/null
blob + ca6c628c4e909244c6f163b03aa208b6fe4c8d3a (mode 644)
--- /dev/null
+++ src/libfs/read.c
@@ -0,0 +1,48 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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);
+}
blob - /dev/null
blob + e55e0b126e72c18fa18d454d43c55aaa3ec3f1b7 (mode 644)
--- /dev/null
+++ src/libfs/stat.c
@@ -0,0 +1,54 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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;
+}
+
blob - /dev/null
blob + ad4eddc7727dea7d76696c008fe64467e72685c3 (mode 644)
--- /dev/null
+++ src/libfs/walk.c
@@ -0,0 +1,73 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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;
+}
blob - /dev/null
blob + 96ecfa864653941be06444505860c82f6041545d (mode 644)
--- /dev/null
+++ src/libfs/write.c
@@ -0,0 +1,46 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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);
+}
blob - /dev/null
blob + 90ae0e0fce7250880e3d1e0307e37d71ed3a307f (mode 644)
--- /dev/null
+++ src/libfs/wstat.c
@@ -0,0 +1,49 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <fs.h>
+#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;
+}
blob - /dev/null
blob + d9679c3ae2ec4630c535f35b4efb85129d1e2065 (mode 644)
--- /dev/null
+++ src/libmux/COPYRIGHT
@@ -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.
+
blob - /dev/null
blob + 3d932b1a1f4925bd0c79192b237ef990309bb667 (mode 644)
--- /dev/null
+++ src/libmux/io.c
@@ -0,0 +1,136 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <mux.h>
+
+/*
+ * 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;
+}
+
blob - /dev/null
blob + 71d62a433c5a346bcc007203d4f6fc61a5e16cde (mode 644)
--- /dev/null
+++ src/libmux/mkfile
@@ -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
+
blob - /dev/null
blob + 0d33498e31dc9ac1db29f93a84f454c103204ca3 (mode 644)
--- /dev/null
+++ src/libmux/mux.c
@@ -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 <u.h>
+#include <libc.h>
+#include <mux.h>
+
+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; i<mux->mwait; 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);
+}
blob - /dev/null
blob + 072f18609bb6ddf7273258b184174834b7c0dfff (mode 644)
--- /dev/null
+++ src/libmux/queue.c
@@ -0,0 +1,109 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <mux.h>
+
+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);
+}
blob - /dev/null
blob + 1c643e06df7b6c94cd18d9b10dc112971f19b072 (mode 644)
--- /dev/null
+++ src/libmux/thread.c
@@ -0,0 +1,27 @@
+/* Copyright (C) 2003 Russ Cox, Massachusetts Institute of Technology */
+/* See COPYRIGHT */
+
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <mux.h>
+
+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);
+}