shithub: riscv

Download patch

ref: 23aaa0c59cb0bf3e82e587571ee70d2c5c9e410b
parent: d14b6a0bf98306a6ec1b9181e998ef672db5a206
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Fri Aug 1 22:30:19 EDT 2014

iostats: reimplement iostats as a 9p filter instead of duplicating exportfs

old iostats failed to work when builidng the kernel due to old bugs
that where already fixed in exportfs. instead of backporting the fixes,
reimplement iostats as a filter that sits between exportfs and the
process mount. from users perspective, theres no difference.

the result is much smaller and can handle everything that exportfs
can like /srv.

--- a/sys/man/4/iostats
+++ b/sys/man/4/iostats
@@ -17,7 +17,7 @@
 ]
 .SH DESCRIPTION
 .I Iostats
-is a user-level file server that interposes itself between a program
+is a user-level 9p filter that interposes itself between a program
 and the regular file server, which
 allows it to gather statistics of file system
 use at the level of the Plan 9 file system protocol, 9P.
@@ -76,9 +76,10 @@
 iostats -df /fd/1 rc
 .EE
 .SH SOURCE
-.B /sys/src/cmd/iostats
+.B /sys/src/cmd/iostats.c
 .SH SEE ALSO
-.IR dup (3)
+.IR dup (3),
+.IR exportfs (4)
 .SH BUGS
 Poor clock resolution means that large amounts of I/O must be done to
 get accurate rate figures.
--- /dev/null
+++ b/sys/src/cmd/iostats.c
@@ -1,0 +1,534 @@
+/*
+ * iostats - Gather file system information
+ */
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+
+#define DEBUGFILE	"iostats.out"
+#define DONESTR		"done"
+
+enum{
+	Maxfile		= 1000,	/* number of Files we'll log */
+	Maxrpc		= 20000,/* number of RPCs we'll log */
+};
+
+typedef struct File File;
+typedef struct Fid Fid;
+typedef struct Req Req;
+typedef struct Rpc Rpc;
+typedef struct Stats Stats;
+
+/* per file statistics */
+struct File
+{
+	Qid	qid;
+	char	*path;
+
+	ulong	nopen;
+
+	ulong	nread;
+	vlong	bread;
+
+	ulong	nwrite;
+	vlong	bwrite;
+};
+
+/* fid context */
+struct Fid
+{
+	int	fid;
+	Qid	qid;
+	char	*path;
+	File	*file;	/* set on open/create */
+	Fid	*next;
+};
+
+/* a request */
+struct Req
+{
+	Req	*next;
+	vlong	t;
+
+	Fcall	f;
+	uchar	buf[];
+};
+
+/* per rpc statistics */
+struct Rpc
+{
+	char	*name;
+	ulong	count;
+	vlong	time;
+	vlong	lo;
+	vlong	hi;
+	vlong	bin;
+	vlong	bout;
+};
+
+/* all the statistics */
+struct Stats
+{
+	vlong	totread;
+	vlong	totwrite;
+	ulong	nrpc;
+	vlong	nproto;
+	Rpc	rpc[Maxrpc];
+	File	file[Maxfile];
+};
+
+Stats	stats[1];
+
+int pfd[2];
+int efd[2];
+int done;
+
+Lock rqlock;
+Req *rqhead;
+
+Fid *fidtab[1024];
+
+void
+catcher(void *a, char *msg)
+{
+	USED(a);
+
+	if(strcmp(msg, DONESTR) == 0) {
+		done = 1;
+		noted(NCONT);
+	}
+	if(strcmp(msg, "exit") == 0)
+		exits("exit");
+
+	noted(NDFLT);
+}
+
+void
+update(Rpc *rpc, vlong t)
+{
+	vlong t2;
+
+	t2 = nsec();
+	t = t2 - t;
+	if(t < 0)
+		t = 0;
+
+	rpc->time += t;
+	if(t < rpc->lo)
+		rpc->lo = t;
+	if(t > rpc->hi)
+		rpc->hi = t;
+}
+
+Fid**
+fidhash(int fid)
+{
+	return &fidtab[fid % nelem(fidtab)];
+}
+
+Fid*
+getfid(int fid, int new)
+{
+	Fid *f, **ff;
+
+	ff = fidhash(fid);
+	for(f = *ff; f != nil; f = f->next){
+		if(f->fid == fid)
+			return f;
+	}
+	if(new){
+		f = mallocz(sizeof(*f), 1);
+		f->fid = fid;
+		f->next = *ff;
+		*ff = f;
+	}
+	return f;
+}
+
+void
+setfid(Fid *f, char *path, Qid qid)
+{
+	if(path != f->path){
+		free(f->path);
+		f->path = path;
+	}
+	f->qid = qid;
+	f->file = nil;
+}
+
+void
+rattach(Fcall *fin, Fcall *fout)
+{
+	setfid(getfid(fin->fid, 1), strdup("/"), fout->qid);
+}
+
+void
+rwalk(Fcall *fin, Fcall *fout)
+{
+	Fid *of, *f;
+	int i;
+
+	if((of = getfid(fin->fid, 0)) == nil)
+		return;
+	f = getfid(fin->newfid, 1);
+	if(f != of)
+		setfid(f, strdup(of->path), of->qid);
+	for(i=0; i<fout->nwqid; i++)
+		setfid(f, cleanname(smprint("%s/%s", f->path, fin->wname[i])), fout->wqid[i]);
+}
+
+void
+ropen(Fcall *fin, Fcall *fout)
+{
+	File *fs;
+	Fid *f;
+
+	if((f = getfid(fin->fid, 0)) == nil)
+		return;
+	if(fin->type == Tcreate)
+		setfid(f, cleanname(smprint("%s/%s", f->path, fin->name)), fout->qid);
+	else
+		setfid(f, f->path, fout->qid);
+	for(fs = stats->file; fs < &stats->file[Maxfile]; fs++){
+		if(fs->nopen == 0){
+			fs->path = strdup(f->path);
+			fs->qid = f->qid;
+			f->file = fs;
+			break;
+		}
+		if(fs->qid.path == f->qid.path && strcmp(fs->path, f->path) == 0){
+			f->file = fs;
+			break;
+		}
+	}
+	if(f->file != nil)
+		f->file->nopen++;
+}
+
+void
+rclunk(Fcall *fin)
+{
+	Fid **ff, *f;
+
+	for(ff = fidhash(fin->fid); (f = *ff) != nil; ff = &f->next){
+		if(f->fid == fin->fid){
+			*ff = f->next;
+			free(f->path);
+			free(f);
+			return;
+		}
+	}
+}
+
+void
+rio(Fcall *fin, Fcall *fout)
+{
+	Fid *f;
+	int count;
+
+	count = fout->count;
+	if((f = getfid(fin->fid, 0)) == nil)
+		return;
+	switch(fout->type){
+	case Rread:
+		if(f->file != nil){
+			f->file->nread++;
+			f->file->bread += count;
+		}
+		stats->totread += count;
+		break;
+	case Rwrite:
+		if(f->file != nil){
+			f->file->nwrite++;
+			f->file->bwrite += count;
+		}
+		stats->totwrite += count;
+		break;
+	}
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: iostats [-d] [-f debugfile] cmds [args ...]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	Rpc *rpc;
+	ulong ttime;
+	char *dbfile;
+	char buf[64*1024];
+	float brpsec, bwpsec, bppsec;
+	int cpid, fspid, rspid, dbg, n, mflag;
+	File *fs;
+	Req *r, **rr;
+
+	dbg = 0;
+	mflag = MREPL;
+	dbfile = DEBUGFILE;
+
+	ARGBEGIN{
+	case 'd':
+		dbg++;
+		break;
+	case 'f':
+		dbfile = ARGF();
+		break;
+	case 'C':
+		mflag |= MCACHE;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	USED(dbfile);
+
+	if(argc == 0)
+		usage();
+
+	if(pipe(pfd) < 0)
+		sysfatal("pipe");
+
+	switch(cpid = fork()) {
+	case -1:
+		sysfatal("fork");
+	case 0:
+		close(pfd[1]);
+		if(getwd(buf, sizeof(buf)) == 0)
+			sysfatal("no working directory");
+
+		rfork(RFENVG|RFNAMEG|RFNOTEG);
+
+		if(mount(pfd[0], -1, "/", mflag, "") < 0)
+			sysfatal("mount /");
+
+		bind("#c/pid", "/dev/pid", MREPL);
+		bind("#c/ppid", "/dev/ppid", MREPL);
+		bind("#e", "/env", MREPL|MCREATE);
+		close(0);
+		close(1);
+		close(2);
+		open("/fd/0", OREAD);
+		open("/fd/1", OWRITE);
+		open("/fd/2", OWRITE);
+		if(chdir(buf) < 0)
+			sysfatal("chdir");
+		exec(argv[0], argv);
+		exec(smprint("/bin/%s", argv[0]), argv);
+		sysfatal("exec: %r");
+	default:
+		close(pfd[0]);
+	}
+
+	switch(fspid = fork()) {
+	default:
+		while(cpid != waitpid())
+			;
+		postnote(PNPROC, fspid, DONESTR);
+		while(fspid != waitpid())
+			;
+		exits(0);
+	case -1:
+		sysfatal("fork");
+	case 0:
+		notify(catcher);
+		break;
+	}
+
+	if(pipe(efd) < 0)
+		sysfatal("pipe");
+
+	/* spawn exportfs */
+	switch(fork()) {
+	default:
+		close(efd[0]);
+		break;
+	case -1:
+		sysfatal("fork");
+	case 0:
+		dup(efd[0], 0);
+		close(efd[0]);
+		close(efd[1]);
+		if(dbg){
+			execl("/bin/exportfs", "exportfs", "-df", dbfile, "-r", "/", nil);
+		} else {
+			execl("/bin/exportfs", "exportfs", "-r", "/", nil);
+		}
+		exits(0);
+	}
+
+	fmtinstall('F', fcallfmt);
+
+	stats->rpc[Tversion].name = "version";
+	stats->rpc[Tauth].name = "auth";
+	stats->rpc[Tflush].name = "flush";
+	stats->rpc[Tattach].name = "attach";
+	stats->rpc[Twalk].name = "walk";
+	stats->rpc[Topen].name = "open";
+	stats->rpc[Tcreate].name = "create";
+	stats->rpc[Tclunk].name = "clunk";
+	stats->rpc[Tread].name = "read";
+	stats->rpc[Twrite].name = "write";
+	stats->rpc[Tremove].name = "remove";
+	stats->rpc[Tstat].name = "stat";
+	stats->rpc[Twstat].name = "wstat";
+
+	for(n = 0; n < Maxrpc; n++)
+		stats->rpc[n].lo = 10000000000LL;
+
+	switch(rspid = rfork(RFPROC|RFMEM)) {
+	case 0:
+		/* read response from exportfs and pass to mount */
+		while(!done){
+			uchar tmp[sizeof(buf)];
+			Fcall f;
+
+			n = read(efd[1], buf, sizeof(buf));
+			if(n < 0)
+				break;
+			if(n == 0)
+				continue;
+
+			/* convert response */
+			memset(&f, 0, sizeof(f));
+			memmove(tmp, buf, n);
+			if(convM2S(tmp, n, &f) != n)
+				sysfatal("convM2S: %r");
+
+			/* find request to this response */
+			lock(&rqlock);
+			for(rr = &rqhead; (r = *rr) != nil; rr = &r->next){
+				if(r->f.tag == f.tag){
+					*rr = r->next;
+					r->next = nil;
+					break;
+				}
+			}
+			stats->nproto += n;
+			unlock(&rqlock);
+
+			switch(f.type){
+			case Ropen:
+			case Rcreate:
+				ropen(&r->f, &f);
+				break;
+			case Rclunk:
+			case Rremove:
+				rclunk(&r->f);
+				break;
+			case Rattach:
+				rattach(&r->f, &f);
+				break;
+			case Rwalk:
+				rwalk(&r->f, &f);
+				break;
+			case Rread:
+			case Rwrite:
+				rio(&r->f, &f);
+				break;
+			}
+
+			rpc = &stats->rpc[r->f.type];
+			update(rpc, r->t);
+			rpc->bout += n;
+			free(r);
+
+			if(write(pfd[1], buf, n) != n)
+				break;
+		}
+		exits(0);
+	default:
+		/* read request from mount and pass to exportfs */
+		while(!done){
+			n = read(pfd[1], buf, sizeof(buf));
+			if(n < 0)
+				break;
+			if(n == 0)
+				continue;
+
+			r = mallocz(sizeof(*r) + n, 1);
+			memmove(r->buf, buf, n);
+			if(convM2S(r->buf, n, &r->f) != n)
+				sysfatal("convM2S: %r");
+
+			rpc = &stats->rpc[r->f.type];
+			rpc->count++;
+			rpc->bin += n;
+
+			lock(&rqlock);
+			stats->nrpc++;
+			stats->nproto += n;
+			r->next = rqhead;
+			rqhead = r;
+			unlock(&rqlock);
+
+			r->t = nsec();
+
+			if(write(efd[1], buf, n) != n)
+				break;
+		}
+	}
+
+	/* shutdown */
+	done = 1;
+	postnote(PNPROC, rspid, DONESTR);
+	close(pfd[1]);
+	close(efd[1]);
+
+	/* dump statistics */
+	rpc = &stats->rpc[Tread];
+	brpsec = (double)stats->totread / (((float)rpc->time/1e9)+.000001);
+
+	rpc = &stats->rpc[Twrite];
+	bwpsec = (double)stats->totwrite / (((float)rpc->time/1e9)+.000001);
+
+	ttime = 0;
+	for(n = 0; n < Maxrpc; n++) {
+		rpc = &stats->rpc[n];
+		if(rpc->count == 0)
+			continue;
+		ttime += rpc->time;
+	}
+
+	bppsec = (double)stats->nproto / ((ttime/1e9)+.000001);
+
+	fprint(2, "\nread      %llud bytes, %g Kb/sec\n", stats->totread, brpsec/1024.0);
+	fprint(2, "write     %llud bytes, %g Kb/sec\n", stats->totwrite, bwpsec/1024.0);
+	fprint(2, "protocol  %llud bytes, %g Kb/sec\n", stats->nproto, bppsec/1024.0);
+	fprint(2, "rpc       %lud count\n\n", stats->nrpc);
+
+	fprint(2, "%-10s %5s %5s %5s %5s %5s          T       R\n", 
+	      "Message", "Count", "Low", "High", "Time", "Averg");
+
+	for(n = 0; n < Maxrpc; n++) {
+		rpc = &stats->rpc[n];
+		if(rpc->count == 0)
+			continue;
+		fprint(2, "%-10s %5lud %5llud %5llud %5llud %5llud ms %8llud %8llud bytes\n", 
+			rpc->name, 
+			rpc->count,
+			rpc->lo/1000000,
+			rpc->hi/1000000,
+			rpc->time/1000000,
+			rpc->time/1000000/rpc->count,
+			rpc->bin,
+			rpc->bout);
+	}
+
+	fprint(2, "\nOpens    Reads  (bytes)   Writes  (bytes) File\n");
+	for(fs = stats->file; fs < &stats->file[Maxfile]; fs++){
+		if(fs->nopen == 0)
+			break;
+		fprint(2, "%5lud %8lud %8llud %8lud %8llud %s\n",
+			fs->nopen,
+			fs->nread, fs->bread,
+			fs->nwrite, fs->bwrite,
+			fs->path);
+	}
+
+	exits(0);
+}
--- a/sys/src/cmd/iostats/iostats.c
+++ /dev/null
@@ -1,601 +1,0 @@
-/*
- * iostats - Gather file system information
- */
-#include <u.h>
-#include <libc.h>
-#include <auth.h>
-#include <fcall.h>
-#define Extern
-#include "statfs.h"
-
-void	runprog(char**);
-
-void (*fcalls[])(Fsrpc*) =
-{
-	[Tversion]	Xversion,
-	[Tauth]	Xauth,
-	[Tflush]	Xflush,
-	[Tattach]	Xattach,
-	[Twalk]		Xwalk,
-	[Topen]		slave,
-	[Tcreate]	Xcreate,
-	[Tclunk]	Xclunk,
-	[Tread]		slave,
-	[Twrite]	slave,
-	[Tremove]	Xremove,
-	[Tstat]		Xstat,
-	[Twstat]	Xwstat,
-};
-
-int p[2];
-
-void
-usage(void)
-{
-	fprint(2, "usage: iostats [-d] [-f debugfile] cmds [args ...]\n");
-	exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
-	Fsrpc *r;
-	Rpc *rpc;
-	Proc *m;
-	Frec *fr;
-	Fid *fid;
-	ulong ttime;
-	char *dbfile, *s;
-	char buf[128];
-	float brpsec, bwpsec, bppsec;
-	int type, cpid, fspid, n, mflag;
-
-	mflag = MREPL;
-	dbfile = DEBUGFILE;
-
-	ARGBEGIN{
-	case 'd':
-		dbg++;
-		break;
-	case 'f':
-		dbfile = ARGF();
-		break;
-	case 'C':
-		mflag |= MCACHE;
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	if(argc == 0)
-		usage();
-
-	if(dbg) {
-		close(2);
-		create(dbfile, OWRITE, 0666);
-	}
-
-	if(pipe(p) < 0)
-		fatal("pipe");
-
-	switch(cpid = fork()) {
-	case -1:
-		fatal("fork");
-	case 0:
-		close(p[1]);
-		if(getwd(buf, sizeof(buf)) == 0)
-			fatal("no working directory");
-
-		rfork(RFENVG|RFNAMEG|RFNOTEG);
-		if(mount(p[0], -1, "/", mflag, "") < 0)
-			fatal("mount /");
-
-		bind("#c/pid", "/dev/pid", MREPL);
-		bind("#e", "/env", MREPL|MCREATE);
-		close(0);
-		close(1);
-		close(2);
-		open("/fd/0", OREAD);
-		open("/fd/1", OWRITE);
-		open("/fd/2", OWRITE);
-
-		if(chdir(buf) < 0)
-			fatal("chdir");
-
-		runprog(argv);
-	default:
-		close(p[0]);
-	}
-
-	switch(fspid = fork()) {
-	default:
-		while(cpid != waitpid())
-			;
-		postnote(PNPROC, fspid, DONESTR);
-		while(fspid != waitpid())
-			;
-		exits(0);
-	case -1:
-		fatal("fork");
-	case 0:
-		break;
-	}
-
-	/* Allocate work queues in shared memory */
-	malloc(Dsegpad);
-	Workq = malloc(sizeof(Fsrpc)*Nr_workbufs);
-	stats = malloc(sizeof(Stats));
-	fhash = mallocz(sizeof(Fid*)*FHASHSIZE, 1);
-
-	if(Workq == 0 || fhash == 0 || stats == 0)
-		fatal("no initial memory");
-
-	memset(Workq, 0, sizeof(Fsrpc)*Nr_workbufs);
-	memset(stats, 0, sizeof(Stats));
-
-	stats->rpc[Tversion].name = "version";
-	stats->rpc[Tauth].name = "auth";
-	stats->rpc[Tflush].name = "flush";
-	stats->rpc[Tattach].name = "attach";
-	stats->rpc[Twalk].name = "walk";
-	stats->rpc[Topen].name = "open";
-	stats->rpc[Tcreate].name = "create";
-	stats->rpc[Tclunk].name = "clunk";
-	stats->rpc[Tread].name = "read";
-	stats->rpc[Twrite].name = "write";
-	stats->rpc[Tremove].name = "remove";
-	stats->rpc[Tstat].name = "stat";
-	stats->rpc[Twstat].name = "wstat";
-
-	for(n = 0; n < Maxrpc; n++)
-		stats->rpc[n].lo = 10000000000LL;
-
-	fmtinstall('M', dirmodefmt);
-	fmtinstall('D', dirfmt);
-	fmtinstall('F', fcallfmt);
-
-	if(chdir("/") < 0)
-		fatal("chdir");
-
-	initroot();
-
-	DEBUG(2, "statfs: %s\n", buf);
-
-	notify(catcher);
-
-	for(;;) {
-		r = getsbuf();
-		if(r == 0)
-			fatal("Out of service buffers");
-
-		while((n = read9pmsg(p[1], r->buf, sizeof(r->buf))) == 0 && !done)
-			;
-		if(done || n < 0)
-			break;
-
-		if(convM2S(r->buf, n, &r->work) == 0)
-			fatal("format error");
-
-		stats->nrpc++;
-		stats->nproto += n;
-
-		DEBUG(2, "%F\n", &r->work);
-
-		type = r->work.type;
-		rpc = &stats->rpc[type];
-		rpc->count++;
-		rpc->bin += n;
-		(fcalls[type])(r);
-	}
-
-	/* Clear away the slave children */
-	for(m = Proclist; m; m = m->next)
-		postnote(PNPROC, m->pid, "kill");
-
-	rpc = &stats->rpc[Tread];
-	brpsec = (float)stats->totread / (((float)rpc->time/1e9)+.000001);
-
-	rpc = &stats->rpc[Twrite];
-	bwpsec = (float)stats->totwrite / (((float)rpc->time/1e9)+.000001);
-
-	ttime = 0;
-	for(n = 0; n < Maxrpc; n++) {
-		rpc = &stats->rpc[n];
-		if(rpc->count == 0)
-			continue;
-		ttime += rpc->time;
-	}
-
-	bppsec = (float)stats->nproto / ((ttime/1e9)+.000001);
-
-	fprint(2, "\nread      %lud bytes, %g Kb/sec\n", stats->totread, brpsec/1024.0);
-	fprint(2, "write     %lud bytes, %g Kb/sec\n", stats->totwrite, bwpsec/1024.0);
-	fprint(2, "protocol  %lud bytes, %g Kb/sec\n", stats->nproto, bppsec/1024.0);
-	fprint(2, "rpc       %lud count\n\n", stats->nrpc);
-
-	fprint(2, "%-10s %5s %5s %5s %5s %5s          T       R\n", 
-	      "Message", "Count", "Low", "High", "Time", "Averg");
-
-	for(n = 0; n < Maxrpc; n++) {
-		rpc = &stats->rpc[n];
-		if(rpc->count == 0)
-			continue;
-		fprint(2, "%-10s %5lud %5llud %5llud %5llud %5llud ms %8lud %8lud bytes\n", 
-			rpc->name, 
-			rpc->count,
-			rpc->lo/1000000,
-			rpc->hi/1000000,
-			rpc->time/1000000,
-			rpc->time/1000000/rpc->count,
-			rpc->bin,
-			rpc->bout);
-	}
-
-	for(n = 0; n < FHASHSIZE; n++)
-		for(fid = fhash[n]; fid; fid = fid->next)
-			if(fid->nread || fid->nwrite)
-				fidreport(fid);
-	if(frhead == 0)
-		exits(0);
-
-	fprint(2, "\nOpens    Reads  (bytes)   Writes  (bytes) File\n");
-	for(fr = frhead; fr; fr = fr->next) {
-		s = fr->op;
-		if(*s) {
-			if(strcmp(s, "/fd/0") == 0)
-				s = "(stdin)";
-			else
-			if(strcmp(s, "/fd/1") == 0)
-				s = "(stdout)";
-			else
-			if(strcmp(s, "/fd/2") == 0)
-				s = "(stderr)";
-		}
-		else
-			s = "/.";
-
-		fprint(2, "%5lud %8lud %8lud %8lud %8lud %s\n", fr->opens, fr->nread, fr->bread,
-							fr->nwrite, fr->bwrite, s);
-	}
-
-	exits(0);
-}
-
-void
-reply(Fcall *r, Fcall *t, char *err)
-{
-	uchar data[IOHDRSZ+Maxfdata];
-	int n;
-
-	t->tag = r->tag;
-	t->fid = r->fid;
-	if(err) {
-		t->type = Rerror;
-		t->ename = err;
-	}
-	else 
-		t->type = r->type + 1;
-
-	DEBUG(2, "\t%F\n", t);
-
-	n = convS2M(t, data, sizeof data);
-	if(write(p[1], data, n)!=n)
-		fatal("mount write");
-	stats->nproto += n;
-	stats->rpc[t->type-1].bout += n;
-}
-
-Fid *
-getfid(int nr)
-{
-	Fid *f;
-
-	for(f = fidhash(nr); f; f = f->next)
-		if(f->nr == nr)
-			return f;
-
-	return 0;
-}
-
-int
-freefid(int nr)
-{
-	Fid *f, **l;
-
-	l = &fidhash(nr);
-	for(f = *l; f; f = f->next) {
-		if(f->nr == nr) {
-			*l = f->next;
-			f->next = fidfree;
-			fidfree = f;
-			return 1;
-		}
-		l = &f->next;
-	}
-
-	return 0;	
-}
-
-Fid *
-newfid(int nr)
-{
-	Fid *new, **l;
-	int i;
-
-	l = &fidhash(nr);
-	for(new = *l; new; new = new->next)
-		if(new->nr == nr)
-			return 0;
-
-	if(fidfree == 0) {
-		fidfree = mallocz(sizeof(Fid) * Fidchunk, 1);
-		if(fidfree == 0)
-			fatal("out of memory");
-
-		for(i = 0; i < Fidchunk-1; i++)
-			fidfree[i].next = &fidfree[i+1];
-
-		fidfree[Fidchunk-1].next = 0;
-	}
-
-	new = fidfree;
-	fidfree = new->next;
-
-	memset(new, 0, sizeof(Fid));
-	new->next = *l;
-	*l = new;
-	new->nr = nr;
-	new->fid = -1;
-	new->nread = 0;
-	new->nwrite = 0;
-	new->bread = 0;
-	new->bwrite = 0;
-
-	return new;	
-}
-
-Fsrpc *
-getsbuf(void)
-{
-	static int ap;
-	int look;
-	Fsrpc *wb;
-
-	for(look = 0; look < Nr_workbufs; look++) {
-		if(++ap == Nr_workbufs)
-			ap = 0;
-		if(Workq[ap].busy == 0)
-			break;
-	}
-
-	if(look == Nr_workbufs)
-		fatal("No more work buffers");
-
-	wb = &Workq[ap];
-	wb->pid = 0;
-	wb->canint = 0;
-	wb->flushtag = NOTAG;
-	wb->busy = 1;
-
-	return wb;
-}
-
-char *
-strcatalloc(char *p, char *n)
-{
-	char *v;
-
-	v = realloc(p, strlen(p)+strlen(n)+1);
-	if(v == 0)
-		fatal("no memory");
-	strcat(v, n);
-	return v;
-}
-
-File *
-file(File *parent, char *name)
-{
-	char buf[128];
-	File *f, *new;
-	Dir *dir;
-
-	DEBUG(2, "\tfile: 0x%p %s name %s\n", parent, parent->name, name);
-
-	for(f = parent->child; f; f = f->childlist)
-		if(strcmp(name, f->name) == 0)
-			break;
-
-	if(f != nil && !f->inval)
-		return f;
-	makepath(buf, parent, name);
-	dir = dirstat(buf);
-	if(dir == nil)
-		return 0;
-	if(f != nil){
-		free(dir);
-		f->inval = 0;
-		return f;
-	}
-
-	new = malloc(sizeof(File));
-	if(new == 0)
-		fatal("no memory");
-
-	memset(new, 0, sizeof(File));
-	new->name = strdup(name);
-	if(new->name == nil)
-		fatal("can't strdup");
-	new->qid.type = dir->qid.type;
-	new->qid.vers = dir->qid.vers;
-	new->qid.path = ++qid;
-
-	new->parent = parent;
-	new->childlist = parent->child;
-	parent->child = new;
-
-	free(dir);
-	return new;
-}
-
-void
-initroot(void)
-{
-	Dir *dir;
-
-	root = malloc(sizeof(File));
-	if(root == 0)
-		fatal("no memory");
-
-	memset(root, 0, sizeof(File));
-	root->name = strdup("/");
-	if(root->name == nil)
-		fatal("can't strdup");
-	dir = dirstat(root->name);
-	if(dir == nil)
-		fatal("root stat");
-
-	root->qid.type = dir->qid.type;
-	root->qid.vers = dir->qid.vers;
-	root->qid.path = ++qid;
-	free(dir);
-}
-
-void
-makepath(char *as, File *p, char *name)
-{
-	char *c, *seg[100];
-	int i;
-	char *s;
-
-	seg[0] = name;
-	for(i = 1; i < 100 && p; i++, p = p->parent){
-		seg[i] = p->name;
-		if(strcmp(p->name, "/") == 0)
-			seg[i] = "";	/* will insert slash later */
-	}
-
-	s = as;
-	while(i--) {
-		for(c = seg[i]; *c; c++)
-			*s++ = *c;
-		*s++ = '/';
-	}
-	while(s[-1] == '/')
-		s--;
-	*s = '\0';
-	if(as == s)	/* empty string is root */
-		strcpy(as, "/");
-}
-
-void
-fatal(char *s)
-{
-	Proc *m;
-
-	fprint(2, "iostats: %s: %r\n", s);
-
-	/* Clear away the slave children */
-	for(m = Proclist; m; m = m->next)
-		postnote(PNPROC, m->pid, "exit");
-
-	exits("fatal");
-}
-
-char*
-rdenv(char *v, char **end)
-{
-	int fd, n;
-	char *buf;
-	Dir *d;
-	if((fd = open(v, OREAD)) == -1)
-		return nil;
-	d = dirfstat(fd);
-	if(d == nil || (buf = malloc(d->length + 1)) == nil)
-		return nil;
-	n = (int)d->length;
-	n = read(fd, buf, n);
-	close(fd);
-	if(n <= 0){
-		free(buf);
-		buf = nil;
-	}else{
-		if(buf[n-1] != '\0')
-			buf[n++] = '\0';
-		*end = &buf[n];
-	}
-	free(d);
-	return buf;
-}
-
-char Defaultpath[] = ".\0/bin";
-void
-runprog(char *argv[])
-{
-	char *path, *ep, *p;
-	char arg0[256];
-
-	path = rdenv("/env/path", &ep);
-	if(path == nil){
-		path = Defaultpath;
-		ep = path+sizeof(Defaultpath);
-	}
-	for(p = path; p < ep; p += strlen(p)+1){
-		snprint(arg0, sizeof arg0, "%s/%s", p, argv[0]);
-		exec(arg0, argv);
-	}
-	fatal("exec");
-}
-
-void
-catcher(void *a, char *msg)
-{
-	USED(a);
-	if(strcmp(msg, DONESTR) == 0) {
-		done = 1;
-		noted(NCONT);
-	}
-	if(strcmp(msg, "exit") == 0)
-		exits("exit");
-
-	noted(NDFLT);
-}
-
-void
-fidreport(Fid *f)
-{
-	char *p, path[128];
-	Frec *fr;
-
-	p = path;
-	makepath(p, f->f, "");
-
-	for(fr = frhead; fr; fr = fr->next) {
-		if(strcmp(fr->op, p) == 0) {
-			fr->nread += f->nread;
-			fr->nwrite += f->nwrite;
-			fr->bread += f->bread;
-			fr->bwrite += f->bwrite;
-			fr->opens++;
-			return;
-		}
-	}
-
-	fr = malloc(sizeof(Frec));
-	if(fr == 0 || (fr->op = strdup(p)) == 0)
-		fatal("no memory");
-
-	fr->nread = f->nread;
-	fr->nwrite = f->nwrite;
-	fr->bread = f->bread;
-	fr->bwrite = f->bwrite;
-	fr->opens = 1;
-	if(frhead == 0) {
-		frhead = fr;
-		frtail = fr;
-	}
-	else {
-		frtail->next = fr;
-		frtail = fr;
-	}
-	fr->next = 0;
-}
--- a/sys/src/cmd/iostats/mkfile
+++ /dev/null
@@ -1,11 +1,0 @@
-</$objtype/mkfile
-
-TARG=iostats
-OFILES=iostats.$O\
-	statsrv.$O\
-
-HFILES=statfs.h\
-
-BIN=/$objtype/bin
-</sys/src/cmd/mkone
-
--- a/sys/src/cmd/iostats/statfs.h
+++ /dev/null
@@ -1,142 +1,0 @@
-/*
- * statfs.h - definitions for statistic gathering file server
- */
-
-#define DEBUGFILE	"iostats.out"
-#define DONESTR		"done"
-#define DEBUG		if(!dbg){}else fprint
-#define MAXPROC		64
-#define FHASHSIZE	64
-#define fidhash(s)	fhash[s%FHASHSIZE]
-
-enum{
-	Maxfdata	= 8192,	/* max size of data in 9P message */
-	Maxrpc		= 20000,/* number of RPCs we'll log */
-};
-
-typedef struct Fsrpc Fsrpc;
-typedef struct Fid Fid;
-typedef struct File File;
-typedef struct Proc Proc;
-typedef struct Stats Stats;
-typedef struct Rpc Rpc;
-typedef struct Frec Frec;
-
-struct Frec
-{
-	Frec	*next;
-	char	*op;
-	ulong	nread;
-	ulong	nwrite;
-	ulong	bread;
-	ulong	bwrite;
-	ulong	opens;
-};
-
-struct Rpc
-{
-	char	*name;
-	ulong	count;
-	vlong	time;
-	vlong	lo;
-	vlong	hi;
-	ulong	bin;
-	ulong	bout;
-};
-
-struct Stats
-{
-	ulong	totread;
-	ulong	totwrite;
-	ulong	nrpc;
-	ulong	nproto;
-	Rpc	rpc[Maxrpc];
-};
-
-struct Fsrpc
-{
-	int	busy;			/* Work buffer has pending rpc to service */
-	uintptr	pid;			/* Pid of slave process executing the rpc */
-	int	canint;			/* Interrupt gate */
-	int	flushtag;		/* Tag on which to reply to flush */
-	Fcall	work;			/* Plan 9 incoming Fcall */
-	uchar	buf[IOHDRSZ+Maxfdata];	/* Data buffer */
-};
-
-struct Fid
-{
-	int	fid;			/* system fd for i/o */
-	File	*f;			/* File attached to this fid */
-	int	mode;
-	int	nr;			/* fid number */
-	Fid	*next;			/* hash link */
-	ulong	nread;
-	ulong	nwrite;
-	ulong	bread;
-	ulong	bwrite;
-	vlong	offset;			/* for directories */
-};
-
-struct File
-{
-	char	*name;
-	Qid	qid;
-	int	inval;
-	File	*parent;
-	File	*child;
-	File	*childlist;
-};
-
-struct Proc
-{
-	uintptr	pid;
-	int	busy;
-	Proc	*next;
-};
-
-enum
-{
-	Nr_workbufs 	= 40,
-	Dsegpad		= 8192,
-	Fidchunk	= 1000,
-};
-
-Extern Fsrpc	*Workq;
-Extern int  	dbg;
-Extern File	*root;
-Extern Fid	**fhash;
-Extern Fid	*fidfree;
-Extern int	qid;
-Extern Proc	*Proclist;
-Extern int	done;
-Extern Stats	*stats;
-Extern Frec	*frhead;
-Extern Frec	*frtail;
-Extern int	myiounit;
-
-/* File system protocol service procedures */
-void Xcreate(Fsrpc*), Xclunk(Fsrpc*); 
-void Xversion(Fsrpc*), Xauth(Fsrpc*), Xflush(Fsrpc*); 
-void Xattach(Fsrpc*), Xwalk(Fsrpc*), Xauth(Fsrpc*);
-void Xremove(Fsrpc*), Xstat(Fsrpc*), Xwstat(Fsrpc*);
-void slave(Fsrpc*);
-
-void	reply(Fcall*, Fcall*, char*);
-Fid 	*getfid(int);
-int	freefid(int);
-Fid	*newfid(int);
-Fsrpc	*getsbuf(void);
-void	initroot(void);
-void	fatal(char*);
-void	makepath(char*, File*, char*);
-File	*file(File*, char*);
-void	slaveopen(Fsrpc*);
-void	slaveread(Fsrpc*);
-void	slavewrite(Fsrpc*);
-void	blockingslave(void);
-void	reopen(Fid *f);
-void	noteproc(int, char*);
-void	flushaction(void*, char*);
-void	catcher(void*, char*);
-ulong	msec(void);
-void	fidreport(Fid*);
--- a/sys/src/cmd/iostats/statsrv.c
+++ /dev/null
@@ -1,672 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <auth.h>
-#include <fcall.h>
-#define Extern	extern
-#include "statfs.h"
-
-char Ebadfid[]	= "Bad fid";
-char Enotdir[]	="Not a directory";
-char Edupfid[]	= "Fid already in use";
-char Eopen[]	= "Fid already opened";
-char Exmnt[]	= "Cannot .. past mount point";
-char Enoauth[]	= "iostats: Authentication failed";
-char Ebadver[]	= "Unrecognized 9P version";
-
-int
-okfile(char *s, int mode)
-{
-	if(strncmp(s, "/fd/", 3) == 0){
-		/* 0, 1, and 2 we handle ourselves */
-		if(s[4]=='/' || atoi(s+4) > 2)
-			return 0;
-		return 1;
-	}
-	if(strncmp(s, "/net/ssl", 8) == 0)
-		return 0;
-	if(strncmp(s, "/net/tls", 8) == 0)
-		return 0;
-	if(strncmp(s, "/srv/", 5) == 0 && ((mode&3) == OWRITE || (mode&3) == ORDWR))
-		return 0;
-	return 1;
-}
-
-void
-update(Rpc *rpc, vlong t)
-{
-	vlong t2;
-
-	t2 = nsec();
-	t = t2 - t;
-	if(t < 0)
-		t = 0;
-
-	rpc->time += t;
-	if(t < rpc->lo)
-		rpc->lo = t;
-	if(t > rpc->hi)
-		rpc->hi = t;
-}
-
-void
-Xversion(Fsrpc *r)
-{
-	Fcall thdr;
-	vlong t;
-
-	t = nsec();
-
-	if(r->work.msize > IOHDRSZ+Maxfdata)
-		thdr.msize = IOHDRSZ+Maxfdata;
-	else
-		thdr.msize = r->work.msize;
-	myiounit = thdr.msize - IOHDRSZ;
-	if(strncmp(r->work.version, "9P2000", 6) != 0){
-		reply(&r->work, &thdr, Ebadver);
-		r->busy = 0;
-		return;
-	}
-	thdr.version = "9P2000";
-	/* BUG: should clunk all fids */
-	reply(&r->work, &thdr, 0);
-	r->busy = 0;
-
-	update(&stats->rpc[Tversion], t);
-}
-
-void
-Xauth(Fsrpc *r)
-{
-	Fcall thdr;
-	vlong t;
-
-	t = nsec();
-
-	reply(&r->work, &thdr, Enoauth);
-	r->busy = 0;
-
-	update(&stats->rpc[Tauth], t);
-}
-
-void
-Xflush(Fsrpc *r)
-{
-	Fsrpc *t, *e;
-	Fcall thdr;
-
-	e = &Workq[Nr_workbufs];
-
-	for(t = Workq; t < e; t++) {
-		if(t->work.tag == r->work.oldtag) {
-			DEBUG(2, "\tQ busy %d pid %p can %d\n", t->busy, t->pid, t->canint);
-			if(t->busy && t->pid) {
-				t->flushtag = r->work.tag;
-				DEBUG(2, "\tset flushtag %d\n", r->work.tag);
-				if(t->canint)
-					postnote(PNPROC, t->pid, "flush");
-				r->busy = 0;
-				return;
-			}
-		}
-	}
-
-	reply(&r->work, &thdr, 0);
-	DEBUG(2, "\tflush reply\n");
-	r->busy = 0;
-}
-
-void
-Xattach(Fsrpc *r)
-{
-	Fcall thdr;
-	Fid *f;
-	vlong t;
-
-	t = nsec();
-
-	f = newfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-
-	f->f = root;
-	thdr.qid = f->f->qid;
-	reply(&r->work, &thdr, 0);
-	r->busy = 0;
-
-	update(&stats->rpc[Tattach], t);
-}
-
-void
-Xwalk(Fsrpc *r)
-{
-	char errbuf[ERRMAX], *err;
-	Fcall thdr;
-	Fid *f, *n;
-	File *nf;
-	vlong t;
-	int i;
-
-	t = nsec();
-
-	f = getfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-	n = nil;
-	if(r->work.newfid != r->work.fid){
-		n = newfid(r->work.newfid);
-		if(n == 0) {
-			reply(&r->work, &thdr, Edupfid);
-			r->busy = 0;
-			return;
-		}
-		n->f = f->f;
-		f = n;	/* walk new guy */
-	}
-
-	thdr.nwqid = 0;
-	err = nil;
-	for(i=0; i<r->work.nwname; i++){
-		if(i >= MAXWELEM)
-			break;
-		if(strcmp(r->work.wname[i], "..") == 0) {
-			if(f->f->parent == 0) {
-				err = Exmnt;
-				break;
-			}
-			f->f = f->f->parent;
-			thdr.wqid[thdr.nwqid++] = f->f->qid;
-			continue;
-		}
-	
-		nf = file(f->f, r->work.wname[i]);
-		if(nf == 0) {
-			errstr(errbuf, sizeof errbuf);
-			err = errbuf;
-			break;
-		}
-
-		f->f = nf;
-		thdr.wqid[thdr.nwqid++] = nf->qid;
-		continue;
-	}
-
-	if(err == nil && thdr.nwqid == 0 && r->work.nwname > 0)
-		err = "file does not exist";
-
-	if(n != nil && (err != 0 || thdr.nwqid < r->work.nwname)){
-		/* clunk the new fid, which is the one we walked */
-		freefid(n->nr);
-	}
-
-	if(thdr.nwqid > 0)
-		err = nil;
-	reply(&r->work, &thdr, err);
-	r->busy = 0;
-
-	update(&stats->rpc[Twalk], t);
-}
-
-void
-Xclunk(Fsrpc *r)
-{
-	Fcall thdr;
-	Fid *f;
-	vlong t;
-	int fid;
-
-	t = nsec();
-
-	f = getfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-
-	if(f->fid >= 0)
-		close(f->fid);
-
-	fid = r->work.fid;
-	reply(&r->work, &thdr, 0);
-	r->busy = 0;
-
-	update(&stats->rpc[Tclunk], t);
-
-	if(f->nread || f->nwrite)
-		fidreport(f);
-
-	freefid(fid);
-}
-
-void
-Xstat(Fsrpc *r)
-{
-	char err[ERRMAX], path[128];
-	uchar statbuf[STATMAX];
-	Fcall thdr;
-	Fid *f;
-	int s;
-	vlong t;
-
-	t = nsec();
-
-	f = getfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-	makepath(path, f->f, "");
-	if(!okfile(path, -1)){
-		snprint(err, sizeof err, "iostats: can't simulate %s", path);
-		reply(&r->work, &thdr, err);
-		r->busy = 0;
-		return;
-	}
-
-	if(f->fid >= 0)
-		s = fstat(f->fid, statbuf, sizeof statbuf);
-	else
-		s = stat(path, statbuf, sizeof statbuf);
-
-	if(s < 0) {
-		errstr(err, sizeof err);
-		reply(&r->work, &thdr, err);
-		r->busy = 0;
-		return;
-	}
-	thdr.stat = statbuf;
-	thdr.nstat = s;
-	reply(&r->work, &thdr, 0);
-	r->busy = 0;
-
-	update(&stats->rpc[Tstat], t);
-}
-
-void
-Xcreate(Fsrpc *r)
-{
-	char err[ERRMAX], path[128];
-	Fcall thdr;
-	Fid *f;
-	File *nf;
-	vlong t;
-
-	t = nsec();
-
-	f = getfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-	
-
-	makepath(path, f->f, r->work.name);
-	f->fid = create(path, r->work.mode, r->work.perm);
-	if(f->fid < 0) {
-		errstr(err, sizeof err);
-		reply(&r->work, &thdr, err);
-		r->busy = 0;
-		return;
-	}
-
-	nf = file(f->f, r->work.name);
-	if(nf == 0) {
-		errstr(err, sizeof err);
-		reply(&r->work, &thdr, err);
-		r->busy = 0;
-		return;
-	}
-
-	f->mode = r->work.mode;
-	f->f = nf;
-	thdr.iounit = myiounit;
-	thdr.qid = f->f->qid;
-	reply(&r->work, &thdr, 0);
-	r->busy = 0;
-
-	update(&stats->rpc[Tcreate], t);
-}
-
-
-void
-Xremove(Fsrpc *r)
-{
-	char err[ERRMAX], path[128];
-	Fcall thdr;
-	Fid *f;
-	vlong t;
-
-	t = nsec();
-
-	f = getfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-
-	makepath(path, f->f, "");
-	DEBUG(2, "\tremove: %s\n", path);
-	if(remove(path) < 0) {
-		errstr(err, sizeof err);
-		reply(&r->work, &thdr, err);
-		freefid(r->work.fid);
-		r->busy = 0;
-		return;
-	}
-
-	f->f->inval = 1;
-	if(f->fid >= 0)
-		close(f->fid);
-	freefid(r->work.fid);
-
-	reply(&r->work, &thdr, 0);
-	r->busy = 0;
-
-	update(&stats->rpc[Tremove], t);
-}
-
-void
-Xwstat(Fsrpc *r)
-{
-	char err[ERRMAX], path[128];
-	Fcall thdr;
-	Fid *f;
-	int s;
-	vlong t;
-
-	t = nsec();
-
-	f = getfid(r->work.fid);
-	if(f == 0) {
-		reply(&r->work, &thdr, Ebadfid);
-		r->busy = 0;
-		return;
-	}
-	if(f->fid >= 0)
-		s = fwstat(f->fid, r->work.stat, r->work.nstat);
-	else {
-		makepath(path, f->f, "");
-		s = wstat(path, r->work.stat, r->work.nstat);
-	}
-	if(s < 0) {
-		errstr(err, sizeof err);
-		reply(&r->work, &thdr, err);
-	}
-	else
-		reply(&r->work, &thdr, 0);
-
-	r->busy = 0;
-	update(&stats->rpc[Twstat], t);
-}
-
-void
-slave(Fsrpc *f)
-{
-	int r;
-	Proc *p;
-	uintptr pid;
-	static int nproc;
-
-	for(;;) {
-		for(p = Proclist; p; p = p->next) {
-			if(p->busy == 0) {
-				f->pid = p->pid;
-				p->busy = 1;
-				pid = (uintptr)rendezvous((void*)p->pid, f);
-				if(pid != p->pid)
-					fatal("rendezvous sync fail");
-				return;
-			}	
-		}
-
-		if(++nproc > MAXPROC)
-			fatal("too many procs");
-
-		r = rfork(RFPROC|RFMEM);
-		if(r < 0)
-			fatal("rfork");
-
-		if(r == 0)
-			blockingslave();
-
-		p = malloc(sizeof(Proc));
-		if(p == 0)
-			fatal("out of memory");
-
-		p->busy = 0;
-		p->pid = r;
-		p->next = Proclist;
-		Proclist = p;
-
-		rendezvous((void*)p->pid, p);
-	}
-}
-
-void
-blockingslave(void)
-{
-	Proc *m;
-	uintptr pid;
-	Fsrpc *p;
-	Fcall thdr;
-
-	notify(flushaction);
-
-	pid = getpid();
-
-	m = rendezvous((void*)pid, 0);
-		
-	for(;;) {
-		p = rendezvous((void*)pid, (void*)pid);
-		if(p == (void*)~0)			/* Interrupted */
-			continue;
-
-		DEBUG(2, "\tslave: %p %F b %d p %p\n", pid, &p->work, p->busy, p->pid);
-		if(p->flushtag != NOTAG)
-			return;
-
-		switch(p->work.type) {
-		case Tread:
-			slaveread(p);
-			break;
-		case Twrite:
-			slavewrite(p);
-			break;
-		case Topen:
-			slaveopen(p);
-			break;
-		default:
-			reply(&p->work, &thdr, "exportfs: slave type error");
-		}
-		if(p->flushtag != NOTAG) {
-			p->work.type = Tflush;
-			p->work.tag = p->flushtag;
-			reply(&p->work, &thdr, 0);
-		}
-		p->busy = 0;	
-		m->busy = 0;
-	}
-}
-
-void
-slaveopen(Fsrpc *p)
-{
-	char err[ERRMAX], path[128];
-	Fcall *work, thdr;
-	Fid *f;
-	vlong t;
-
-	work = &p->work;
-
-	t = nsec();
-
-	f = getfid(work->fid);
-	if(f == 0) {
-		reply(work, &thdr, Ebadfid);
-		return;
-	}
-	if(f->fid >= 0) {
-		close(f->fid);
-		f->fid = -1;
-	}
-	
-	makepath(path, f->f, "");
-	DEBUG(2, "\topen: %s %d\n", path, work->mode);
-
-	p->canint = 1;
-	if(p->flushtag != NOTAG)
-		return;
-
-	if(!okfile(path, work->mode)){
-		snprint(err, sizeof err, "iostats can't simulate %s", path);
-		reply(work, &thdr, err);
-		return;
-	}
-
-	/* There is a race here I ignore because there are no locks */
-	f->fid = open(path, work->mode);
-	p->canint = 0;
-	if(f->fid < 0) {
-		errstr(err, sizeof err);
-		reply(work, &thdr, err);
-		return;
-	}
-
-	DEBUG(2, "\topen: fd %d\n", f->fid);
-	f->mode = work->mode;
-	thdr.iounit = myiounit;
-	thdr.qid = f->f->qid;
-	reply(work, &thdr, 0);
-
-	update(&stats->rpc[Topen], t);
-}
-
-void
-slaveread(Fsrpc *p)
-{
-	char data[Maxfdata], err[ERRMAX];
-	Fcall *work, thdr;
-	Fid *f;
-	int n, r;
-	vlong t;
-
-	work = &p->work;
-
-	t = nsec();
-
-	f = getfid(work->fid);
-	if(f == 0) {
-		reply(work, &thdr, Ebadfid);
-		return;
-	}
-
-	n = (work->count > Maxfdata) ? Maxfdata : work->count;
-	p->canint = 1;
-	if(p->flushtag != NOTAG)
-		return;
-	/* can't just call pread, since directories must update the offset */
-	if(f->f->qid.type&QTDIR){
-		if(work->offset != f->offset){
-			if(work->offset != 0){
-				snprint(err, sizeof err, "can't seek in directory from %lld to %lld", f->offset, work->offset);
-				reply(work, &thdr, err);
-				return;
-			}
-			if(seek(f->fid, 0, 0) != 0){
-				errstr(err, sizeof err);
-				reply(work, &thdr, err);
-				return;	
-			}
-			f->offset = 0;
-		}
-		r = read(f->fid, data, n);
-		if(r > 0)
-			f->offset += r;
-	}else
-		r = pread(f->fid, data, n, work->offset);
-	p->canint = 0;
-	if(r < 0) {
-		errstr(err, sizeof err);
-		reply(work, &thdr, err);
-		return;
-	}
-
-	DEBUG(2, "\tread: fd=%d %d bytes\n", f->fid, r);
-
-	thdr.data = data;
-	thdr.count = r;
-	stats->totread += r;
-	f->nread++;
-	f->bread += r;
-	reply(work, &thdr, 0);
-
-	update(&stats->rpc[Tread], t);
-}
-
-void
-slavewrite(Fsrpc *p)
-{
-	char err[ERRMAX];
-	Fcall *work, thdr;
-	Fid *f;
-	int n;
-	vlong t;
-
-	work = &p->work;
-
-	t = nsec();
-
-	f = getfid(work->fid);
-	if(f == 0) {
-		reply(work, &thdr, Ebadfid);
-		return;
-	}
-
-	n = (work->count > Maxfdata) ? Maxfdata : work->count;
-	p->canint = 1;
-	if(p->flushtag != NOTAG)
-		return;
-	n = pwrite(f->fid, work->data, n, work->offset);
-	p->canint = 0;
-	if(n < 0) {
-		errstr(err, sizeof err);
-		reply(work, &thdr, err);
-		return;
-	}
-
-	DEBUG(2, "\twrite: %d bytes fd=%d\n", n, f->fid);
-
-	thdr.count = n;
-	f->nwrite++;
-	f->bwrite += n;
-	stats->totwrite += n;
-	reply(work, &thdr, 0);
-
-	update(&stats->rpc[Twrite], t);
-}
-
-void
-reopen(Fid *f)
-{
-	USED(f);
-	fatal("reopen");
-}
-
-void
-flushaction(void *a, char *cause)
-{
-	USED(a);
-	if(strncmp(cause, "kill", 4) == 0)
-		noted(NDFLT);
-
-	noted(NCONT);
-}