shithub: 9pro

Download patch

ref: 071f360f5723668a1836c15723d75b988f3714b7
parent: 71bee3deb73e04c314ed968533f4c04d497e8c2f
parent: 9c70d9eff63775f79cdf91a5cea86de8a169bcbd
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Tue Oct 1 22:43:26 EDT 2024

Merge commit '9c70d9eff63775f79cdf91a5cea86de8a169bcbd'

--- a/c9/README.md
+++ b/c9/README.md
@@ -1,8 +1,25 @@
 # c9
 
-Low level 9p client and server.
+NOTE (2022-06-27): the library's (yep, it's going to be a library)
+API is getting completely revamped, and examples are being added.
+Use either an older version, or expect API breakage.
 
-## Examples
+A lightweight library for implmeneting 9p clients and servers in C.
 
-Until I have time to write a minimal example you could take a look at 
-https://git.sr.ht/~ft/9pro/blob/master/9pex.c
+This is 9p client and server implementation which aims to be correct,
+small and secure, targetting mainly low-resource MCUs.
+
+`proto.[ch]`: lowest level of the library, contains nothing but
+(de)serealization of the protocol messages and tag (de)allocation.
+
+`fs.h`: higher level, blocking file API based on POSIX.
+
+`fs9p.[ch]`: blocking file API implementation for 9p - having a
+context created with `proto.[ch]` one can use POSIX-like file API to
+work with a remote filesystem.
+
+Goals:
+
+* portability: zero external dependencies, no POSIX, just C
+* no dynamic memory allocation
+* security: no crashes due to incoming data
--- a/c9/c9.c
+++ /dev/null
@@ -1,1177 +1,0 @@
-/*
- * This is 9p client and server implementation which aims to be
- * correct, small and secure. It's the lowest level implementation.
- * It doesn't have much comments, mostly because it doesn't make
- * any sense to copy-paste protocol documentation, which
- * you can read at http://man.cat-v.org/plan_9/5/, see 'intro'.
- */
-#ifdef __plan9__
-#include <u.h>
-#include <libc.h>
-#else
-#include <stdint.h>
-#include <string.h>
-#endif
-#include "c9.h"
-
-enum
-{
-	Svver = 1<<0,
-};
-
-#define safestrlen(s) (s == NULL ? 0 : (uint32_t)strlen(s))
-#define maxread(c) (c->msize-4-4-1-2)
-#define maxwrite(c) maxread(c)
-
-static void
-w08(uint8_t **p, uint8_t x)
-{
-	(*p)[0] = x;
-	*p += 1;
-}
-
-static void
-w16(uint8_t **p, uint16_t x)
-{
-	(*p)[0] = x;
-	(*p)[1] = x>>8;
-	*p += 2;
-}
-
-static void
-w32(uint8_t **p, uint32_t x)
-{
-	(*p)[0] = x;
-	(*p)[1] = x>>8;
-	(*p)[2] = x>>16;
-	(*p)[3] = x>>24;
-	*p += 4;
-}
-
-static void
-w64(uint8_t **p, uint64_t x)
-{
-	(*p)[0] = x;
-	(*p)[1] = x>>8;
-	(*p)[2] = x>>16;
-	(*p)[3] = x>>24;
-	(*p)[4] = x>>32;
-	(*p)[5] = x>>40;
-	(*p)[6] = x>>48;
-	(*p)[7] = x>>56;
-	*p += 8;
-}
-
-static void
-wcs(uint8_t **p, const char *s, int len)
-{
-	w16(p, len);
-	if(s != NULL){
-		memmove(*p, s, len);
-		*p += len;
-	}
-}
-
-static uint8_t
-r08(uint8_t **p)
-{
-	*p += 1;
-	return (*p)[-1];
-}
-
-static uint16_t
-r16(uint8_t **p)
-{
-	*p += 2;
-	return (*p)[-2]<<0 | (*p)[-1]<<8;
-}
-
-static uint32_t
-r32(uint8_t **p)
-{
-	*p += 4;
-	return (*p)[-4]<<0 | (*p)[-3]<<8 | (*p)[-2]<<16 | (*p)[-1]<<24;
-}
-
-static uint64_t
-r64(uint8_t **p)
-{
-	uint64_t v;
-
-	v = r32(p);
-	v |= (uint64_t)r32(p)<<32;
-	return v;
-}
-
-#ifndef C9_NO_CLIENT
-
-static C9error
-newtag(C9ctx *c, C9ttype type, C9tag *tag)
-{
-	uint32_t i;
-
-	if(type == Tversion){
-		*tag = 0xffff;
-		return 0;
-	}
-
-	if(c->lowfreetag < C9maxtags){
-		uint32_t d = c->lowfreetag / C9tagsbits, m = c->lowfreetag % C9tagsbits;
-		if((c->tags[d] & 1<<m) != 0){
-			c->tags[d] &= ~(1<<m);
-			*tag = c->lowfreetag++;
-			return 0;
-		}
-	}
-
-	for(i = 0; i < (int)sizeof(c->tags)/sizeof(c->tags[0]); i++){
-		uint32_t x, j;
-		if((x = c->tags[i]) == 0)
-			continue;
-		for(j = 0; j < C9tagsbits; j++){
-			if((x & (1<<j)) != 0){
-				c->tags[i] &= ~(1<<j);
-				*tag = i*C9tagsbits + j;
-				c->lowfreetag = *tag + 1;
-				return 0;
-			}
-		}
-	}
-
-	c->error("newtag: no free tags");
-	return C9Etag;
-}
-
-static int
-freetag(C9ctx *c, C9tag tag)
-{
-	if(tag != 0xffff){
-		uint32_t d = tag / C9tagsbits, m = tag % C9tagsbits;
-		if(tag >= C9maxtags){
-			c->error("freetag: invalid tag %ud", (uint32_t)tag);
-			return -1;
-		}
-		if((c->tags[d] & 1<<m) != 0){
-			c->error("freetag: double free for tag %ud", (uint32_t)tag);
-			return -1;
-		}
-		if(c->lowfreetag > tag)
-			c->lowfreetag = tag;
-		c->tags[d] |= 1<<m;
-	}
-	return 0;
-}
-
-static uint8_t *
-T(C9ctx *c, uint32_t size, C9ttype type, C9tag *tag, C9error *err)
-{
-	uint8_t *p = NULL;
-
-	if(size > c->msize-4-1-2){
-		c->error("T: invalid size %ud", size);
-		*err = C9Esize;
-	}else if((*err = newtag(c, type, tag)) == 0){
-		size += 4+1+2;
-		if((p = c->begin(c, size)) == NULL){
-			c->error("T: no buffer for %ud bytes", size);
-			freetag(c, *tag);
-			*err = C9Ebuf;
-		}else{
-			*err = 0;
-			w32(&p, size);
-			w08(&p, type);
-			w16(&p, *tag);
-		}
-	}
-	return p;
-}
-
-C9error
-c9version(C9ctx *c, C9tag *tag, uint32_t msize)
-{
-	uint8_t *b;
-	C9error err;
-
-	if(msize < C9minmsize){
-		c->error("c9version: msize too small: %ud", msize);
-		return C9Einit;
-	}
-	memset(c->tags, 0xff, sizeof(c->tags));
-	memset(c->flush, 0xff, sizeof(c->flush));
-	c->lowfreetag = 0;
-	c->msize = msize;
-
-	if((b = T(c, 4+2+6, Tversion, tag, &err)) != NULL){
-		w32(&b, msize);
-		wcs(&b, "9P2000", 6);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9auth(C9ctx *c, C9tag *tag, C9fid afid, const char *uname, const char *aname)
-{
-	uint8_t *b;
-	uint32_t ulen = safestrlen(uname), alen = safestrlen(aname);
-	C9error err;
-
-	if(ulen > C9maxstr || alen > C9maxstr){
-		c->error("c9auth: string too long: %ud chars", ulen > alen ? ulen : alen);
-		return C9Estr;
-	}
-	if((b = T(c, 4+2+ulen+2+alen, Tauth, tag, &err)) != NULL){
-		w32(&b, afid);
-		wcs(&b, uname, ulen);
-		wcs(&b, aname, alen);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9flush(C9ctx *c, C9tag *tag, C9tag oldtag)
-{
-	uint8_t *b;
-	C9error err;
-	int i;
-
-	for(i = 0; i < C9maxflush && c->flush[i] != (uint32_t)~0; i++);
-	if(i == C9maxflush){
-		c->error("c9flush: no free flush slots");
-		return C9Eflush;
-	}
-	if((b = T(c, 2, Tflush, tag, &err)) != NULL){
-		w16(&b, oldtag);
-		err = c->end(c);
-		if(err == 0)
-			c->flush[i] = (uint32_t)oldtag<<16 | *tag;
-	}
-	return err;
-}
-
-C9error
-c9attach(C9ctx *c, C9tag *tag, C9fid fid, C9fid afid, const char *uname, const char *aname)
-{
-	uint32_t ulen = safestrlen(uname), alen = safestrlen(aname);
-	uint8_t *b;
-	C9error err;
-
-	if(ulen > C9maxstr || alen > C9maxstr){
-		c->error("c9attach: string too long: %ud chars", ulen > alen ? ulen : alen);
-		return C9Estr;
-	}
-	if((b = T(c, 4+4+2+ulen+2+alen, Tattach, tag, &err)) != NULL){
-		w32(&b, fid);
-		w32(&b, afid);
-		wcs(&b, uname, ulen);
-		wcs(&b, aname, alen);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9walk(C9ctx *c, C9tag *tag, C9fid fid, C9fid newfid, const char *path[])
-{
-	uint32_t i, j, sz;
-	uint32_t len[C9maxpathel];
-	uint8_t *b;
-	C9error err;
-
-	for(sz = i = 0; i < (int)sizeof(len)/sizeof(len[0]) && path[i] != NULL; i++){
-		len[i] = safestrlen(path[i]);
-		if(len[i] == 0 || len[i] > C9maxstr){
-			c->error("c9walk: invalid path element: %ud chars", len[i]);
-			return C9Epath;
-		}
-		sz += 2 + len[i];
-	}
-	if(path[i] != NULL){
-		c->error("c9walk: invalid elements !(0 <= %ud <= %ud)", i, C9maxpathel);
-		return C9Epath;
-	}
-
-	if((b = T(c, 4+4+2+sz, Twalk, tag, &err)) != NULL){
-		w32(&b, fid);
-		w32(&b, newfid);
-		w16(&b, i);
-		for(j = 0; j < i; j++)
-			wcs(&b, path[j], len[j]);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9open(C9ctx *c, C9tag *tag, C9fid fid, C9mode mode)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = T(c, 4+1, Topen, tag, &err)) != NULL){
-		w32(&b, fid);
-		w08(&b, mode);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9create(C9ctx *c, C9tag *tag, C9fid fid, const char *name, uint32_t perm, C9mode mode)
-{
-	uint32_t nlen = strlen(name);
-	uint8_t *b;
-	C9error err;
-
-	if(nlen == 0 || nlen > C9maxstr){
-		c->error("c9create: invalid name: %ud chars", nlen);
-		return C9Epath;
-	}
-	if((b = T(c, 4+2+nlen+4+1, Tcreate, tag, &err)) != NULL){
-		w32(&b, fid);
-		wcs(&b, name, nlen);
-		w32(&b, perm);
-		w08(&b, mode);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9read(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, uint32_t count)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = T(c, 4+8+4, Tread, tag, &err)) != NULL){
-		w32(&b, fid);
-		w64(&b, offset);
-		w32(&b, count);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9write(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, const void *in, uint32_t count)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = T(c, 4+8+4+count, Twrite, tag, &err)) != NULL){
-		w32(&b, fid);
-		w64(&b, offset);
-		w32(&b, count);
-		memmove(b, in, count);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9wrstr(C9ctx *c, C9tag *tag, C9fid fid, const char *s)
-{
-	return c9write(c, tag, fid, 0, s, strlen(s));
-}
-
-C9error
-c9clunk(C9ctx *c, C9tag *tag, C9fid fid)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = T(c, 4, Tclunk, tag, &err)) != NULL){
-		w32(&b, fid);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9remove(C9ctx *c, C9tag *tag, C9fid fid)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = T(c, 4, Tremove, tag, &err)) != NULL){
-		w32(&b, fid);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9stat(C9ctx *c, C9tag *tag, C9fid fid)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = T(c, 4, Tstat, tag, &err)) != NULL){
-		w32(&b, fid);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9wstat(C9ctx *c, C9tag *tag, C9fid fid, const C9stat *s)
-{
-	uint32_t nlen = safestrlen(s->name), ulen = safestrlen(s->uid), glen = safestrlen(s->gid);
-	uint32_t unusedsz = 2+4+13, statsz = unusedsz+4+4+4+8+2+nlen+2+ulen+2+glen+2;
-	uint8_t *b;
-	C9error err;
-
-	if(nlen == 0 || nlen > C9maxstr){
-		c->error("c9wstat: invalid name: %ud chars", nlen);
-		return C9Epath;
-	}
-	if(ulen > C9maxstr || glen > C9maxstr){
-		c->error("c9wstat: string too long: %ud chars", ulen > glen ? ulen : glen);
-		return C9Estr;
-	}
-	if((b = T(c, 4+2+2+statsz, Twstat, tag, &err)) != NULL){
-		w32(&b, fid);
-		w16(&b, statsz+2);
-		w16(&b, statsz);
-		memset(b, 0xff, unusedsz); /* leave type(2), dev(4) and qid(13) unchanged */
-		b += unusedsz;
-		w32(&b, s->mode);
-		w32(&b, s->atime);
-		w32(&b, s->mtime);
-		w64(&b, s->size);
-		wcs(&b, s->name, nlen);
-		wcs(&b, s->uid, ulen);
-		wcs(&b, s->gid, glen);
-		wcs(&b, NULL, 0); /* muid unchanged */
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-c9proc(C9ctx *c)
-{
-	uint32_t i, sz, cnt, msize;
-	uint8_t *b;
-	int err;
-	C9r r;
-
-	err = -1;
-	if((b = c->read(c, 4, &err)) == NULL){
-		if(err != 0)
-			c->error("c9proc: short read");
-		return err == 0 ? 0 : C9Epkt;
-	}
-
-	sz = r32(&b);
-	if(sz < 7 || sz > c->msize){
-		c->error("c9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
-		return C9Epkt;
-	}
-	sz -= 4;
-	err = -1;
-	if((b = c->read(c, sz, &err)) == NULL){
-		if(err != 0)
-			c->error("c9proc: short read");
-		return err == 0 ? 0 : C9Epkt;
-	}
-
-	r.type = r08(&b);
-	r.tag = r16(&b);
-	if(r.type != Rversion){
-		if(r.tag >= C9maxtags){
-			c->error("c9proc: invalid tag %ud", (uint32_t)r.tag);
-			return C9Epkt;
-		}
-		if(freetag(c, r.tag) != 0)
-			return C9Etag;
-	}
-	sz -= 3;
-	r.numqid = 0;
-
-	switch(r.type){
-	case Rread:
-		if(sz < 4 || (cnt = r32(&b)) > sz-4)
-			goto error;
-		r.read.data = b;
-		r.read.size = cnt;
-		c->r(c, &r);
-		break;
-
-	case Rwrite:
-		if(sz < 4 || (cnt = r32(&b)) > c->msize)
-			goto error;
-		r.write.size = cnt;
-		c->r(c, &r);
-		break;
-
-	case Rwalk:
-		if(sz < 2 || (cnt = r16(&b))*13 > sz-2)
-			goto error;
-		if(cnt > C9maxpathel){
-			c->error("c9proc: Rwalk !(%ud <= %ud)", cnt, C9maxpathel);
-			return C9Epath;
-		}
-		for(i = 0; i < cnt; i++){
-			r.qid[i].type = r08(&b);
-			r.qid[i].version = r32(&b);
-			r.qid[i].path = r64(&b);
-		}
-		r.numqid = cnt;
-		c->r(c, &r);
-		break;
-
-	case Rstat:
-		b += 2; sz -= 2;
-		if((err = c9parsedir(c, &r.stat, &b, &sz)) != 0)
-			return err;
-		r.numqid = 1;
-		c->r(c, &r);
-		break;
-
-	case Rflush:
-		for(i = 0; i < C9maxflush; i++){
-			if((c->flush[i] & 0xffff) == r.tag){
-				freetag(c, c->flush[i]>>16);
-				c->flush[i] = 0xffffffff;
-				break;
-			}
-		}
-		/* fallthrough */
-	case Rclunk:
-	case Rremove:
-	case Rwstat:
-		c->r(c, &r);
-		break;
-
-	case Ropen:
-	case Rcreate:
-		if(sz < 17)
-			goto error;
-		r.qid[0].type = r08(&b);
-		r.qid[0].version = r32(&b);
-		r.qid[0].path = r64(&b);
-		r.iounit = r32(&b);
-		r.numqid = 1;
-		c->r(c, &r);
-		break;
-
-	case Rerror:
-		if(sz < 2 || (cnt = r16(&b)) > sz-2)
-			goto error;
-		r.error = memmove(b-1, b, cnt);
-		r.error[cnt] = 0;
-		c->r(c, &r);
-		break;
-
-	case Rauth:
-	case Rattach:
-		if(sz < 13)
-			goto error;
-		r.qid[0].type = r08(&b);
-		r.qid[0].version = r32(&b);
-		r.qid[0].path = r64(&b);
-		r.numqid = 1;
-		c->r(c, &r);
-		break;
-
-	case Rversion:
-		if(sz < 4+2 || (msize = r32(&b)) < C9minmsize || (cnt = r16(&b)) > sz-4-2)
-			goto error;
-		if(cnt < 6 || memcmp(b, "9P2000", 6) != 0){
-			c->error("invalid version");
-			return C9Ever;
-		}
-		if(msize < c->msize)
-			c->msize = msize;
-		c->r(c, &r);
-		break;
-
-	default:
-		goto error;
-	}
-	return 0;
-error:
-	c->error("c9proc: invalid packet type %ud", r.type);
-	return C9Epkt;
-}
-
-#endif /* C9_NO_CLIENT */
-
-C9error
-c9parsedir(C9ctx *c, C9stat *stat, uint8_t **t, uint32_t *size)
-{
-	uint8_t *b;
-	uint32_t cnt, sz;
-
-	sz = 0;
-	if(*size < 49 || (sz = r16(t)) < 47 || *size < 2+sz)
-		goto error;
-	*size -= 2+sz;
-	*t += 6; /* skip type(2) and dev(4) */
-	stat->qid.type = r08(t);
-	stat->qid.version = r32(t);
-	stat->qid.path = r64(t);
-	stat->mode = r32(t);
-	stat->atime = r32(t);
-	stat->mtime = r32(t);
-	stat->size = r64(t);
-	sz -= 39;
-	if((cnt = r16(t)) > sz-2)
-		goto error;
-	stat->name = (char*)*t; b = *t = *t+cnt; sz -= 2+cnt;
-	if(sz < 2 || (cnt = r16(t)) > sz-2)
-		goto error;
-	stat->uid = (char*)*t; *b = 0; b = *t = *t+cnt; sz -= 2+cnt;
-	if(sz < 2 || (cnt = r16(t)) > sz-2)
-		goto error;
-	stat->gid = (char*)*t; *b = 0; b = *t = *t+cnt; sz -= 2+cnt;
-	if(sz < 2 || (cnt = r16(t)) > sz-2)
-		goto error;
-	stat->muid = memmove(*t-1, *t, cnt); *b = stat->muid[cnt] = 0; *t = *t+cnt; sz -= 2+cnt;
-	*t += sz;
-	return 0;
-error:
-	c->error("c9parsedir: invalid size: size=%ud sz=%ud", *size, sz);
-	return C9Epkt;
-}
-
-#ifndef C9_NO_SERVER
-
-static uint8_t *
-R(C9ctx *c, uint32_t size, C9rtype type, C9tag tag, C9error *err)
-{
-	uint8_t *p = NULL;
-
-	if(size > c->msize-4-1-2){
-		c->error("R: invalid size %ud", size);
-		*err = C9Esize;
-	}else{
-		size += 4+1+2;
-		if((p = c->begin(c, size)) == NULL){
-			c->error("R: no buffer for %ud bytes", size);
-			*err = C9Ebuf;
-		}else{
-			*err = 0;
-			w32(&p, size);
-			w08(&p, type);
-			w16(&p, tag);
-		}
-	}
-	return p;
-}
-
-C9error
-s9version(C9ctx *c)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 4+2+6, Rversion, 0xffff, &err)) != NULL){
-		w32(&b, c->msize);
-		wcs(&b, "9P2000", 6);
-		err = c->end(c);
-	};
-	return err;
-}
-
-C9error
-s9auth(C9ctx *c, C9tag tag, const C9qid *aqid)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 13, Rauth, tag, &err)) != NULL){
-		w08(&b, aqid->type);
-		w32(&b, aqid->version);
-		w64(&b, aqid->path);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9error(C9ctx *c, C9tag tag, const char *ename)
-{
-	uint32_t len = safestrlen(ename);
-	uint8_t *b;
-	C9error err;
-
-	if(len > C9maxstr){
-		c->error("s9error: invalid ename: %ud chars", len);
-		return C9Estr;
-	}
-	if((b = R(c, 2+len, Rerror, tag, &err)) != NULL){
-		wcs(&b, ename, len);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9attach(C9ctx *c, C9tag tag, const C9qid *qid)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 13, Rattach, tag, &err)) != NULL){
-		w08(&b, qid->type);
-		w32(&b, qid->version);
-		w64(&b, qid->path);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9flush(C9ctx *c, C9tag tag)
-{
-	C9error err;
-
-	if(R(c, 0, Rflush, tag, &err) != NULL)
-		err = c->end(c);
-	return err;
-}
-
-C9error
-s9walk(C9ctx *c, C9tag tag, C9qid *qids[])
-{
-	uint32_t i, n;
-	uint8_t *b;
-	C9error err;
-
-	for(n = 0; n < C9maxpathel && qids[n] != NULL; n++);
-	if(n > C9maxpathel){
-		c->error("s9walk: invalid elements !(0 <= %ud <= %ud)", n, C9maxpathel);
-		return C9Epath;
-	}
-
-	if((b = R(c, 2+n*13, Rwalk, tag, &err)) != NULL){
-		w16(&b, n);
-		for(i = 0; i < n; i++){
-			w08(&b, qids[i]->type);
-			w32(&b, qids[i]->version);
-			w64(&b, qids[i]->path);
-		}
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9open(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 13+4, Ropen, tag, &err)) != NULL){
-		w08(&b, qid->type);
-		w32(&b, qid->version);
-		w64(&b, qid->path);
-		w32(&b, iounit);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9create(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 13+4, Rcreate, tag, &err)) != NULL){
-		w08(&b, qid->type);
-		w32(&b, qid->version);
-		w64(&b, qid->path);
-		w32(&b, iounit);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9read(C9ctx *c, C9tag tag, const void *data, uint32_t size)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 4+size, Rread, tag, &err)) != NULL){
-		w32(&b, size);
-		memmove(b, data, size);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9write(C9ctx *c, C9tag tag, uint32_t size)
-{
-	uint8_t *b;
-	C9error err;
-
-	if((b = R(c, 4, Rwrite, tag, &err)) != NULL){
-		w32(&b, size);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9readdir(C9ctx *c, C9tag tag, C9stat *st[], int *num, uint64_t *offset, uint32_t size)
-{
-	uint8_t *b;
-	const C9stat *s;
-	uint32_t nlen, ulen, glen, mulen, m, n;
-	C9error err;
-	int i;
-
-	if(size > c->msize-4-1-2)
-		size = c->msize-4-1-2;
-
-	m = 0;
-	for(i = 0; i < *num; i++){
-		s = st[i];
-		nlen = safestrlen(s->name);
-		ulen = safestrlen(s->uid);
-		glen = safestrlen(s->gid);
-		mulen = safestrlen(s->muid);
-
-		if(nlen == 0 || nlen > C9maxstr){
-			c->error("s9readdir: invalid name: %ud chars", nlen);
-			return C9Epath;
-		}
-		if(ulen > C9maxstr || glen > C9maxstr || mulen > C9maxstr){
-			ulen = ulen > glen ? ulen : glen;
-			ulen = ulen > mulen ? ulen : mulen;
-			c->error("s9readdir: string too long: %ud chars", ulen);
-			return C9Estr;
-		}
-
-		n = 2 + 2+4+13+4+4+4+8+2+nlen+2+ulen+2+glen+2+mulen;
-		if(4+m+n > size)
-			break;
-		m += n;
-	}
-
-	if((b = R(c, 4+m, Rread, tag, &err)) != NULL){
-		*num = i;
-		w32(&b, m);
-		for(i = 0; i < *num; i++){
-			s = st[i];
-			nlen = safestrlen(s->name);
-			ulen = safestrlen(s->uid);
-			glen = safestrlen(s->gid);
-			mulen = safestrlen(s->muid);
-			w16(&b, 2+4+13+4+4+4+8+2+nlen+2+ulen+2+glen+2+mulen);
-			w16(&b, 0xffff); /* type */
-			w32(&b, 0xffffffff); /* dev */
-			w08(&b, s->qid.type);
-			w32(&b, s->qid.version);
-			w64(&b, s->qid.path);
-			w32(&b, s->mode);
-			w32(&b, s->atime);
-			w32(&b, s->mtime);
-			w64(&b, s->size);
-			wcs(&b, s->name, nlen);
-			wcs(&b, s->uid, ulen);
-			wcs(&b, s->gid, glen);
-			wcs(&b, s->muid, mulen);
-		}
-		err = c->end(c);
-		if(err == 0)
-			*offset += m;
-	}
-	return err;
-}
-
-C9error
-s9clunk(C9ctx *c, C9tag tag)
-{
-	C9error err;
-
-	if(R(c, 0, Rclunk, tag, &err) != NULL)
-		err = c->end(c);
-	return err;
-}
-
-C9error
-s9remove(C9ctx *c, C9tag tag)
-{
-	C9error err;
-
-	if(R(c, 0, Rremove, tag, &err) != NULL)
-		err = c->end(c);
-	return err;
-}
-
-C9error
-s9stat(C9ctx *c, C9tag tag, const C9stat *s)
-{
-	uint32_t nlen = safestrlen(s->name), ulen = safestrlen(s->uid);
-	uint32_t glen = safestrlen(s->gid), mulen = safestrlen(s->name);
-	uint32_t statsz = 2+4+13+4+4+4+8+2+nlen+2+ulen+2+glen+2+mulen;
-	uint8_t *b;
-	C9error err;
-
-	if(nlen == 0 || nlen > C9maxstr){
-		c->error("s9stat: invalid name: %ud chars", nlen);
-		return C9Epath;
-	}
-	if(ulen > C9maxstr || glen > C9maxstr || mulen > C9maxstr){
-		ulen = ulen > glen ? ulen : glen;
-		ulen = ulen > mulen ? ulen : mulen;
-		c->error("s9stat: string too long: %ud chars", ulen);
-		return C9Estr;
-	}
-
-	if((b = R(c, 2+2+statsz, Rstat, tag, &err)) != NULL){
-		w16(&b, statsz+2);
-		w16(&b, statsz);
-		w16(&b, 0xffff); /* type */
-		w32(&b, 0xffffffff); /* dev */
-		w08(&b, s->qid.type);
-		w32(&b, s->qid.version);
-		w64(&b, s->qid.path);
-		w32(&b, s->mode);
-		w32(&b, s->atime);
-		w32(&b, s->mtime);
-		w64(&b, s->size);
-		wcs(&b, s->name, nlen);
-		wcs(&b, s->uid, ulen);
-		wcs(&b, s->gid, glen);
-		wcs(&b, s->muid, mulen);
-		err = c->end(c);
-	}
-	return err;
-}
-
-C9error
-s9wstat(C9ctx *c, C9tag tag)
-{
-	C9error err;
-
-	if(R(c, 0, Rwstat, tag, &err) != NULL)
-		err = c->end(c);
-	return err;
-}
-
-C9error
-s9proc(C9ctx *c)
-{
-	uint32_t i, sz, cnt, n, msize;
-	int readerr;
-	uint8_t *b;
-	C9error err;
-	C9t t;
-
-	readerr = -1;
-	if((b = c->read(c, 4, &readerr)) == NULL){
-		if(readerr != 0)
-			c->error("s9proc: short read");
-		return readerr == 0 ? 0 : C9Epkt;
-	}
-
-	sz = r32(&b);
-	if(sz < 7 || sz > c->msize){
-		c->error("s9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
-		return C9Epkt;
-	}
-	sz -= 4;
-	readerr = -1;
-	if((b = c->read(c, sz, &readerr)) == NULL){
-		if(readerr != 0)
-			c->error("s9proc: short read");
-		return readerr == 0 ? 0 : C9Epkt;
-	}
-
-	t.type = r08(&b);
-	t.tag = r16(&b);
-	sz -= 3;
-
-	if((c->svflags & Svver) == 0 && t.type != Tversion){
-		c->error("s9proc: expected Tversion, got %ud", t.type);
-		return C9Epkt;
-	}
-
-	switch(t.type){
-	case Tread:
-		if(sz < 4+8+4)
-			goto error;
-		t.fid = r32(&b);
-		t.read.offset = r64(&b);
-		t.read.size = r32(&b);
-		if(t.read.size > maxread(c))
-		  t.read.size = maxread(c);
-		c->t(c, &t);
-		break;
-
-	case Twrite:
-		if(sz < 4+8+4)
-			goto error;
-		t.fid = r32(&b);
-		t.write.offset = r64(&b);
-		if((t.write.size = r32(&b)) < sz-4-8-4)
-			goto error;
-		if(t.write.size > maxwrite(c))
-		  t.write.size = maxwrite(c);
-		t.write.data = b;
-		c->t(c, &t);
-		break;
-
-	case Tclunk:
-	case Tstat:
-	case Tremove:
-		if(sz < 4)
-			goto error;
-		t.fid = r32(&b);
-		c->t(c, &t);
-		break;
-
-	case Twalk:
-		if(sz < 4+4+2)
-			goto error;
-		t.fid = r32(&b);
-		t.walk.newfid = r32(&b);
-		if((n = r16(&b)) > 16){
-			c->error("s9proc: Twalk !(%ud <= 16)", n);
-			return C9Epath;
-		}
-		sz -= 4+4+2;
-		if(n > 0){
-			for(i = 0; i < n; i++){
-				if(sz < 2 || (cnt = r16(&b)) > sz-2)
-					goto error;
-				if(cnt < 1){
-					c->error("s9proc: Twalk invalid element [%ud]", i);
-					return C9Epath;
-				}
-				b[-2] = 0;
-				t.walk.wname[i] = (char*)b;
-				b += cnt;
-				sz -= 2 + cnt;
-			}
-			memmove(t.walk.wname[i-1]-1, t.walk.wname[i-1], (char*)b - t.walk.wname[i-1]);
-			t.walk.wname[i-1]--;
-			b[-1] = 0;
-		}else
-			i = 0;
-		t.walk.wname[i] = NULL;
-		c->t(c, &t);
-		break;
-
-	case Topen:
-		if(sz < 4+1)
-			goto error;
-		t.fid = r32(&b);
-		t.open.mode = r08(&b);
-		c->t(c, &t);
-		break;
-
-	case Twstat:
-		if(sz < 4+2)
-			goto error;
-		t.fid = r32(&b);
-		if((cnt = r16(&b)) > sz-4)
-			goto error;
-		if((err = c9parsedir(c, &t.wstat, &b, &cnt)) != 0){
-			c->error("s9proc");
-			return err;
-		}
-		c->t(c, &t);
-		break;
-
-	case Tcreate:
-		if(sz < 4+2+4+1)
-			goto error;
-		t.fid = r32(&b);
-		if((cnt = r16(&b)) < 1 || cnt > sz-4-2-4-1)
-			goto error;
-		t.create.name = (char*)b;
-		b += cnt;
-		t.create.perm = r32(&b);
-		t.create.mode = r08(&b);
-		t.create.name[cnt] = 0;
-		c->t(c, &t);
-		break;
-
-	case Tflush:
-		if(sz < 2)
-			goto error;
-		t.flush.oldtag = r16(&b);
-		c->t(c, &t);
-		break;
-
-	case Tversion:
-		if(sz < 4+2 || (msize = r32(&b)) < C9minmsize || (cnt = r16(&b)) > sz-4-2)
-			goto error;
-		if(cnt < 6 || memcmp(b, "9P2000", 6) != 0){
-			if((b = R(c, 4+2+7, Rversion, 0xffff, &err)) != NULL){
-				w32(&b, 0);
-				wcs(&b, "unknown", 7);
-				err = c->end(c);
-				c->error("s9proc: invalid version");
-			}
-			return C9Ever;
-		}
-		if(msize < c->msize)
-			c->msize = msize;
-		c->svflags |= Svver;
-		t.version.msize = msize;
-		c->t(c, &t);
-		break;
-
-	case Tattach:
-		if(sz < 4+4+2+2)
-			goto error;
-		t.fid = r32(&b);
-		t.attach.afid = r32(&b);
-		cnt = r16(&b);
-		sz -= 4+4+2;
-		if(cnt+2 > sz)
-			goto error;
-		t.attach.uname = (char*)b;
-		b += cnt;
-		cnt = r16(&b);
-		b[-2] = 0;
-		sz -= cnt+2;
-		if(cnt > sz)
-			goto error;
-		memmove(b-1, b, cnt);
-		t.attach.aname = (char*)b-1;
-		t.attach.aname[cnt] = 0;
-		c->t(c, &t);
-		break;
-
-	case Tauth:
-		if(sz < 4+2+2)
-			goto error;
-		t.auth.afid = r32(&b);
-		cnt = r16(&b);
-		sz -= 4+2;
-		if(cnt+2 > sz)
-			goto error;
-		t.auth.uname = (char*)b;
-		b += cnt;
-		cnt = r16(&b);
-		b[-2] = 0;
-		sz -= cnt+2;
-		if(cnt > sz)
-			goto error;
-		memmove(b-1, b, cnt);
-		t.auth.aname = (char*)b-1;
-		t.auth.aname[cnt] = 0;
-		c->t(c, &t);
-		break;
-
-	default:
-		goto error;
-	}
-	return 0;
-error:
-	c->error("s9proc: invalid packet (type=%ud)", t.type);
-	return C9Epkt;
-}
-
-#endif /* C9_NO_SERVER */
--- a/c9/c9.h
+++ /dev/null
@@ -1,370 +1,0 @@
-#ifdef __plan9__
-typedef u64int uint64_t;
-typedef u32int uint32_t;
-typedef u16int uint16_t;
-typedef u8int uint8_t;
-#define __attribute__(...)
-#define NULL nil
-#endif
-
-struct C9aux;
-
-typedef struct C9r C9r;
-typedef struct C9t C9t;
-typedef struct C9stat C9stat;
-typedef struct C9ctx C9ctx;
-typedef struct C9qid C9qid;
-typedef uint32_t C9fid;
-typedef uint16_t C9tag;
-
-/* Stat field is not changed if it's set to this value when calling c9wstat. */
-#define C9nochange (~0)
-
-/* Special fid used with auth/attach to basically avoid authentication. */
-#define C9nofid ((C9fid)~0)
-
-/* C9modes for opening a file. */
-typedef enum
-{
-	C9read = 0,
-	C9write = 1,
-	C9rdwr = 2,
-	C9exec = 3,
-	C9trunc = 0x10,
-	C9rclose = 0x40,
-	C9excl = 0x1000,
-}C9mode;
-
-typedef enum
-{
-	/* User/owner. */
-	C9permur = 1<<8, /* Readable. */
-	C9permuw = 1<<7, /* Writable. */
-	C9permux = 1<<6, /* Executable. */
-
-	/* Group. */
-	C9permgr = 1<<5,
-	C9permgw = 1<<4,
-	C9permgx = 1<<3,
-
-	/* Other. */
-	C9permor = 1<<2,
-	C9permow = 1<<1,
-	C9permox = 1<<0,
-}C9perm;
-
-/* Directory. */
-#define C9permdir 0x80000000
-
-/* Bitmask of stat.mode. */
-#define C9stdir 0x80000000
-#define C9stappend 0x40000000
-#define C9stexcl 0x20000000
-#define C9sttmp 0x04000000
-
-/* Limits. */
-enum
-{
-	C9maxtags = 64,    /* Maximal number of outstanding requests. [1-65535] */
-	C9maxflush = 8,    /* Maximal number of outstanding flushes. [1-65535] */
-	C9maxstr = 0xffff, /* Maximal string length. [1-65535] */
-	C9minmsize = 4096, /* Minimal sane msize. [4096-...] */
-	C9maxpathel = 16,  /* Maximal number of elements in a path. Do not change. */
-};
-
-/* Errors. */
-typedef enum
-{
-	C9Einit = -1,  /* Initialization failed. */
-	C9Ever = -2,   /* Protocol version doesn't match. */
-	C9Epkt = -3,   /* Incoming packet error. */
-	C9Etag = -4,   /* No free tags or bad tag. */
-	C9Ebuf = -5,   /* No buffer space enough for a message. */
-	C9Epath = -6,  /* Path is too long or just invalid. */
-	C9Eflush = -7, /* Limit of outstanding flushes reached. */
-	C9Esize = -8,  /* Can't fit data in one message. */
-	C9Estr = -9    /* Bad string. */
-}C9error;
-
-/* Request types. */
-typedef enum
-{
-	Tversion = 100,
-	Tauth = 102,
-	Tattach = 104,
-	Tflush = 108,
-	Twalk = 110,
-	Topen = 112,
-	Tcreate = 114,
-	Tread = 116,
-	Twrite = 118,
-	Tclunk = 120,
-	Tremove = 122,
-	Tstat = 124,
-	Twstat = 126
-}C9ttype;
-
-/* Response types. */
-typedef enum
-{
-	Rversion = 101,
-	Rauth = 103,
-	Rattach = 105,
-	Rerror = 107,
-	Rflush = 109,
-	Rwalk = 111,
-	Ropen = 113,
-	Rcreate = 115,
-	Rread = 117,
-	Rwrite = 119,
-	Rclunk = 121,
-	Rremove = 123,
-	Rstat = 125,
-	Rwstat = 127
-}C9rtype;
-
-/* Unique file id type. */
-typedef enum
-{
-	C9qtdir = 1<<7,
-	C9qtappend = 1<<6,
-	C9qtexcl = 1<<5,
-	C9qtauth = 1<<3,
-	C9qttmp = 1<<2,
-	C9qtfile = 0
-}C9qt;
-
-/* Unique file id. */
-struct C9qid
-{
-	uint64_t path;
-	uint32_t version;
-	C9qt type;
-};
-
-/*
- * File stats. Version and muid are ignored on wstat. Dmdir bit
- * change in mode won't work on wstat. Set any integer field to
- * C9nochange to keep it unchanged on wstat. Set any string to NULL to
- * keep it unchanged. Strings can be empty (""), but never NULL after
- * stat call.
- */
-struct C9stat
-{
-	C9qid qid;   /* Same as qid[0]. */
-	uint64_t size; /* Size of the file (in bytes). */
-	char *name;  /* Name of the file. */
-	char *uid;   /* Owner of the file. */
-	char *gid;   /* Group of the file. */
-	char *muid;  /* The user who modified the file last. */
-	uint32_t mode;   /* Permissions. See C9st* and C9perm. */
-	uint32_t atime;  /* Last access time. */
-	uint32_t mtime;  /* Last modification time. */
-};
-
-/* Response data. */
-struct C9r
-{
-	union
-	{
-		char *error;
-
-		struct
-		{
-			uint8_t *data;
-			uint32_t size;
-		}read;
-
-		struct
-		{
-			uint32_t size;
-		}write;
-
-		/* File stats (only valid if type is Rstat). */
-		C9stat stat;
-
-		/*
-		 * Qid(s). qid[0] is valid for auth/attach/create/stat/open.
-		 * More ids may be a result of a walk, see numqid.
-		 */
-		C9qid qid[C9maxpathel];
-	};
-	C9rtype type; /* Response type. */
-
-	/*
-	 * If not zero, is the maximum number of bytes that are guaranteed
-	 * to be read or written atomically, without breaking into multiple
-	 * messages.
-	 */
-	uint32_t iounit;
-
-	int numqid; /* Number of valid unique ids in qid array. */
-	C9tag tag;  /* Tag number. */
-};
-
-/* Request data. */
-struct C9t
-{
-	C9ttype type;
-	C9tag tag;
-	union
-	{
-		struct
-		{
-			int msize;
-		}version;
-
-		struct
-		{
-			char *uname;
-			char *aname;
-			C9fid afid;
-		}attach;
-
-		struct
-		{
-			char *uname;
-			char *aname;
-			C9fid afid;
-		}auth;
-
-		struct
-		{
-			char *name;
-			uint32_t perm;
-			C9mode mode;
-		}create;
-
-		struct
-		{
-			C9tag oldtag;
-		}flush;
-
-		struct
-		{
-			C9mode mode;
-		}open;
-
-		struct
-		{
-			uint64_t offset;
-			uint32_t size;
-		}read;
-
-		struct
-		{
-			char *wname[C9maxpathel+1]; /* wname[16] is always NULL */
-			C9fid newfid;
-		}walk;
-
-		struct
-		{
-			uint64_t offset;
-			uint8_t *data;
-			uint32_t size;
-		}write;
-
-		C9stat wstat;
-	};
-	C9fid fid;
-};
-
-enum
-{
-	C9tagsbits = sizeof(uint32_t) * 8,
-};
-
-struct C9ctx
-{
-	/*
-	 * Should return a pointer to the data (exactly 'size' bytes) read.
-	 * Set 'err' to non-zero and return NULL in case of error.
-	 * 'err' set to zero (no error) should be used to return from c9process
-	 * early (timeout on read to do non-blocking operations, for example).
-	 */
-	uint8_t *(*read)(C9ctx *ctx, uint32_t size, int *err) __attribute__((nonnull(1, 3)));
-
-	/* Should return a buffer to store 'size' bytes. Nil means no memory. */
-	uint8_t *(*begin)(C9ctx *ctx, uint32_t size) __attribute__((nonnull(1)));
-
-	/*
-	 * Marks the end of a message. Callback may decide if any accumulated
-	 * messages should be sent to the server/client.
-	 */
-	int (*end)(C9ctx *ctx) __attribute__((nonnull(1)));
-
-	/* Callback called every time a new R-message is received. */
-	void (*r)(C9ctx *ctx, C9r *r) __attribute__((nonnull(1, 2)));
-
-	/* Callback called every time a new T-message is received. */
-	void (*t)(C9ctx *ctx, C9t *t) __attribute__((nonnull(1, 2)));
-
-	/* Callback for error messages. */
-	void (*error)(const char *fmt, ...) __attribute__((nonnull(1), format(printf, 1, 2)));
-
-	/* Auxiliary data, can be used by any of above callbacks. */
-	struct C9aux *aux;
-
-	/* private stuff */
-	uint32_t msize;
-#ifndef C9_NO_CLIENT
-	uint32_t flush[C9maxflush];
-	uint32_t tags[C9maxtags/C9tagsbits];
-#endif
-	union
-	{
-		C9tag lowfreetag;
-		uint16_t svflags;
-	};
-};
-
-/* Parse one directory entry. */
-extern C9error c9parsedir(C9ctx *c, C9stat *stat, uint8_t **data, uint32_t *size) __attribute__((nonnull(1, 2, 3)));
-
-#ifndef C9_NO_CLIENT
-
-extern C9error c9version(C9ctx *c, C9tag *tag, uint32_t msize) __attribute__((nonnull(1, 2)));
-extern C9error c9auth(C9ctx *c, C9tag *tag, C9fid afid, const char *uname, const char *aname) __attribute__((nonnull(1, 2)));
-extern C9error c9flush(C9ctx *c, C9tag *tag, C9tag oldtag) __attribute__((nonnull(1, 2)));
-extern C9error c9attach(C9ctx *c, C9tag *tag, C9fid fid, C9fid afid, const char *uname, const char *aname) __attribute__((nonnull(1, 2)));
-extern C9error c9walk(C9ctx *c, C9tag *tag, C9fid fid, C9fid newfid, const char *path[]) __attribute__((nonnull(1, 2, 5)));
-extern C9error c9open(C9ctx *c, C9tag *tag, C9fid fid, C9mode mode) __attribute__((nonnull(1, 2)));
-extern C9error c9create(C9ctx *c, C9tag *tag, C9fid fid, const char *name, uint32_t perm, C9mode mode) __attribute__((nonnull(1, 2, 4)));
-extern C9error c9read(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, uint32_t count) __attribute__((nonnull(1, 2)));
-extern C9error c9write(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, const void *in, uint32_t count) __attribute__((nonnull(1, 2, 5)));
-extern C9error c9wrstr(C9ctx *c, C9tag *tag, C9fid fid, const char *s) __attribute__((nonnull(1, 2, 4)));
-extern C9error c9clunk(C9ctx *c, C9tag *tag, C9fid fid) __attribute__((nonnull(1, 2)));
-extern C9error c9remove(C9ctx *c, C9tag *tag, C9fid fid) __attribute__((nonnull(1, 2)));
-extern C9error c9stat(C9ctx *c, C9tag *tag, C9fid fid) __attribute__((nonnull(1, 2)));
-extern C9error c9wstat(C9ctx *c, C9tag *tag, C9fid fid, const C9stat *s) __attribute__((nonnull(1, 2, 4)));
-
-/*
- * Wait until a response comes and process it. If the function returns
- * any error, context must be treated as 'broken' and no subsequent calls
- * should be made without reinitialization (c9version).
- */
-extern C9error c9proc(C9ctx *c) __attribute__((nonnull(1)));
-
-#endif /* C9_NO_CLIENT */
-
-#ifndef C9_NO_SERVER
-
-extern C9error s9version(C9ctx *c) __attribute__((nonnull(1)));
-extern C9error s9auth(C9ctx *c, C9tag tag, const C9qid *aqid) __attribute__((nonnull(1, 3)));
-extern C9error s9error(C9ctx *c, C9tag tag, const char *err) __attribute__((nonnull(1)));
-extern C9error s9attach(C9ctx *c, C9tag tag, const C9qid *qid) __attribute__((nonnull(1, 3)));
-extern C9error s9flush(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
-extern C9error s9walk(C9ctx *c, C9tag tag, C9qid *qids[]) __attribute__((nonnull(1, 3)));
-extern C9error s9open(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit) __attribute__((nonnull(1, 3)));
-extern C9error s9create(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit) __attribute__((nonnull(1, 3)));
-extern C9error s9read(C9ctx *c, C9tag tag, const void *data, uint32_t size) __attribute__((nonnull(1, 3)));
-extern C9error s9readdir(C9ctx *c, C9tag tag, C9stat *st[], int *num, uint64_t *offset, uint32_t size) __attribute__((nonnull(1, 3, 4)));
-extern C9error s9write(C9ctx *c, C9tag tag, uint32_t size) __attribute__((nonnull(1)));
-extern C9error s9clunk(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
-extern C9error s9remove(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
-extern C9error s9stat(C9ctx *c, C9tag tag, const C9stat *s) __attribute__((nonnull(1, 3)));
-extern C9error s9wstat(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
-
-extern C9error s9proc(C9ctx *c) __attribute__((nonnull(1)));
-
-#endif /* C9_NO_SERVER */
--- /dev/null
+++ b/c9/examples/dial.c
@@ -1,0 +1,50 @@
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+
+int
+dial(const char *s)
+{
+	struct addrinfo *r, *a, hint = {.ai_flags = AI_ADDRCONFIG, .ai_family = AF_UNSPEC, 0};
+	char host[128], *port;
+	int e, f;
+
+	if(strncmp(s, "udp!", 4) == 0){
+		hint.ai_socktype = SOCK_DGRAM;
+		hint.ai_protocol = IPPROTO_UDP;
+	}else if(strncmp(s, "tcp!", 4) == 0){
+		hint.ai_socktype = SOCK_STREAM;
+		hint.ai_protocol = IPPROTO_TCP;
+	}else{
+		fprintf(stderr, "invalid dial string: %s\n", s);
+		return -1;
+	}
+	if((port = strchr(s+4, '!')) == NULL){
+		fprintf(stderr, "invalid dial string: %s\n", s);
+		return -1;
+	}
+	if(snprintf(host, sizeof(host), "%.*s", (int)(port-s-4), s+4) >= (int)sizeof(host)){
+		fprintf(stderr, "host name too large: %s\n", s);
+		return -1;
+	}
+	port++;
+	if((e = getaddrinfo(host, port, &hint, &r)) != 0){
+		fprintf(stderr, "%s: %s\n", gai_strerror(e), s);
+		return -1;
+	}
+	f = -1;
+	for(a = r; a != NULL; a = a->ai_next){
+		if((f = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) < 0)
+			continue;
+		if(connect(f, a->ai_addr, a->ai_addrlen) == 0)
+			break;
+		close(f);
+		f = -1;
+	}
+	freeaddrinfo(r);
+
+	return f;
+}
--- /dev/null
+++ b/c9/examples/fs9ptest.c
@@ -1,0 +1,52 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include "c9/proto.h"
+#include "c9/fs.h"
+#include "c9/fs9p.h"
+
+int dial(const char *s);
+C9ctx *init9p(int f);
+
+static FS9pfd fds[8];
+
+static void
+fserror(FS *fs, const char *fmt, ...)
+{
+	va_list ap;
+
+	(void)fs;
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, "\n");
+	va_end(ap);
+}
+
+int
+main(int argc, char **argv)
+{
+	C9ctx *ctx;
+	FSstat st;
+	FS fs;
+	FS9p fs9 = {
+		.fds = fds,
+		.numfds = sizeof(fds)/sizeof(fds[0]),
+	};
+	int f;
+
+	(void)argc; (void)argv;
+
+	fs.error = fserror;
+	fs.aux = (void*)&fs9;
+	if((f = dial("tcp!ftrv.se!564")) < 0)
+		return 1;
+	ctx = init9p(f);
+	fs9.ctx = ctx;
+	if(fs9pinit(&fs, 8192) != 0)
+		return 1;
+	if(fs.fstat(&fs, FS9proot, &st) != 0)
+		return 1;
+	printf("%s %d %s %o\n", st.name, (int)st.size, st.uid, st.mode&0777);
+
+	return 0;
+}
--- /dev/null
+++ b/c9/examples/sockets.c
@@ -1,0 +1,127 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "c9/proto.h"
+
+enum {
+	Msize = 8192,
+
+	Disconnected = 1<<0,
+};
+
+typedef struct C9aux C9aux;
+
+struct C9aux {
+	C9ctx c;
+	int f;
+	int flags;
+	uint8_t rdbuf[Msize];
+	uint8_t wrbuf[Msize];
+	uint32_t wroff;
+};
+
+static uint8_t *
+ctxread(C9ctx *ctx, uint32_t size, int *err)
+{
+	uint32_t n;
+	int r;
+	C9aux *a;
+
+	a = ctx->aux;
+	*err = 0;
+	for(n = 0; n < size; n += r){
+		if((r = read(a->f, a->rdbuf+n, size-n)) <= 0){
+			if(errno == EINTR)
+				continue;
+			a->flags |= Disconnected;
+			close(a->f);
+			return NULL;
+		}
+	}
+
+	return a->rdbuf;
+}
+
+static int
+wrsend(C9aux *a)
+{
+	uint32_t n;
+	int w;
+
+	for(n = 0; n < a->wroff; n += w){
+		if((w = write(a->f, a->wrbuf+n, a->wroff-n)) <= 0){
+			if(errno == EINTR)
+				continue;
+			if(errno != EPIPE) /* remote end closed */
+				perror("write");
+			return -1;
+		}
+	}
+	a->wroff = 0;
+
+	return 0;
+}
+
+static uint8_t *
+ctxbegin(C9ctx *ctx, uint32_t size)
+{
+	uint8_t *b;
+	C9aux *a;
+
+	a = ctx->aux;
+	if(a->wroff + size > sizeof(a->wrbuf)){
+		if(wrsend(a) != 0 || a->wroff + size > sizeof(a->wrbuf))
+			return NULL;
+	}
+	b = a->wrbuf + a->wroff;
+	a->wroff += size;
+
+	return b;
+}
+
+static int
+ctxend(C9ctx *ctx)
+{
+	C9aux *a;
+
+	/*
+	 * To batch up requests and instead flush them all at once just return 0
+	 * here and call wrsend yourself when needed.
+	 */
+	a = ctx->aux;
+	return wrsend(a);
+}
+
+static void
+ctxerror(C9ctx *ctx, const char *fmt, ...)
+{
+	va_list ap;
+
+	(void)ctx;
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, "\n");
+	va_end(ap);
+}
+
+C9aux *
+init9p(int f)
+{
+	C9aux *c;
+
+	if((c = calloc(1, sizeof(*c))) == NULL){
+		close(f);
+		return NULL;
+	}
+	c->f = f;
+	c->c.read = ctxread;
+	c->c.begin = ctxbegin;
+	c->c.end = ctxend;
+	c->c.error = ctxerror;
+	c->c.aux = c;
+
+	return c;
+}
--- /dev/null
+++ b/c9/fs9p.c
@@ -1,0 +1,301 @@
+#include <stdint.h>
+#include <string.h>
+#include "c9/proto.h"
+#include "c9/fs.h"
+#include "c9/fs9p.h"
+
+enum
+{
+	Flinit = 1<<0,
+	Flerror = 1<<1,
+
+	F9init = 0,
+	F9chdir,
+	F9open,
+	F9create,
+	F9read,
+	F9readdir,
+	F9write,
+	F9seek,
+	F9stat,
+	F9fstat,
+	F9close,
+	F9remove,
+	F9rename,
+};
+
+static const FS9pfd freefd =
+{
+	.offset = ~0ULL,
+};
+
+#define isfree(fd) ((fd).offset == ~0ULL)
+
+static const char *f2s[] =
+{
+	[F9init] = "init",
+	[F9chdir] = "chdir",
+	[F9open] = "open",
+	[F9create] = "create",
+	[F9read] = "read",
+	[F9readdir] = "readdir",
+	[F9write] = "write",
+	[F9seek] = "seek",
+	[F9stat] = "stat",
+	[F9fstat] = "fstat",
+	[F9close] = "close",
+	[F9remove] = "remove",
+	[F9rename] = "rename",
+};
+
+static int
+fs9pchdir(FS *fs, const char *path)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9chdir;
+
+	return -1;
+}
+
+static int
+fs9popen(FS *fs, const char *name, FSmode mode)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9open;
+
+	return -1;
+}
+
+static int
+fs9pcreate(FS *fs, const char *path, int perm)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9create;
+
+	return -1;
+}
+
+static int64_t
+fs9pread(FS *fs, int fd, void *buf, uint64_t count)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9read;
+
+	return -1;
+}
+
+static int64_t
+fs9preaddir(FS *fs, int fd, FSstat *st, int nst)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9readdir;
+
+	return -1;
+}
+
+static int64_t
+fs9pwrite(FS *fs, int fd, const void *buf, uint64_t count)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9write;
+
+	return -1;
+}
+
+static int64_t
+fs9pseek(FS *fs, int fd, int64_t offset, int whence)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9seek;
+
+	return -1;
+}
+
+static int
+fs9pstat(FS *fs, const char *path, FSstat *st)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9stat;
+
+	return -1;
+}
+
+static int
+fs9pfstat(FS *fs, int fd, FSstat *st)
+{
+	FS9p *aux = (void*)fs->aux;
+	C9tag tag;
+	int e;
+
+	aux->f = F9fstat;
+	aux->p = st;
+	if((e = c9stat(aux->ctx, &tag, fd)) < 0)
+		return e;
+	while(aux->f != -F9fstat)
+		c9proc(aux->ctx);
+
+	return 0;
+}
+
+static int
+fs9pclose(FS *fs, int fd)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9close;
+
+	return -1;
+}
+
+static int
+fs9premove(FS *fs, const char *name)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9remove;
+
+	return -1;
+}
+
+static int
+fs9prename(FS *fs, const char *oldpath, const char *newpath)
+{
+	FS9p *aux = (void*)fs->aux;
+
+	aux->f = F9rename;
+
+	return -1;
+}
+
+static void
+fs9pr(C9ctx *ctx, C9r *r)
+{
+	FSstat *st;
+	C9tag tag;
+	FS9p *aux;
+	FS *fs;
+
+	fs = ctx->paux;
+	aux = (void*)fs->aux;
+
+	switch(r->type){
+	case Rversion:
+		c9attach(ctx, &tag, FS9proot, C9nofid, "none", NULL); /* FIXME those need to be configurable */
+		break;
+
+	case Rauth:
+		break;
+
+	case Rattach:
+		aux->flags |= Flinit;
+		break;
+
+	case Rerror:
+		fs->error(fs, "%s: %s", f2s[aux->f], r->error);
+		aux->flags |= Flerror;
+		break;
+
+	case Rflush:
+		break;
+
+	case Rwalk:
+		break;
+
+	case Ropen:
+		break;
+
+	case Rcreate:
+		break;
+
+	case Rread:
+		break;
+
+	case Rwrite:
+		break;
+
+	case Rclunk:
+		break;
+
+	case Rremove:
+		break;
+
+	case Rstat:
+		if(aux->f == F9stat || aux->f == F9fstat){
+			st = aux->p;
+			st->size = r->stat.size;
+			st->name = r->stat.name;
+			st->uid = r->stat.uid;
+			st->gid = r->stat.gid;
+			st->mode = r->stat.mode;
+			st->mtime = r->stat.mtime;
+			aux->f = -aux->f;
+		}
+		break;
+
+	case Rwstat:
+		break;
+	}
+}
+
+int
+fs9pinit(FS *fs, uint32_t msize)
+{
+	FS9p *aux = (void*)fs->aux;
+	C9error err;
+	C9tag tag;
+	int i;
+
+	if(fs->error == NULL)
+		return -1;
+	if(aux == NULL || aux->ctx == NULL || aux->fds == NULL || aux->numfds < 1){
+		fs->error(fs, "fs9pinit: invalid aux");
+		return -1;
+	}
+	if(aux->ctx == NULL){
+		fs->error(fs, "fs9pinit: invalid ctx");
+		return -1;
+	}
+
+	fs->chdir = fs9pchdir;
+	fs->open = fs9popen;
+	fs->create = fs9pcreate;
+	fs->read = fs9pread;
+	fs->readdir = fs9preaddir;
+	fs->write = fs9pwrite;
+	fs->seek = fs9pseek;
+	fs->stat = fs9pstat;
+	fs->fstat = fs9pfstat;
+	fs->close = fs9pclose;
+	fs->remove = fs9premove;
+	fs->rename = fs9prename;
+
+	aux->root.offset = 0;
+	for(i = 0; i < aux->numfds; i++)
+		aux->fds[i] = freefd;
+
+	aux->ctx->r = fs9pr;
+	aux->ctx->paux = fs;
+	aux->f = F9init;
+	aux->flags = 0;
+
+	if((err = c9version(aux->ctx, &tag, msize)) != 0)
+		return err;
+	for(;;){
+		if((err = c9proc(aux->ctx)) != 0)
+			return err;
+		if(aux->flags & Flerror)
+			return -1;
+		if(aux->flags & Flinit)
+			break;
+	}
+
+	return 0;
+}
--- /dev/null
+++ b/c9/include/c9/fs.h
@@ -1,0 +1,48 @@
+typedef struct FS FS;
+typedef struct FSstat FSstat;
+
+typedef enum
+{
+	FS_OREAD,
+	FS_OWRITE,
+	FS_ORDWR,
+	FS_OEXEC,
+	FS_OTRUNC = 0x10,
+	FS_ORCLOSE = 0x40,
+	FS_OEXCL = 0x1000,
+	FS_DIR = 0x80000000U, /* open/create: it's supposed to be a directory. */
+}FSmode;
+
+struct FSstat
+{
+	uint64_t size; /* Size of the file (in bytes). */
+	char *name;  /* Name of the file. */
+	char *uid;   /* Owner of the file. */
+	char *gid;   /* Group of the file. */
+	uint32_t mode;   /* Permissions. See C9st* and C9perm. */
+	uint64_t mtime;  /* Last modification time. Nanoseconds since epoch. */
+};
+
+struct FSaux;
+
+struct FS
+{
+	/* Callback for error messages. */
+	void (*error)(FS *fs, const char *fmt, ...) __attribute__((nonnull(1), format(printf, 2, 3)));
+
+	/* Do not set these. */
+	int (*chdir)(FS *fs, const char *path) __attribute__((nonnull(1, 2)));
+	int (*open)(FS *fs, const char *name, FSmode mode) __attribute__((nonnull(1, 2)));
+	int (*create)(FS *fs, const char *path, int perm) __attribute__((nonnull(1, 2))); 
+	int64_t (*read)(FS *fs, int fd, void *buf, uint64_t count) __attribute__((nonnull(1, 3)));
+	int64_t (*readdir)(FS *fs, int fd, FSstat *st, int nst);
+	int64_t (*write)(FS *fs, int fd, const void *buf, uint64_t count) __attribute__((nonnull(1, 3)));
+	int64_t (*seek)(FS *fs, int fd, int64_t offset, int whence) __attribute__((nonnull(1)));
+	int (*stat)(FS *fs, const char *path, FSstat *st) __attribute__((nonnull(1, 2, 3)));
+	int (*fstat)(FS *fs, int fd, FSstat *st) __attribute__((nonnull(1, 3)));
+	int (*close)(FS *fs, int fd) __attribute__((nonnull(1)));
+	int (*remove)(FS *fs, const char *name) __attribute__((nonnull(1, 2)));
+	int (*rename)(FS *fs, const char *oldpath, const char *newpath) __attribute__((nonnull(1, 2, 3)));
+
+	struct FSaux *aux;
+};
--- /dev/null
+++ b/c9/include/c9/fs9p.h
@@ -1,0 +1,31 @@
+enum
+{
+	FS9proot = 3, /* '/' file descriptor. */
+};
+
+typedef struct FS9p FS9p;
+typedef struct FS9pfd FS9pfd;
+
+struct FS9pfd
+{
+	uint64_t offset;
+};
+
+struct FS9p
+{
+	/* The following three fields need to be set before calling fs9pinit. */
+
+	C9ctx *ctx; /* Set to a full set up context, except "r" and "t" fields. */
+	FS9pfd *fds; /* Point at the allocated fds array. */
+	int numfds; /* Set to the number of entries available in fds array. */
+	void *aux; /* Optional, user-supplied aux value. */
+
+	/* Private, do not touch. */
+	FS9pfd root;
+	FS9pfd cwd;
+	int f;
+	void *p;
+	int flags;
+};
+
+extern int fs9pinit(FS *fs, uint32_t msize) __attribute__((nonnull(1)));
--- /dev/null
+++ b/c9/include/c9/proto.h
@@ -1,0 +1,366 @@
+struct C9aux;
+
+typedef struct C9r C9r;
+typedef struct C9t C9t;
+typedef struct C9stat C9stat;
+typedef struct C9ctx C9ctx;
+typedef struct C9qid C9qid;
+typedef uint32_t C9fid;
+typedef uint16_t C9tag;
+
+/* Stat field is not changed if it's set to this value when calling c9wstat. */
+#define C9nochange (~0)
+
+/* Special fid used with auth/attach to basically avoid authentication. */
+#define C9nofid ((C9fid)~0)
+
+/* C9modes for opening a file. */
+typedef enum
+{
+	C9read = 0,
+	C9write = 1,
+	C9rdwr = 2,
+	C9exec = 3,
+	C9trunc = 0x10,
+	C9rclose = 0x40,
+	C9excl = 0x1000,
+}C9mode;
+
+typedef enum
+{
+	/* User/owner. */
+	C9permur = 1<<8, /* Readable. */
+	C9permuw = 1<<7, /* Writable. */
+	C9permux = 1<<6, /* Executable. */
+
+	/* Group. */
+	C9permgr = 1<<5,
+	C9permgw = 1<<4,
+	C9permgx = 1<<3,
+
+	/* Other. */
+	C9permor = 1<<2,
+	C9permow = 1<<1,
+	C9permox = 1<<0,
+}C9perm;
+
+/* Directory. */
+#define C9permdir 0x80000000
+
+/* Bitmask of stat.mode. */
+#define C9stdir 0x80000000
+#define C9stappend 0x40000000
+#define C9stexcl 0x20000000
+#define C9sttmp 0x04000000
+
+/* Limits. */
+enum
+{
+	C9maxtags = 64,    /* Maximal number of outstanding requests. [1-65535] */
+	C9maxflush = 8,    /* Maximal number of outstanding flushes. [1-65535] */
+	C9maxstr = 0xffff, /* Maximal string length. [1-65535] */
+	C9minmsize = 4096, /* Minimal sane msize. [4096-...] */
+	C9maxpathel = 16,  /* Maximal number of elements in a path. Do not change. */
+};
+
+/* Errors. */
+typedef enum
+{
+	C9Einit = -1,  /* Initialization failed. */
+	C9Ever = -2,   /* Protocol version doesn't match. */
+	C9Epkt = -3,   /* Incoming packet error. */
+	C9Etag = -4,   /* No free tags or bad tag. */
+	C9Ebuf = -5,   /* No buffer space enough for a message. */
+	C9Epath = -6,  /* Path is too long or just invalid. */
+	C9Eflush = -7, /* Limit of outstanding flushes reached. */
+	C9Esize = -8,  /* Can't fit data in one message. */
+	C9Estr = -9    /* Bad string. */
+}C9error;
+
+/* Request types. */
+typedef enum
+{
+	Tversion = 100,
+	Tauth = 102,
+	Tattach = 104,
+	Tflush = 108,
+	Twalk = 110,
+	Topen = 112,
+	Tcreate = 114,
+	Tread = 116,
+	Twrite = 118,
+	Tclunk = 120,
+	Tremove = 122,
+	Tstat = 124,
+	Twstat = 126
+}C9ttype;
+
+/* Response types. */
+typedef enum
+{
+	Rversion = 101,
+	Rauth = 103,
+	Rattach = 105,
+	Rerror = 107,
+	Rflush = 109,
+	Rwalk = 111,
+	Ropen = 113,
+	Rcreate = 115,
+	Rread = 117,
+	Rwrite = 119,
+	Rclunk = 121,
+	Rremove = 123,
+	Rstat = 125,
+	Rwstat = 127
+}C9rtype;
+
+/* Unique file id type. */
+typedef enum
+{
+	C9qtdir = 1<<7,
+	C9qtappend = 1<<6,
+	C9qtexcl = 1<<5,
+	C9qtauth = 1<<3,
+	C9qttmp = 1<<2,
+	C9qtfile = 0
+}C9qt;
+
+/* Unique file id. */
+struct C9qid
+{
+	uint64_t path;
+	uint32_t version;
+	C9qt type;
+};
+
+/*
+ * File stats. Version and muid are ignored on wstat. Dmdir bit
+ * change in mode won't work on wstat. Set any integer field to
+ * C9nochange to keep it unchanged on wstat. Set any string to NULL to
+ * keep it unchanged. Strings can be empty (""), but never NULL after
+ * stat call.
+ */
+struct C9stat
+{
+	C9qid qid;   /* Same as qid[0]. */
+	uint64_t size; /* Size of the file (in bytes). */
+	char *name;  /* Name of the file. */
+	char *uid;   /* Owner of the file. */
+	char *gid;   /* Group of the file. */
+	char *muid;  /* The user who modified the file last. */
+	uint32_t mode;   /* Permissions. See C9st* and C9perm. */
+	uint32_t atime;  /* Last access time. */
+	uint32_t mtime;  /* Last modification time. */
+};
+
+/* Response data. */
+struct C9r
+{
+	union
+	{
+		char *error;
+
+		struct
+		{
+			uint8_t *data;
+			uint32_t size;
+		}read;
+
+		struct
+		{
+			uint32_t size;
+		}write;
+
+		/* File stats (only valid if type is Rstat). */
+		C9stat stat;
+
+		/*
+		 * Qid(s). qid[0] is valid for auth/attach/create/stat/open.
+		 * More ids may be a result of a walk, see numqid.
+		 */
+		C9qid qid[C9maxpathel];
+	};
+	C9rtype type; /* Response type. */
+
+	/*
+	 * If not zero, is the maximum number of bytes that are guaranteed
+	 * to be read or written atomically, without breaking into multiple
+	 * messages.
+	 */
+	uint32_t iounit;
+
+	int numqid; /* Number of valid unique ids in qid array. */
+	C9tag tag;  /* Tag number. */
+};
+
+/* Request data. */
+struct C9t
+{
+	C9ttype type;
+	C9tag tag;
+	union
+	{
+		struct
+		{
+			int msize;
+		}version;
+
+		struct
+		{
+			char *uname;
+			char *aname;
+			C9fid afid;
+		}attach;
+
+		struct
+		{
+			char *uname;
+			char *aname;
+			C9fid afid;
+		}auth;
+
+		struct
+		{
+			char *name;
+			uint32_t perm;
+			C9mode mode;
+		}create;
+
+		struct
+		{
+			C9tag oldtag;
+		}flush;
+
+		struct
+		{
+			C9mode mode;
+		}open;
+
+		struct
+		{
+			uint64_t offset;
+			uint32_t size;
+		}read;
+
+		struct
+		{
+			char *wname[C9maxpathel+1]; /* wname[16] is always NULL */
+			C9fid newfid;
+		}walk;
+
+		struct
+		{
+			uint64_t offset;
+			uint8_t *data;
+			uint32_t size;
+		}write;
+
+		C9stat wstat;
+	};
+	C9fid fid;
+};
+
+enum
+{
+	C9tagsbits = sizeof(uint32_t) * 8,
+};
+
+struct C9ctx
+{
+	/*
+	 * Should return a pointer to the data (exactly 'size' bytes) read.
+	 * Set 'err' to non-zero and return NULL in case of error.
+	 * 'err' set to zero (no error) should be used to return from c9process
+	 * early (timeout on read to do non-blocking operations, for example).
+	 */
+	uint8_t *(*read)(C9ctx *ctx, uint32_t size, int *err) __attribute__((nonnull(1, 3)));
+
+	/* Should return a buffer to store 'size' bytes. Nil means no memory. */
+	uint8_t *(*begin)(C9ctx *ctx, uint32_t size) __attribute__((nonnull(1)));
+
+	/*
+	 * Marks the end of a message. Callback may decide if any accumulated
+	 * messages should be sent to the server/client.
+	 */
+	int (*end)(C9ctx *ctx) __attribute__((nonnull(1)));
+
+	/* Callback called every time a new R-message is received. */
+	void (*r)(C9ctx *ctx, C9r *r) __attribute__((nonnull(1, 2)));
+
+	/* Callback called every time a new T-message is received. */
+	void (*t)(C9ctx *ctx, C9t *t) __attribute__((nonnull(1, 2)));
+
+	/* Callback for error messages. */
+	void (*error)(C9ctx *ctx, const char *fmt, ...) __attribute__((nonnull(1), format(printf, 2, 3)));
+
+	/* Auxiliary data, can be used by any of above callbacks. */
+	struct C9aux *aux;
+
+	/* private stuff */
+	void *paux;
+	uint32_t msize;
+#ifndef C9_NO_CLIENT
+	uint32_t flush[C9maxflush];
+	uint32_t tags[C9maxtags/C9tagsbits];
+#endif
+	union
+	{
+#ifndef C9_NO_CLIENT
+		C9tag lowfreetag;
+#endif
+#ifndef C9_NO_SERVER
+		uint16_t svflags;
+#endif
+	};
+};
+
+/* Parse one directory entry. */
+extern C9error c9parsedir(C9ctx *c, C9stat *stat, uint8_t **data, uint32_t *size) __attribute__((nonnull(1, 2, 3)));
+
+#ifndef C9_NO_CLIENT
+
+extern C9error c9version(C9ctx *c, C9tag *tag, uint32_t msize) __attribute__((nonnull(1, 2)));
+extern C9error c9auth(C9ctx *c, C9tag *tag, C9fid afid, const char *uname, const char *aname) __attribute__((nonnull(1, 2)));
+extern C9error c9flush(C9ctx *c, C9tag *tag, C9tag oldtag) __attribute__((nonnull(1, 2)));
+extern C9error c9attach(C9ctx *c, C9tag *tag, C9fid fid, C9fid afid, const char *uname, const char *aname) __attribute__((nonnull(1, 2)));
+extern C9error c9walk(C9ctx *c, C9tag *tag, C9fid fid, C9fid newfid, const char *path[]) __attribute__((nonnull(1, 2, 5)));
+extern C9error c9open(C9ctx *c, C9tag *tag, C9fid fid, C9mode mode) __attribute__((nonnull(1, 2)));
+extern C9error c9create(C9ctx *c, C9tag *tag, C9fid fid, const char *name, uint32_t perm, C9mode mode) __attribute__((nonnull(1, 2, 4)));
+extern C9error c9read(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, uint32_t count) __attribute__((nonnull(1, 2)));
+extern C9error c9write(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, const void *in, uint32_t count) __attribute__((nonnull(1, 2, 5)));
+extern C9error c9wrstr(C9ctx *c, C9tag *tag, C9fid fid, const char *s) __attribute__((nonnull(1, 2, 4)));
+extern C9error c9clunk(C9ctx *c, C9tag *tag, C9fid fid) __attribute__((nonnull(1, 2)));
+extern C9error c9remove(C9ctx *c, C9tag *tag, C9fid fid) __attribute__((nonnull(1, 2)));
+extern C9error c9stat(C9ctx *c, C9tag *tag, C9fid fid) __attribute__((nonnull(1, 2)));
+extern C9error c9wstat(C9ctx *c, C9tag *tag, C9fid fid, const C9stat *s) __attribute__((nonnull(1, 2, 4)));
+
+/*
+ * Wait until a response comes and process it. If the function returns
+ * any error, context must be treated as 'broken' and no subsequent calls
+ * should be made without reinitialization (c9version).
+ */
+extern C9error c9proc(C9ctx *c) __attribute__((nonnull(1)));
+
+#endif /* C9_NO_CLIENT */
+
+#ifndef C9_NO_SERVER
+
+extern C9error s9version(C9ctx *c) __attribute__((nonnull(1)));
+extern C9error s9auth(C9ctx *c, C9tag tag, const C9qid *aqid) __attribute__((nonnull(1, 3)));
+extern C9error s9error(C9ctx *c, C9tag tag, const char *err) __attribute__((nonnull(1)));
+extern C9error s9attach(C9ctx *c, C9tag tag, const C9qid *qid) __attribute__((nonnull(1, 3)));
+extern C9error s9flush(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
+extern C9error s9walk(C9ctx *c, C9tag tag, C9qid *qids[]) __attribute__((nonnull(1, 3)));
+extern C9error s9open(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit) __attribute__((nonnull(1, 3)));
+extern C9error s9create(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit) __attribute__((nonnull(1, 3)));
+extern C9error s9read(C9ctx *c, C9tag tag, const void *data, uint32_t size) __attribute__((nonnull(1, 3)));
+extern C9error s9readdir(C9ctx *c, C9tag tag, C9stat *st[], int *num, uint64_t *offset, uint32_t size) __attribute__((nonnull(1, 3, 4)));
+extern C9error s9write(C9ctx *c, C9tag tag, uint32_t size) __attribute__((nonnull(1)));
+extern C9error s9clunk(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
+extern C9error s9remove(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
+extern C9error s9stat(C9ctx *c, C9tag tag, const C9stat *s) __attribute__((nonnull(1, 3)));
+extern C9error s9wstat(C9ctx *c, C9tag tag) __attribute__((nonnull(1)));
+
+extern C9error s9proc(C9ctx *c) __attribute__((nonnull(1)));
+
+#endif /* C9_NO_SERVER */
--- /dev/null
+++ b/c9/proto.c
@@ -1,0 +1,1165 @@
+#include <stdint.h>
+#include <string.h>
+#include "c9/proto.h"
+
+enum
+{
+	Svver = 1<<0,
+};
+
+#define safestrlen(s) (s == NULL ? 0 : (uint32_t)strlen(s))
+#define maxread(c) (c->msize-4-4-1-2)
+#define maxwrite(c) maxread(c)
+
+static void
+w08(uint8_t **p, uint8_t x)
+{
+	(*p)[0] = x;
+	*p += 1;
+}
+
+static void
+w16(uint8_t **p, uint16_t x)
+{
+	(*p)[0] = x;
+	(*p)[1] = x>>8;
+	*p += 2;
+}
+
+static void
+w32(uint8_t **p, uint32_t x)
+{
+	(*p)[0] = x;
+	(*p)[1] = x>>8;
+	(*p)[2] = x>>16;
+	(*p)[3] = x>>24;
+	*p += 4;
+}
+
+static void
+w64(uint8_t **p, uint64_t x)
+{
+	(*p)[0] = x;
+	(*p)[1] = x>>8;
+	(*p)[2] = x>>16;
+	(*p)[3] = x>>24;
+	(*p)[4] = x>>32;
+	(*p)[5] = x>>40;
+	(*p)[6] = x>>48;
+	(*p)[7] = x>>56;
+	*p += 8;
+}
+
+static void
+wcs(uint8_t **p, const char *s, int len)
+{
+	w16(p, len);
+	if(s != NULL){
+		memmove(*p, s, len);
+		*p += len;
+	}
+}
+
+static uint8_t
+r08(uint8_t **p)
+{
+	*p += 1;
+	return (*p)[-1];
+}
+
+static uint16_t
+r16(uint8_t **p)
+{
+	*p += 2;
+	return (*p)[-2]<<0 | (*p)[-1]<<8;
+}
+
+static uint32_t
+r32(uint8_t **p)
+{
+	*p += 4;
+	return (*p)[-4]<<0 | (*p)[-3]<<8 | (*p)[-2]<<16 | (*p)[-1]<<24;
+}
+
+static uint64_t
+r64(uint8_t **p)
+{
+	uint64_t v;
+
+	v = r32(p);
+	v |= (uint64_t)r32(p)<<32;
+	return v;
+}
+
+#ifndef C9_NO_CLIENT
+
+static C9error
+newtag(C9ctx *c, C9ttype type, C9tag *tag)
+{
+	uint32_t i;
+
+	if(type == Tversion){
+		*tag = 0xffff;
+		return 0;
+	}
+
+	if(c->lowfreetag < C9maxtags){
+		uint32_t d = c->lowfreetag / C9tagsbits, m = c->lowfreetag % C9tagsbits;
+		if((c->tags[d] & 1<<m) != 0){
+			c->tags[d] &= ~(1<<m);
+			*tag = c->lowfreetag++;
+			return 0;
+		}
+	}
+
+	for(i = 0; i < (int)sizeof(c->tags)/sizeof(c->tags[0]); i++){
+		uint32_t x, j;
+		if((x = c->tags[i]) == 0)
+			continue;
+		for(j = 0; j < C9tagsbits; j++){
+			if((x & (1<<j)) != 0){
+				c->tags[i] &= ~(1<<j);
+				*tag = i*C9tagsbits + j;
+				c->lowfreetag = *tag + 1;
+				return 0;
+			}
+		}
+	}
+
+	c->error(c,  "newtag: no free tags");
+	return C9Etag;
+}
+
+static int
+freetag(C9ctx *c, C9tag tag)
+{
+	if(tag != 0xffff){
+		uint32_t d = tag / C9tagsbits, m = tag % C9tagsbits;
+		if(tag >= C9maxtags){
+			c->error(c, "freetag: invalid tag %ud", (uint32_t)tag);
+			return -1;
+		}
+		if((c->tags[d] & 1<<m) != 0){
+			c->error(c, "freetag: double free for tag %ud", (uint32_t)tag);
+			return -1;
+		}
+		if(c->lowfreetag > tag)
+			c->lowfreetag = tag;
+		c->tags[d] |= 1<<m;
+	}
+	return 0;
+}
+
+static uint8_t *
+T(C9ctx *c, uint32_t size, C9ttype type, C9tag *tag, C9error *err)
+{
+	uint8_t *p = NULL;
+
+	if(size > c->msize-4-1-2){
+		c->error(c, "T: invalid size %ud", size);
+		*err = C9Esize;
+	}else if((*err = newtag(c, type, tag)) == 0){
+		size += 4+1+2;
+		if((p = c->begin(c, size)) == NULL){
+			c->error(c, "T: no buffer for %ud bytes", size);
+			freetag(c, *tag);
+			*err = C9Ebuf;
+		}else{
+			*err = 0;
+			w32(&p, size);
+			w08(&p, type);
+			w16(&p, *tag);
+		}
+	}
+	return p;
+}
+
+C9error
+c9version(C9ctx *c, C9tag *tag, uint32_t msize)
+{
+	uint8_t *b;
+	C9error err;
+
+	if(msize < C9minmsize){
+		c->error(c, "c9version: msize too small: %ud", msize);
+		return C9Einit;
+	}
+	memset(c->tags, 0xff, sizeof(c->tags));
+	memset(c->flush, 0xff, sizeof(c->flush));
+	c->lowfreetag = 0;
+	c->msize = msize;
+
+	if((b = T(c, 4+2+6, Tversion, tag, &err)) != NULL){
+		w32(&b, msize);
+		wcs(&b, "9P2000", 6);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9auth(C9ctx *c, C9tag *tag, C9fid afid, const char *uname, const char *aname)
+{
+	uint8_t *b;
+	uint32_t ulen = safestrlen(uname), alen = safestrlen(aname);
+	C9error err;
+
+	if(ulen > C9maxstr || alen > C9maxstr){
+		c->error(c, "c9auth: string too long: %ud chars", ulen > alen ? ulen : alen);
+		return C9Estr;
+	}
+	if((b = T(c, 4+2+ulen+2+alen, Tauth, tag, &err)) != NULL){
+		w32(&b, afid);
+		wcs(&b, uname, ulen);
+		wcs(&b, aname, alen);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9flush(C9ctx *c, C9tag *tag, C9tag oldtag)
+{
+	uint8_t *b;
+	C9error err;
+	int i;
+
+	for(i = 0; i < C9maxflush && c->flush[i] != (uint32_t)~0; i++);
+	if(i == C9maxflush){
+		c->error(c, "c9flush: no free flush slots");
+		return C9Eflush;
+	}
+	if((b = T(c, 2, Tflush, tag, &err)) != NULL){
+		w16(&b, oldtag);
+		err = c->end(c);
+		if(err == 0)
+			c->flush[i] = (uint32_t)oldtag<<16 | *tag;
+	}
+	return err;
+}
+
+C9error
+c9attach(C9ctx *c, C9tag *tag, C9fid fid, C9fid afid, const char *uname, const char *aname)
+{
+	uint32_t ulen = safestrlen(uname), alen = safestrlen(aname);
+	uint8_t *b;
+	C9error err;
+
+	if(ulen > C9maxstr || alen > C9maxstr){
+		c->error(c, "c9attach: string too long: %ud chars", ulen > alen ? ulen : alen);
+		return C9Estr;
+	}
+	if((b = T(c, 4+4+2+ulen+2+alen, Tattach, tag, &err)) != NULL){
+		w32(&b, fid);
+		w32(&b, afid);
+		wcs(&b, uname, ulen);
+		wcs(&b, aname, alen);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9walk(C9ctx *c, C9tag *tag, C9fid fid, C9fid newfid, const char *path[])
+{
+	uint32_t i, j, sz;
+	uint32_t len[C9maxpathel];
+	uint8_t *b;
+	C9error err;
+
+	for(sz = i = 0; i < (int)sizeof(len)/sizeof(len[0]) && path[i] != NULL; i++){
+		len[i] = safestrlen(path[i]);
+		if(len[i] == 0 || len[i] > C9maxstr){
+			c->error(c, "c9walk: invalid path element: %ud chars", len[i]);
+			return C9Epath;
+		}
+		sz += 2 + len[i];
+	}
+	if(path[i] != NULL){
+		c->error(c, "c9walk: invalid elements !(0 <= %ud <= %ud)", i, C9maxpathel);
+		return C9Epath;
+	}
+
+	if((b = T(c, 4+4+2+sz, Twalk, tag, &err)) != NULL){
+		w32(&b, fid);
+		w32(&b, newfid);
+		w16(&b, i);
+		for(j = 0; j < i; j++)
+			wcs(&b, path[j], len[j]);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9open(C9ctx *c, C9tag *tag, C9fid fid, C9mode mode)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = T(c, 4+1, Topen, tag, &err)) != NULL){
+		w32(&b, fid);
+		w08(&b, mode);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9create(C9ctx *c, C9tag *tag, C9fid fid, const char *name, uint32_t perm, C9mode mode)
+{
+	uint32_t nlen = strlen(name);
+	uint8_t *b;
+	C9error err;
+
+	if(nlen == 0 || nlen > C9maxstr){
+		c->error(c, "c9create: invalid name: %ud chars", nlen);
+		return C9Epath;
+	}
+	if((b = T(c, 4+2+nlen+4+1, Tcreate, tag, &err)) != NULL){
+		w32(&b, fid);
+		wcs(&b, name, nlen);
+		w32(&b, perm);
+		w08(&b, mode);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9read(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, uint32_t count)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = T(c, 4+8+4, Tread, tag, &err)) != NULL){
+		w32(&b, fid);
+		w64(&b, offset);
+		w32(&b, count);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9write(C9ctx *c, C9tag *tag, C9fid fid, uint64_t offset, const void *in, uint32_t count)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = T(c, 4+8+4+count, Twrite, tag, &err)) != NULL){
+		w32(&b, fid);
+		w64(&b, offset);
+		w32(&b, count);
+		memmove(b, in, count);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9wrstr(C9ctx *c, C9tag *tag, C9fid fid, const char *s)
+{
+	return c9write(c, tag, fid, 0, s, strlen(s));
+}
+
+C9error
+c9clunk(C9ctx *c, C9tag *tag, C9fid fid)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = T(c, 4, Tclunk, tag, &err)) != NULL){
+		w32(&b, fid);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9remove(C9ctx *c, C9tag *tag, C9fid fid)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = T(c, 4, Tremove, tag, &err)) != NULL){
+		w32(&b, fid);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9stat(C9ctx *c, C9tag *tag, C9fid fid)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = T(c, 4, Tstat, tag, &err)) != NULL){
+		w32(&b, fid);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9wstat(C9ctx *c, C9tag *tag, C9fid fid, const C9stat *s)
+{
+	uint32_t nlen = safestrlen(s->name), ulen = safestrlen(s->uid), glen = safestrlen(s->gid);
+	uint32_t unusedsz = 2+4+13, statsz = unusedsz+4+4+4+8+2+nlen+2+ulen+2+glen+2;
+	uint8_t *b;
+	C9error err;
+
+	if(nlen == 0 || nlen > C9maxstr){
+		c->error(c, "c9wstat: invalid name: %ud chars", nlen);
+		return C9Epath;
+	}
+	if(ulen > C9maxstr || glen > C9maxstr){
+		c->error(c, "c9wstat: string too long: %ud chars", ulen > glen ? ulen : glen);
+		return C9Estr;
+	}
+	if((b = T(c, 4+2+2+statsz, Twstat, tag, &err)) != NULL){
+		w32(&b, fid);
+		w16(&b, statsz+2);
+		w16(&b, statsz);
+		memset(b, 0xff, unusedsz); /* leave type(2), dev(4) and qid(13) unchanged */
+		b += unusedsz;
+		w32(&b, s->mode);
+		w32(&b, s->atime);
+		w32(&b, s->mtime);
+		w64(&b, s->size);
+		wcs(&b, s->name, nlen);
+		wcs(&b, s->uid, ulen);
+		wcs(&b, s->gid, glen);
+		wcs(&b, NULL, 0); /* muid unchanged */
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+c9proc(C9ctx *c)
+{
+	uint32_t i, sz, cnt, msize;
+	uint8_t *b;
+	int err;
+	C9r r;
+
+	err = -1;
+	if((b = c->read(c, 4, &err)) == NULL){
+		if(err != 0)
+			c->error(c, "c9proc: short read");
+		return err == 0 ? 0 : C9Epkt;
+	}
+
+	sz = r32(&b);
+	if(sz < 7 || sz > c->msize){
+		c->error(c, "c9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
+		return C9Epkt;
+	}
+	sz -= 4;
+	err = -1;
+	if((b = c->read(c, sz, &err)) == NULL){
+		if(err != 0)
+			c->error(c, "c9proc: short read");
+		return err == 0 ? 0 : C9Epkt;
+	}
+
+	r.type = r08(&b);
+	r.tag = r16(&b);
+	if(r.type != Rversion){
+		if(r.tag >= C9maxtags){
+			c->error(c, "c9proc: invalid tag %ud", (uint32_t)r.tag);
+			return C9Epkt;
+		}
+		if(freetag(c, r.tag) != 0)
+			return C9Etag;
+	}
+	sz -= 3;
+	r.numqid = 0;
+
+	switch(r.type){
+	case Rread:
+		if(sz < 4 || (cnt = r32(&b)) > sz-4)
+			goto error;
+		r.read.data = b;
+		r.read.size = cnt;
+		c->r(c, &r);
+		break;
+
+	case Rwrite:
+		if(sz < 4 || (cnt = r32(&b)) > c->msize)
+			goto error;
+		r.write.size = cnt;
+		c->r(c, &r);
+		break;
+
+	case Rwalk:
+		if(sz < 2 || (cnt = r16(&b))*13 > sz-2)
+			goto error;
+		if(cnt > C9maxpathel){
+			c->error(c, "c9proc: Rwalk !(%ud <= %ud)", cnt, C9maxpathel);
+			return C9Epath;
+		}
+		for(i = 0; i < cnt; i++){
+			r.qid[i].type = r08(&b);
+			r.qid[i].version = r32(&b);
+			r.qid[i].path = r64(&b);
+		}
+		r.numqid = cnt;
+		c->r(c, &r);
+		break;
+
+	case Rstat:
+		b += 2; sz -= 2;
+		if((err = c9parsedir(c, &r.stat, &b, &sz)) != 0)
+			return err;
+		r.numqid = 1;
+		c->r(c, &r);
+		break;
+
+	case Rflush:
+		for(i = 0; i < C9maxflush; i++){
+			if((c->flush[i] & 0xffff) == r.tag){
+				freetag(c, c->flush[i]>>16);
+				c->flush[i] = 0xffffffff;
+				break;
+			}
+		}
+		/* fallthrough */
+	case Rclunk:
+	case Rremove:
+	case Rwstat:
+		c->r(c, &r);
+		break;
+
+	case Ropen:
+	case Rcreate:
+		if(sz < 17)
+			goto error;
+		r.qid[0].type = r08(&b);
+		r.qid[0].version = r32(&b);
+		r.qid[0].path = r64(&b);
+		r.iounit = r32(&b);
+		r.numqid = 1;
+		c->r(c, &r);
+		break;
+
+	case Rerror:
+		if(sz < 2 || (cnt = r16(&b)) > sz-2)
+			goto error;
+		r.error = memmove(b-1, b, cnt);
+		r.error[cnt] = 0;
+		c->r(c, &r);
+		break;
+
+	case Rauth:
+	case Rattach:
+		if(sz < 13)
+			goto error;
+		r.qid[0].type = r08(&b);
+		r.qid[0].version = r32(&b);
+		r.qid[0].path = r64(&b);
+		r.numqid = 1;
+		c->r(c, &r);
+		break;
+
+	case Rversion:
+		if(sz < 4+2 || (msize = r32(&b)) < C9minmsize || (cnt = r16(&b)) > sz-4-2)
+			goto error;
+		if(cnt < 6 || memcmp(b, "9P2000", 6) != 0){
+			c->error(c, "invalid version");
+			return C9Ever;
+		}
+		if(msize < c->msize)
+			c->msize = msize;
+		c->r(c, &r);
+		break;
+
+	default:
+		goto error;
+	}
+	return 0;
+error:
+	c->error(c, "c9proc: invalid packet type %ud", r.type);
+	return C9Epkt;
+}
+
+#endif /* C9_NO_CLIENT */
+
+C9error
+c9parsedir(C9ctx *c, C9stat *stat, uint8_t **t, uint32_t *size)
+{
+	uint8_t *b;
+	uint32_t cnt, sz;
+
+	sz = 0;
+	if(*size < 49 || (sz = r16(t)) < 47 || *size < 2+sz)
+		goto error;
+	*size -= 2+sz;
+	*t += 6; /* skip type(2) and dev(4) */
+	stat->qid.type = r08(t);
+	stat->qid.version = r32(t);
+	stat->qid.path = r64(t);
+	stat->mode = r32(t);
+	stat->atime = r32(t);
+	stat->mtime = r32(t);
+	stat->size = r64(t);
+	sz -= 39;
+	if((cnt = r16(t)) > sz-2)
+		goto error;
+	stat->name = (char*)*t; b = *t = *t+cnt; sz -= 2+cnt;
+	if(sz < 2 || (cnt = r16(t)) > sz-2)
+		goto error;
+	stat->uid = (char*)*t; *b = 0; b = *t = *t+cnt; sz -= 2+cnt;
+	if(sz < 2 || (cnt = r16(t)) > sz-2)
+		goto error;
+	stat->gid = (char*)*t; *b = 0; b = *t = *t+cnt; sz -= 2+cnt;
+	if(sz < 2 || (cnt = r16(t)) > sz-2)
+		goto error;
+	stat->muid = memmove(*t-1, *t, cnt); *b = stat->muid[cnt] = 0; *t = *t+cnt; sz -= 2+cnt;
+	*t += sz;
+	return 0;
+error:
+	c->error(c, "c9parsedir: invalid size: size=%ud sz=%ud", *size, sz);
+	return C9Epkt;
+}
+
+#ifndef C9_NO_SERVER
+
+static uint8_t *
+R(C9ctx *c, uint32_t size, C9rtype type, C9tag tag, C9error *err)
+{
+	uint8_t *p = NULL;
+
+	if(size > c->msize-4-1-2){
+		c->error(c, "R: invalid size %ud", size);
+		*err = C9Esize;
+	}else{
+		size += 4+1+2;
+		if((p = c->begin(c, size)) == NULL){
+			c->error(c, "R: no buffer for %ud bytes", size);
+			*err = C9Ebuf;
+		}else{
+			*err = 0;
+			w32(&p, size);
+			w08(&p, type);
+			w16(&p, tag);
+		}
+	}
+	return p;
+}
+
+C9error
+s9version(C9ctx *c)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 4+2+6, Rversion, 0xffff, &err)) != NULL){
+		w32(&b, c->msize);
+		wcs(&b, "9P2000", 6);
+		err = c->end(c);
+	};
+	return err;
+}
+
+C9error
+s9auth(C9ctx *c, C9tag tag, const C9qid *aqid)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 13, Rauth, tag, &err)) != NULL){
+		w08(&b, aqid->type);
+		w32(&b, aqid->version);
+		w64(&b, aqid->path);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9error(C9ctx *c, C9tag tag, const char *ename)
+{
+	uint32_t len = safestrlen(ename);
+	uint8_t *b;
+	C9error err;
+
+	if(len > C9maxstr){
+		c->error(c, "s9error: invalid ename: %ud chars", len);
+		return C9Estr;
+	}
+	if((b = R(c, 2+len, Rerror, tag, &err)) != NULL){
+		wcs(&b, ename, len);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9attach(C9ctx *c, C9tag tag, const C9qid *qid)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 13, Rattach, tag, &err)) != NULL){
+		w08(&b, qid->type);
+		w32(&b, qid->version);
+		w64(&b, qid->path);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9flush(C9ctx *c, C9tag tag)
+{
+	C9error err;
+
+	if(R(c, 0, Rflush, tag, &err) != NULL)
+		err = c->end(c);
+	return err;
+}
+
+C9error
+s9walk(C9ctx *c, C9tag tag, C9qid *qids[])
+{
+	uint32_t i, n;
+	uint8_t *b;
+	C9error err;
+
+	for(n = 0; n < C9maxpathel && qids[n] != NULL; n++);
+	if(n > C9maxpathel){
+		c->error(c, "s9walk: invalid elements !(0 <= %ud <= %ud)", n, C9maxpathel);
+		return C9Epath;
+	}
+
+	if((b = R(c, 2+n*13, Rwalk, tag, &err)) != NULL){
+		w16(&b, n);
+		for(i = 0; i < n; i++){
+			w08(&b, qids[i]->type);
+			w32(&b, qids[i]->version);
+			w64(&b, qids[i]->path);
+		}
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9open(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 13+4, Ropen, tag, &err)) != NULL){
+		w08(&b, qid->type);
+		w32(&b, qid->version);
+		w64(&b, qid->path);
+		w32(&b, iounit);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9create(C9ctx *c, C9tag tag, const C9qid *qid, uint32_t iounit)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 13+4, Rcreate, tag, &err)) != NULL){
+		w08(&b, qid->type);
+		w32(&b, qid->version);
+		w64(&b, qid->path);
+		w32(&b, iounit);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9read(C9ctx *c, C9tag tag, const void *data, uint32_t size)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 4+size, Rread, tag, &err)) != NULL){
+		w32(&b, size);
+		memmove(b, data, size);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9write(C9ctx *c, C9tag tag, uint32_t size)
+{
+	uint8_t *b;
+	C9error err;
+
+	if((b = R(c, 4, Rwrite, tag, &err)) != NULL){
+		w32(&b, size);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9readdir(C9ctx *c, C9tag tag, C9stat *st[], int *num, uint64_t *offset, uint32_t size)
+{
+	uint8_t *b;
+	const C9stat *s;
+	uint32_t nlen, ulen, glen, mulen, m, n;
+	C9error err;
+	int i;
+
+	if(size > c->msize-4-1-2)
+		size = c->msize-4-1-2;
+
+	m = 0;
+	for(i = 0; i < *num; i++){
+		s = st[i];
+		nlen = safestrlen(s->name);
+		ulen = safestrlen(s->uid);
+		glen = safestrlen(s->gid);
+		mulen = safestrlen(s->muid);
+
+		if(nlen == 0 || nlen > C9maxstr){
+			c->error(c, "s9readdir: invalid name: %ud chars", nlen);
+			return C9Epath;
+		}
+		if(ulen > C9maxstr || glen > C9maxstr || mulen > C9maxstr){
+			ulen = ulen > glen ? ulen : glen;
+			ulen = ulen > mulen ? ulen : mulen;
+			c->error(c, "s9readdir: string too long: %ud chars", ulen);
+			return C9Estr;
+		}
+
+		n = 2 + 2+4+13+4+4+4+8+2+nlen+2+ulen+2+glen+2+mulen;
+		if(4+m+n > size)
+			break;
+		m += n;
+	}
+
+	if((b = R(c, 4+m, Rread, tag, &err)) != NULL){
+		*num = i;
+		w32(&b, m);
+		for(i = 0; i < *num; i++){
+			s = st[i];
+			nlen = safestrlen(s->name);
+			ulen = safestrlen(s->uid);
+			glen = safestrlen(s->gid);
+			mulen = safestrlen(s->muid);
+			w16(&b, 2+4+13+4+4+4+8+2+nlen+2+ulen+2+glen+2+mulen);
+			w16(&b, 0xffff); /* type */
+			w32(&b, 0xffffffff); /* dev */
+			w08(&b, s->qid.type);
+			w32(&b, s->qid.version);
+			w64(&b, s->qid.path);
+			w32(&b, s->mode);
+			w32(&b, s->atime);
+			w32(&b, s->mtime);
+			w64(&b, s->size);
+			wcs(&b, s->name, nlen);
+			wcs(&b, s->uid, ulen);
+			wcs(&b, s->gid, glen);
+			wcs(&b, s->muid, mulen);
+		}
+		err = c->end(c);
+		if(err == 0)
+			*offset += m;
+	}
+	return err;
+}
+
+C9error
+s9clunk(C9ctx *c, C9tag tag)
+{
+	C9error err;
+
+	if(R(c, 0, Rclunk, tag, &err) != NULL)
+		err = c->end(c);
+	return err;
+}
+
+C9error
+s9remove(C9ctx *c, C9tag tag)
+{
+	C9error err;
+
+	if(R(c, 0, Rremove, tag, &err) != NULL)
+		err = c->end(c);
+	return err;
+}
+
+C9error
+s9stat(C9ctx *c, C9tag tag, const C9stat *s)
+{
+	uint32_t nlen = safestrlen(s->name), ulen = safestrlen(s->uid);
+	uint32_t glen = safestrlen(s->gid), mulen = safestrlen(s->name);
+	uint32_t statsz = 2+4+13+4+4+4+8+2+nlen+2+ulen+2+glen+2+mulen;
+	uint8_t *b;
+	C9error err;
+
+	if(nlen == 0 || nlen > C9maxstr){
+		c->error(c, "s9stat: invalid name: %ud chars", nlen);
+		return C9Epath;
+	}
+	if(ulen > C9maxstr || glen > C9maxstr || mulen > C9maxstr){
+		ulen = ulen > glen ? ulen : glen;
+		ulen = ulen > mulen ? ulen : mulen;
+		c->error(c, "s9stat: string too long: %ud chars", ulen);
+		return C9Estr;
+	}
+
+	if((b = R(c, 2+2+statsz, Rstat, tag, &err)) != NULL){
+		w16(&b, statsz+2);
+		w16(&b, statsz);
+		w16(&b, 0xffff); /* type */
+		w32(&b, 0xffffffff); /* dev */
+		w08(&b, s->qid.type);
+		w32(&b, s->qid.version);
+		w64(&b, s->qid.path);
+		w32(&b, s->mode);
+		w32(&b, s->atime);
+		w32(&b, s->mtime);
+		w64(&b, s->size);
+		wcs(&b, s->name, nlen);
+		wcs(&b, s->uid, ulen);
+		wcs(&b, s->gid, glen);
+		wcs(&b, s->muid, mulen);
+		err = c->end(c);
+	}
+	return err;
+}
+
+C9error
+s9wstat(C9ctx *c, C9tag tag)
+{
+	C9error err;
+
+	if(R(c, 0, Rwstat, tag, &err) != NULL)
+		err = c->end(c);
+	return err;
+}
+
+C9error
+s9proc(C9ctx *c)
+{
+	uint32_t i, sz, cnt, n, msize;
+	int readerr;
+	uint8_t *b;
+	C9error err;
+	C9t t;
+
+	readerr = -1;
+	if((b = c->read(c, 4, &readerr)) == NULL){
+		if(readerr != 0)
+			c->error(c, "s9proc: short read");
+		return readerr == 0 ? 0 : C9Epkt;
+	}
+
+	sz = r32(&b);
+	if(sz < 7 || sz > c->msize){
+		c->error(c, "s9proc: invalid packet size !(7 <= %ud <= %ud)", sz, c->msize);
+		return C9Epkt;
+	}
+	sz -= 4;
+	readerr = -1;
+	if((b = c->read(c, sz, &readerr)) == NULL){
+		if(readerr != 0)
+			c->error(c, "s9proc: short read");
+		return readerr == 0 ? 0 : C9Epkt;
+	}
+
+	t.type = r08(&b);
+	t.tag = r16(&b);
+	sz -= 3;
+
+	if((c->svflags & Svver) == 0 && t.type != Tversion){
+		c->error(c, "s9proc: expected Tversion, got %ud", t.type);
+		return C9Epkt;
+	}
+
+	switch(t.type){
+	case Tread:
+		if(sz < 4+8+4)
+			goto error;
+		t.fid = r32(&b);
+		t.read.offset = r64(&b);
+		t.read.size = r32(&b);
+		if(t.read.size > maxread(c))
+		  t.read.size = maxread(c);
+		c->t(c, &t);
+		break;
+
+	case Twrite:
+		if(sz < 4+8+4)
+			goto error;
+		t.fid = r32(&b);
+		t.write.offset = r64(&b);
+		if((t.write.size = r32(&b)) < sz-4-8-4)
+			goto error;
+		if(t.write.size > maxwrite(c))
+		  t.write.size = maxwrite(c);
+		t.write.data = b;
+		c->t(c, &t);
+		break;
+
+	case Tclunk:
+	case Tstat:
+	case Tremove:
+		if(sz < 4)
+			goto error;
+		t.fid = r32(&b);
+		c->t(c, &t);
+		break;
+
+	case Twalk:
+		if(sz < 4+4+2)
+			goto error;
+		t.fid = r32(&b);
+		t.walk.newfid = r32(&b);
+		if((n = r16(&b)) > 16){
+			c->error(c, "s9proc: Twalk !(%ud <= 16)", n);
+			return C9Epath;
+		}
+		sz -= 4+4+2;
+		if(n > 0){
+			for(i = 0; i < n; i++){
+				if(sz < 2 || (cnt = r16(&b)) > sz-2)
+					goto error;
+				if(cnt < 1){
+					c->error(c, "s9proc: Twalk invalid element [%ud]", i);
+					return C9Epath;
+				}
+				b[-2] = 0;
+				t.walk.wname[i] = (char*)b;
+				b += cnt;
+				sz -= 2 + cnt;
+			}
+			memmove(t.walk.wname[i-1]-1, t.walk.wname[i-1], (char*)b - t.walk.wname[i-1]);
+			t.walk.wname[i-1]--;
+			b[-1] = 0;
+		}else
+			i = 0;
+		t.walk.wname[i] = NULL;
+		c->t(c, &t);
+		break;
+
+	case Topen:
+		if(sz < 4+1)
+			goto error;
+		t.fid = r32(&b);
+		t.open.mode = r08(&b);
+		c->t(c, &t);
+		break;
+
+	case Twstat:
+		if(sz < 4+2)
+			goto error;
+		t.fid = r32(&b);
+		if((cnt = r16(&b)) > sz-4)
+			goto error;
+		if((err = c9parsedir(c, &t.wstat, &b, &cnt)) != 0){
+			c->error(c, "s9proc");
+			return err;
+		}
+		c->t(c, &t);
+		break;
+
+	case Tcreate:
+		if(sz < 4+2+4+1)
+			goto error;
+		t.fid = r32(&b);
+		if((cnt = r16(&b)) < 1 || cnt > sz-4-2-4-1)
+			goto error;
+		t.create.name = (char*)b;
+		b += cnt;
+		t.create.perm = r32(&b);
+		t.create.mode = r08(&b);
+		t.create.name[cnt] = 0;
+		c->t(c, &t);
+		break;
+
+	case Tflush:
+		if(sz < 2)
+			goto error;
+		t.flush.oldtag = r16(&b);
+		c->t(c, &t);
+		break;
+
+	case Tversion:
+		if(sz < 4+2 || (msize = r32(&b)) < C9minmsize || (cnt = r16(&b)) > sz-4-2)
+			goto error;
+		if(cnt < 6 || memcmp(b, "9P2000", 6) != 0){
+			if((b = R(c, 4+2+7, Rversion, 0xffff, &err)) != NULL){
+				w32(&b, 0);
+				wcs(&b, "unknown", 7);
+				err = c->end(c);
+				c->error(c, "s9proc: invalid version");
+			}
+			return C9Ever;
+		}
+		if(msize < c->msize)
+			c->msize = msize;
+		c->svflags |= Svver;
+		t.version.msize = msize;
+		c->t(c, &t);
+		break;
+
+	case Tattach:
+		if(sz < 4+4+2+2)
+			goto error;
+		t.fid = r32(&b);
+		t.attach.afid = r32(&b);
+		cnt = r16(&b);
+		sz -= 4+4+2;
+		if(cnt+2 > sz)
+			goto error;
+		t.attach.uname = (char*)b;
+		b += cnt;
+		cnt = r16(&b);
+		b[-2] = 0;
+		sz -= cnt+2;
+		if(cnt > sz)
+			goto error;
+		memmove(b-1, b, cnt);
+		t.attach.aname = (char*)b-1;
+		t.attach.aname[cnt] = 0;
+		c->t(c, &t);
+		break;
+
+	case Tauth:
+		if(sz < 4+2+2)
+			goto error;
+		t.auth.afid = r32(&b);
+		cnt = r16(&b);
+		sz -= 4+2;
+		if(cnt+2 > sz)
+			goto error;
+		t.auth.uname = (char*)b;
+		b += cnt;
+		cnt = r16(&b);
+		b[-2] = 0;
+		sz -= cnt+2;
+		if(cnt > sz)
+			goto error;
+		memmove(b-1, b, cnt);
+		t.auth.aname = (char*)b-1;
+		t.auth.aname[cnt] = 0;
+		c->t(c, &t);
+		break;
+
+	default:
+		goto error;
+	}
+	return 0;
+error:
+	c->error(c, "s9proc: invalid packet (type=%ud)", t.type);
+	return C9Epkt;
+}
+
+#endif /* C9_NO_SERVER */