]
.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.
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
+/*
+ * 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);
+}
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-</$objtype/mkfile
-
-TARG=iostats
-OFILES=iostats.$O\
- statsrv.$O\
-
-HFILES=statfs.h\
-
-BIN=/$objtype/bin
-</sys/src/cmd/mkone
-
+++ /dev/null
-/*
- * 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*);
+++ /dev/null
-#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);
-}