shithub: 9pro

ref: d1d3a8d1fe4af711ca6628b457489b4a56e0908c
dir: /9pex.c/

View raw version
#ifdef __linux__
#define _GNU_SOURCE
#else
#define _DEFAULT_SOURCE
#endif
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#if defined(__APPLE__) || defined(__NetBSD__)
#define st_atim st_atimespec
#define st_ctim st_ctimespec
#endif
#include "c9.h"
#include "parg.h"

#define max(a,b) ((a)>(b)?(a):(b))
#define used(x) ((void)(x))
#define nelem(x) (int)(sizeof(x)/sizeof((x)[0]))

uint32_t crc32(const void *data, int len);

typedef struct Fid Fid;
typedef struct Id Id;
typedef struct Tag Tag;

enum
{
	Canrd = 1<<0,
	Canwr = 1<<1,
};

struct Fid
{
	char *path; /* full path */
	char *name; /* base name */

	DIR *dir; /* set when it's an opened directory */
	uint64_t diroffset; /* to read dirs correctly */

	C9qid qid;
	C9fid fid;
	C9mode mode; /* mode in which the file was opened */
	uint32_t iounit;

	int fd; /* set when it's an opened file */
};

struct Tag
{
	C9tag tag;
};

struct Id
{
	char *name;
	uint32_t id;
};

static char *t2s[] =
{
	[Tversion-Tversion] = "Tversion",
	[Tauth-Tversion] = "Tauth",
	[Tattach-Tversion] = "Tattach",
	[Tflush-Tversion] = "Tflush",
	[Twalk-Tversion] = "Twalk",
	[Topen-Tversion] = "Topen",
	[Tcreate-Tversion] = "Tcreate",
	[Tread-Tversion] = "Tread",
	[Twrite-Tversion] = "Twrite",
	[Tclunk-Tversion] = "Tclunk",
	[Tremove-Tversion] = "Tremove",
	[Tstat-Tversion] = "Tstat",
	[Twstat-Tversion] = "Twstat",
};

static char *modes[] =
{
	"read", "write", "rdwr", "exec",
};

static char *Enoauth = "authentication not required";
static char *Eunknownfid = "unknown fid";
static char *Enowstat = "wstat prohibited";
static char *Eperm = "permission denied";
static char *Enowrite = "write prohibited";
static char *Enomem = "out of memory";
static char *Edupfid = "duplicate fid";
static char *Ewalknodir = "walk in non-directory";
static char *Enotfound = "file not found";
static char *Eduptag = "duplicate tag";
static char *Ebotch = "9P protocol botch";
static char *Enocreate = "create prohibited";
static char *Eisdir = "is a directory";
static char *Ebadoffset = "bad offset";

static int in, out, eof;
static C9ctx ctx;
static int debug, rootescape;
static Fid **fids;
static int numfids;
static Tag **tags;
static int numtags;
static char *rootpath;
static size_t rootlen;
static C9qid walkqids[C9maxpathel];
static uint8_t *rdbuf;
static uint8_t *wrbuf;
static uint32_t wroff, wrend, wrbufsz;
static Id *uids, *gids;
static int numuids, numgids;

__attribute__((format(printf, 1, 2)))
static void
trace(const char *fmt, ...)
{
	va_list ap;

	if(debug == 0)
		return;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
}

static int
canrw(int rdonly, int block)
{
	struct timeval t;
	fd_set r, w, e;
	int fl;

	FD_ZERO(&r);
	FD_SET(in, &r);
	FD_ZERO(&w);
	if(rdonly == 0)
		FD_SET(out, &w);
	FD_ZERO(&e);
	FD_SET(in, &e);
	FD_SET(out, &e);
	memset(&t, 0, sizeof(t));
	t.tv_usec = 1000;
	for(;;){
		errno = 0;
		if(select(max(in, out) + 1, &r, &w, &e, block ? NULL : &t) < 0 ||
		   FD_ISSET(in, &e) || FD_ISSET(out, &e)){
			if(errno == EINTR)
				continue;
			return -1;
		}
		break;
	}

	fl = 0;
	if(FD_ISSET(in, &r))
		fl |= Canrd;
	if(FD_ISSET(out, &w))
		fl |= Canwr;

	return fl;
}

static int
wrsend(void)
{
	uint32_t n;
	int w;

	if(wrend == 0)
		return 0;
	for(n = 0; n < wrend; n += w){
		errno = 0;
		if((w = write(out, wrbuf+n, wrend-n)) <= 0){
			if(errno == EINTR)
				continue;
			if(errno != EPIPE) /* remote end closed */
				perror("write");
			return -1;
		}
	}
	if(debug >= 2)
		trace("<- %d bytes, %d left\n", wrend, wroff-wrend);
	memmove(wrbuf, wrbuf+wrend, wroff-wrend);
	wroff = wroff - wrend;

	return 0;
}

static int
hastag(C9tag tag)
{
	int i;

	for(i = 0; i < numtags; i++){
		if(tags[i] != NULL && tags[i]->tag == tag)
			return 1;
	}

	return 0;
}

static int
statpath(char *path, struct stat *st, char **err)
{
	if(stat(path, st) == 0)
		return 0;

	if(errno == EACCES)
		*err = Eperm;
	else if(errno == ENOMEM)
		*err = Enomem;
	else if(errno == ENOTDIR)
		*err = Ewalknodir;
	else if(errno == ENOENT)
		*err = Enotfound;
	else
		*err = strerror(errno);

	return -1;
}

#define copyadvance(p, data, size) do{ memmove(p, data, size); p += size; }while(0)

static void
stat2qid(struct stat *st, C9qid *qid, uint32_t *iounit)
{
	uint8_t b[sizeof(st->st_ctim) + sizeof(st->st_size) + sizeof(st->st_dev) + sizeof(st->st_ino)], *p;
	int fmt;

	qid->path = st->st_ino;
	qid->type = C9qtfile;

	p = b;
	copyadvance(p, &st->st_ctime, sizeof(st->st_ctim));
	copyadvance(p, &st->st_size, sizeof(st->st_size));
	copyadvance(p, &st->st_dev, sizeof(st->st_dev));
	copyadvance(p, &st->st_ino, sizeof(st->st_ino));
	qid->version = crc32(b, p-b);

	fmt = st->st_mode & S_IFMT;
	if(fmt == S_IFDIR)
		qid->type |= C9qtdir;
	if((st->st_mode & 0222) != 0 &&
	   (fmt == S_IFCHR || fmt == S_IFCHR || fmt == S_IFSOCK || fmt == S_IFIFO))
		qid->type |= C9qtappend;
	if(iounit != NULL)
		*iounit = 0;
}

static Fid *
newfid(C9fid fid, char *path, char **err)
{
	Fid *f, **newfids;
	struct stat st;
	int i;

	for(i = 0; i < numfids; i++){
		if(fids[i] != NULL && fids[i]->fid == fid){
			*err = Edupfid;
			return NULL;
		}
	}

	if(statpath(path, &st, err) != 0)
		return NULL;

	if((f = calloc(1, sizeof(*f))) == NULL || (f->path = strdup(path)) == NULL){
nomem:
		if(f != NULL){
			free(f->path);
			free(f);
		}
		*err = Enomem;
		return NULL;
	}
	f->fd = -1;
	f->name = strrchr(f->path, '/');
	if(f->name == NULL)
		f->name = f->path;
	else
		f->name++;
	if(f->name[0] == 0)
		fprintf(stderr, "%s -> empty file name\n", f->path);

	for(i = 0; i < numfids; i++){
		if(fids[i] == NULL){
			fids[i] = f;
			break;
		}
	}
	if(i >= numfids){
		if((newfids = realloc(fids, (numfids+1)*sizeof(*fids))) == NULL)
			goto nomem;
		fids = newfids;
		fids[numfids++] = f;
	}

	f->fid = fid;
	stat2qid(&st, &f->qid, &f->iounit);

	return f;
}

static Fid *
findfid(C9fid fid, char **err)
{
	int i;

	for(i = 0; i < numfids; i++){
		if(fids[i] != NULL && fids[i]->fid == fid){
			return fids[i];
		}
	}

	*err = Eunknownfid;

	return NULL;
}

static int
delfid(C9fid fid, char **err)
{
	Fid *f;
	int i;

	for(i = 0; i < numfids; i++){
		f = fids[i];
		if(f != NULL && f->fid == fid){
			if(f->dir != NULL)
				closedir(f->dir);
			else if(f->fd >= 0)
				close(f->fd);
			free(f->path);
			free(f);
			fids[i] = NULL;
			return 0;
		}
	}

	*err = Eunknownfid;

	return -1;
}

static int
hasperm(struct stat *st, C9mode mode, char **err)
{
	int m, stm, fmt;

	m = mode & 0xf;
	stm = st->st_mode & 0777;
	*err = Eperm;
	if(((stm & 0111) == 0 || (stm & 0444) == 0) && m == C9exec) /* executing needs rx */
		return 0;
	if((stm & 0222) == 0 && (m == C9write || m == C9rdwr)) /* writing needs w */
		return 0;
	if((stm & 0444) == 0 && m != C9write) /* reading needs r */
		return 0;
	fmt = st->st_mode & S_IFMT;
	if(fmt == S_IFDIR && ((stm & 0111) == 0 || (stm & 0444) == 0)) /* dirs need rx */
		return 0;
	*err = NULL;

	return 1;
}

static Fid *
walk(C9fid fid, C9fid nfid, char *el[], C9qid *qids[], char **err)
{
	Fid *f;
	char *path, *real, *p;
	struct stat st;
	int i, plen, ellen;

	if((f = findfid(fid, err)) == NULL)
		return NULL;

	if(el[0] == NULL){ /* nwname = 0 */
		qids[0] = NULL;
		if(fid == nfid)
			return f;
		return newfid(nfid, f->path, err);
	}

	if((f->qid.type & C9qtdir) == 0){ /* has to be a dir */
		*err = Ewalknodir;
		return NULL;
	}

	p = strdup(f->path);
	f = NULL;
	for(i = 0; el[i] != NULL; i++){
		plen = strlen(p);
		ellen = strlen(el[i]);
		path = malloc(plen + 1 + ellen + 1);
		memmove(path, p, plen);
		path[plen] = '/';
		memmove(path+plen+1, el[i], ellen);
		path[plen+1+ellen] = 0;

		if(!rootescape){
			real = realpath(path, NULL);
			free(path);
			if(real == NULL)
				break;
			if(strlen(real) < rootlen){ /* don't escape root */
				free(real);
				real = strdup(rootpath);
			}
		}else{
			real = path;
		}

		free(p);
		p = real;

		if(statpath(p, &st, err) != 0)
			break;
		if(el[i+1] != NULL && !hasperm(&st, C9read, err))
			break;

		qids[i] = &walkqids[i];
		stat2qid(&st, qids[i], NULL);
	}

	qids[i] = NULL;
	if(el[i] == NULL){ /* could walk all the way */
		f = newfid(nfid, p, err);
		if(f != NULL && f->name[0] == '/' && f->name[1] == 0) /* root */
			f->name = "/";
	}else if(i != 0){ /* didn't fail on the first one */
		*err = NULL;
	}
	free(p);

	return f;
}

static int
mode2unix(C9mode mode, int qidtype)
{
	int omode;

	omode = O_NOFOLLOW;
	if((mode & 3) == C9read)
		omode |= O_RDONLY;
	else if((mode & 3) == C9write)
		omode |= O_WRONLY;
	else if((mode & 3) == C9rdwr)
		omode |= O_RDWR;
	if(mode & C9trunc)
		omode |= O_TRUNC;
#ifdef __linux__
	if(mode & C9rclose)
		omode |= O_TMPFILE;
#endif
	if((qidtype & C9qtappend) != 0)
		omode |= O_APPEND;

	return omode;
}

static int
openfid(Fid *f, C9mode mode, char **err)
{
	struct stat st;
	int omode;

	if(stat(f->path, &st) != 0 || !hasperm(&st, mode, err))
		goto error;
	stat2qid(&st, &f->qid, &f->iounit);

	if((f->qid.type & C9qtdir) != 0){
		if((f->dir = opendir(f->path)) == NULL){
			*err = strerror(errno);
			return -1;
		}
		f->fd = dirfd(f->dir);
	}else{
		omode = mode2unix(mode, f->qid.type);
		f->fd = open(f->path, omode);
	}

	if(f->fd < 0)
		goto error;
	f->mode = mode;

	return 0;
error:
	if(*err == NULL)
		*err = strerror(errno);

	if(f->dir != NULL)
		closedir(f->dir);
	else if(f->fd >= 0)
		close(f->fd);

	f->dir = NULL;
	f->fd = -1;
	return -1;
}

static char *
uid2str(uid_t uid, char **err)
{
	struct passwd *p;
	Id *newuids;
	int i;

	for(i = 0; i < numuids; i++){
		if(uids[i].id == uid)
			return uids[i].name;
	}
	if((p = getpwuid(uid)) == NULL){
		*err = strerror(errno);
		return NULL;
	}
	if((newuids = realloc(uids, sizeof(*uids)*(numuids+1))) == NULL){
		*err = Enomem;
		return NULL;
	}
	uids = newuids;
	uids[numuids].id = uid;
	uids[numuids].name = strdup(p->pw_name);

	return uids[numuids++].name;
}

static char *
gid2str(gid_t gid, char **err)
{
	struct group *g;
	Id *newgids;
	int i;

	for(i = 0; i < numgids; i++){
		if(gids[i].id == gid)
			return gids[i].name;
	}
	if((g = getgrgid(gid)) == NULL){
		*err = strerror(errno);
		return NULL;
	}
	if((newgids = realloc(gids, sizeof(*gids)*(numgids+1))) == NULL){
		*err = Enomem;
		return NULL;
	}
	gids = newgids;
	gids[numgids].id = gid;
	gids[numgids].name = strdup(g->gr_name);

	return gids[numgids++].name;
}

static int
stat2c9stat(char *name, struct stat *st, C9stat *stout, char **err)
{
	int fmt;

	memset(stout, 0, sizeof(*stout));
	stout->size = st->st_size;
	stat2qid(st, &stout->qid, NULL);
	stout->name = name;
	stout->atime = st->st_atim.tv_sec;
	stout->mtime = st->st_ctim.tv_sec;

	fmt = st->st_mode & S_IFMT;
	if(fmt == S_IFDIR)
		stout->mode |= C9stdir;
	if(fmt == S_IFCHR || fmt == S_IFCHR || fmt == S_IFSOCK || fmt == S_IFIFO)
		stout->mode |= C9stappend;
	stout->mode |= st->st_mode & 0x1ff;
	if((stout->uid = uid2str(st->st_uid, err)) == NULL)
		return -1;
	if((stout->gid = gid2str(st->st_gid, err)) == NULL)
		return -1;

	return 0;
}

static int
statfid(Fid *f, C9stat *stout, char **err)
{
	struct stat st;
	int r;

	if(f->fd >= 0)
		r = fstat(f->fd, &st);
	else
		r = stat(f->path, &st);
	if(r != 0){
		*err = strerror(errno);
		return -1;
	}

	return stat2c9stat(f->name, &st, stout, err);
}

static uint8_t *
ctxread(C9ctx *c, uint32_t size, int *err)
{
	uint32_t n;
	int r;

	used(c);
	*err = 0;
	for(n = 0; n < size; n += r){
		errno = 0;
		if((r = read(in, rdbuf+n, size-n)) <= 0){
			if(r == EINTR)
				continue;
			if(r == 0){
				eof = 1;
			}else{
				*err = 1;
				perror("ctxread");
			}
			return NULL;
		}
	}

	return rdbuf;
}

static uint8_t *
ctxbegin(C9ctx *c, uint32_t size)
{
	uint8_t *b;

	used(c);
	if(wroff + size > wrbufsz){
		if(wrsend() != 0 || wroff + size > wrbufsz)
			return NULL;
	}
	b = wrbuf + wroff;
	wroff += size;

	return b;
}

static int
ctxend(C9ctx *c)
{
	used(c);
	wrend = wroff;
	return 0;
}

__attribute__((format(printf, 1, 2)))
static void
ctxerror(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

static int
s9do(C9error e, char **err)
{
	if(e == 0){
		*err = NULL;
		return 0;
	}

	switch(e){
	case C9Einit: *err = "c9: initialization failed"; break;
	case C9Ever: *err = "c9: protocol version doesn't match"; break;
	case C9Epkt: *err = "c9: incoming packet error"; break;
	case C9Etag: *err = "c9: no free tags or bad tag"; break;
	case C9Ebuf: *err = Enomem; break;
	case C9Epath: *err = "c9: path is too long or just invalid"; break;
	case C9Eflush: *err = "c9: limit of outstanding flushes reached"; break;
	case C9Esize: *err = "c9: can't fit data in one message"; break;
	case C9Estr: *err = "c9: bad string"; break;
	default: *err = "c9: unknown error"; break;
	}

	return -1;
}

static int
readf(C9ctx *c, C9tag tag, Fid *f, uint64_t offset, uint32_t size, char **err)
{
	struct stat st;
	void *p;
	struct dirent *e;
	ssize_t n;
	C9stat c9st[16], *c9stp[16];
	long dirpos[16];
	int i, num, res;

	if(size > c->msize - 12) /* make sure it fits */
		size = c->msize - 12;

	if(f->dir == NULL){ /* a file */
		if((p = malloc(size)) == NULL){
			*err = Enomem;
			return -1;
		}
		if((n = pread(f->fd, p, size, offset)) < 0){
			*err = strerror(errno);
			free(p);
			return -1;
		}
		res = s9do(s9read(c, tag, p, n), err);
		free(p);
		if(res != 0)
			return -1;
		trace("<- Rread tag=%d count=%zd data=...\n", tag, n);
		return 0;
	}

	/* dir */
	if(offset != f->diroffset){
		if(offset == 0){
			rewinddir(f->dir);
			f->diroffset = 0;
		}else{
			*err = Ebadoffset;
			return -1;
		}
	}

	res = 0;
	num = 0;
	for(i = 0; i < nelem(c9st); i++){
		/* so we can rewind in case another stat doesn't fit */
		dirpos[i] = telldir(f->dir);

		errno = 0;
		if((e = readdir(f->dir)) == NULL && errno != 0){
			*err = strerror(errno);
			res = -1;
			goto done;
		}
		if(e == NULL) /* eof */
			break;
		if(e->d_name[0] == '.' &&
		   (e->d_name[1] == 0 || ((e->d_name[1] == '.' && e->d_name[2] == 0))))
			continue;

		if(fstatat(f->fd, e->d_name, &st, 0) != 0){
			*err = strerror(errno);
			if(fstatat(f->fd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0){
				/* broken symlink, try to stat the link itself */
				res = -1;
				goto done;
			}
		}
		if(stat2c9stat(e->d_name, &st, &c9st[i], err) != 0){
			res = -1;
			goto done;
		}
		c9st[i].name = strdup(c9st[i].name);
		c9stp[num++] = &c9st[i];
	}

	i = num;
	if(s9do(s9readdir(c, tag, c9stp, &num, &f->diroffset, size), err) != 0){
		res = -1;
		goto done;
	}
	trace("<- Rread tag=%d count=%"PRIu64" data=...\n", tag, f->diroffset - offset);
	if(i != num)
		seekdir(f->dir, dirpos[num]);

done:
	for(i = 0; i < num; i++)
		free(c9stp[i]->name);
	return res;
}

static void
ctxt(C9ctx *c, C9t *t)
{
	Fid *f;
	C9qid *qids[C9maxpathel+1];
	char *err, *err2;
	C9stat st;
	int i;

	trace("-> %s tag=%d", t2s[t->type-Tversion], t->tag);

	err = NULL;
	if(hastag(t->tag)){
		err = Eduptag;
	}else{
		switch(t->type){
		case Tversion:
			trace(" msize=%d\n", t->version.msize);
			if(s9do(s9version(c), &err) == 0)
				trace("<- Rversion msize=%d\n", c->msize);
			break;
		case Tauth:
			trace(" afid=%d uname=\"%s\" aname=\"%s\"\n",
			      t->auth.afid, t->auth.uname, t->auth.aname);
			err = Enoauth;
			break;
		case Tattach:
			trace(" afid=%d fid=%d uname=\"%s\" aname=\"%s\"\n",
			      t->attach.afid, t->fid, t->attach.uname, t->attach.aname);
			if(t->attach.afid != C9nofid){
				err = Eunknownfid;
			}else if((f = newfid(t->fid, rootpath, &err)) != NULL){
				f->name = "/";
				if(s9do(s9attach(c, t->tag, &f->qid), &err) == 0)
					trace("<- Rattach\n");
			}
			break;
		case Tflush:
			trace(" oldtag=%d\n", t->flush.oldtag);
			/* FIXME flush it for realz */
			if(s9do(s9flush(c, t->tag), &err) == 0)
				trace("<- Rflush tag=%d\n", t->tag);
			break;
		case Twalk:
			trace(" fid=%d newfid=%d", t->fid, t->walk.newfid);
			for(i = 0; t->walk.wname[i] != NULL; i++)
				trace(" \"%s\"", t->walk.wname[i]);
			trace("\n");
			walk(t->fid, t->walk.newfid, t->walk.wname, qids, &err);
			if(err == NULL && s9do(s9walk(c, t->tag, qids), &err) == 0){
				trace("<- Rwalk tag=%d ", t->tag);
				for(i = 0; qids[i] != NULL; i++)
					trace("qid=[path=%"PRIu64" type=0x%02x version=%"PRIu32"] ",
					      qids[i]->path, qids[i]->type, qids[i]->version);
				trace("\n");
			}
			break;
		case Topen:
			trace(" fid=%d mode=0x%02x\n", t->fid, t->open.mode);
			if((f = findfid(t->fid, &err)) != NULL){
				if(f->fd >= 0)
					err = Ebotch;
				else if(t->open.mode != C9read && t->open.mode != C9exec)
					err = Eperm;
				else if(t->open.mode != C9read && (f->qid.type & C9qtdir) != 0)
					err = Eisdir;
				else if(openfid(f, t->open.mode, &err) == 0 &&
				        s9do(s9open(c, t->tag, &f->qid, f->iounit), &err) == 0){
					trace("<- Ropen tag=%d qid=[path=%"PRIu64" type=0x%02x version=%"PRIu32"] iounit=%d\n",
					      t->tag, f->qid.path, f->qid.type, f->qid.version, f->iounit);
				}
			}
			break;
		case Tcreate:
			trace("...\n");
			err = Enocreate;
			break;
		case Tread:
			trace(" fid=%d offset=%"PRIu64" count=%"PRIu32"\n", t->fid, t->read.offset, t->read.size);
			if((f = findfid(t->fid, &err)) != NULL){
				if((f->dir == NULL && f->fd < 0) || (f->mode & 0xf) == C9write)
					err = Ebotch;
				else if(readf(c, t->tag, f, t->read.offset, t->read.size, &err) != 0)
					trace("readf failed\n");
			}
			break;
		case Twrite:
			trace("...\n");
			err = Enowrite;
			break;
		case Tclunk:
			trace(" fid=%d\n", t->fid);
			if(delfid(t->fid, &err) == 0 && s9do(s9clunk(c, t->tag), &err) == 0)
				trace("<- Rclunk tag=%d\n", t->tag);
			break;
		case Tremove:
			trace("\n");
			err = Eperm;
			break;
		case Tstat:
			trace(" fid=%d\n", t->fid);
			if((f = findfid(t->fid, &err)) != NULL &&
			   statfid(f, &st, &err) == 0 &&\
			   s9do(s9stat(c, t->tag, &st), &err) == 0){
				trace("<- Rstat tag=%d ...\n", t->tag);
			}
			break;
		case Twstat:
			trace("...\n");
			err = Enowstat;
			break;
		}
	}

	if(err != NULL){
		if(s9do(s9error(c, t->tag, err), &err2) == 0)
			trace("<- Rerror tag=%d \"%s\"\n", t->tag, err);
		else
			fprintf(stderr, "s9error: %s\n", err2);
	}
}

static void
sigdebug(int s)
{
	Fid *f;
	int i, n;

	used(s);
	n = 0;
	for(i = 0; i < numfids; i++){
		f = fids[i];
		if(f == NULL)
			continue;

		fprintf(stderr, "fid %u ", f->fid);

		if(f->dir != NULL)
			fprintf(stderr, "open mode=dir ");
		else if(f->fd >= 0){
			fprintf(stderr, "open mode=%s%s%s ",
			        modes[(f->mode & 0xf)],
			        (f->mode & C9trunc) ? ",trunc" : "",
			        (f->mode & C9rclose) ? ",rclose" : "");
		}

		fprintf(stderr, "qid=[path=%"PRIu64" type=0x%02x version=%"PRIu32"] iounit=%d ",
		        f->qid.path,
		        f->qid.type,
		        f->qid.version,
		        f->iounit);
		fprintf(stderr, " %s %s\n", f->path, f->name);
		n++;
	}

	fprintf(stderr, "fids\t%d\n", n);
	fprintf(stderr, "tags\t%d\n", numtags);
	fprintf(stderr, "uids\t%d\n", numuids);
	fprintf(stderr, "gids\t%d\n", numgids);
}

int
main(int argc, char **argv)
{
	const char *dir;
	char *err;
	Fid *f;
	struct parg_state ps;
	struct sigaction sa;
	int can, i, c, rdonly, block;

	parg_init(&ps);

	debug = 0;
	dir = NULL;
	while((c = parg_getopt(&ps, argc, argv, "deh")) >= 0){
		switch(c){
		case 1:
			if(dir != NULL){
				fprintf(stderr, "only one dir can be specified\n");
				return 1;
			}
			dir = ps.optarg;
			break;
		case 'e':
			rootescape++;
			break;
		case 'd':
			debug++;
			break;
		case 'h':
			fprintf(stderr, "usage: 9pex [-e] [-d] DIR\n");
			return 0;
			break;
		case '?':
			fprintf(stderr, "unknown option -%c\n", ps.optopt);
			return 1;
			break;
		default:
			fprintf(stderr, "unhandled option -%c\n", c);
			return 1;
			break;
		}
	}

	if(dir == NULL){
		fprintf(stderr, "no dir specified\n");
		return 1;
	}

	if((rootpath = realpath(dir, NULL)) == NULL){
		trace("%s: %s\n", dir, strerror(errno));
		return 1;
	}
	rootlen = strlen(rootpath);

	in = 0;
	out = 1;
	eof = 0;
	fids = NULL;
	numfids = 0;
	tags = NULL;
	numtags = 0;
	uids = NULL;
	numuids = 0;
	gids = NULL;
	numgids = 0;

	memset(&ctx, 0, sizeof(ctx));
	ctx.msize = 64*1024;
	ctx.read = ctxread;
	ctx.begin = ctxbegin;
	ctx.end = ctxend;
	ctx.t = ctxt;
	ctx.error = ctxerror;

	rdbuf = calloc(1, ctx.msize);
	wrbufsz = ctx.msize;
	wrbuf = calloc(1, wrbufsz);
	wroff = wrend = 0;

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = SA_RESTART;
	sigaction(SIGPIPE, &sa, NULL);

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sigdebug;
	sa.sa_flags = SA_RESTART;
	sigfillset(&sa.sa_mask);
	sigaction(SIGUSR1, &sa, NULL);

	err = NULL;
	rdonly = block = 1; /* at first we wait until the client sends in data */
	for(; !eof;){
		if((can = canrw(rdonly, block)) < 0)
			break;
		if((can & Canrd) != 0){ /* if there is data, process it */
			if(s9do(s9proc(&ctx), &err) != 0)
				break;
			/* give it a chance to receive all the data first */
			rdonly = 1;
			block = 0;
		}else if(block == 0){ /* got all the data */
			if(rdonly != 0){ /* wait until we can send OR we get more data */
				rdonly = 0;
				block = 1;
			}
		}else if(rdonly == 0 && (can & Canwr) != 0){ /* can send */
			if(wrsend() != 0) /* send all the data */
				break;
			rdonly = 1; /* and go back to reading */
			block = 1;
		}
	}

	if(err != NULL)
		trace("s9proc: %s\n", err);

	for(i = 0; i < numfids; i++){
		if((f = fids[i]) != NULL){
			if(f->dir != NULL)
				closedir(f->dir);
			else if(f->fd >= 0)
				close(f->fd);
			free(f->path);
			free(f);
		}
	}

	for(i = 0; i < numuids; i++)
		free(uids[i].name);
	free(uids);
	for(i = 0; i < numgids; i++)
		free(gids[i].name);
	free(gids);

	memset(wrbuf, 0, ctx.msize);
	free(wrbuf);
	memset(rdbuf, 0, ctx.msize);
	free(rdbuf);
	free(fids);
	free(rootpath);

	return 0;
}