aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cmd/plumb/fsys.c975
-rw-r--r--src/cmd/plumb/match.c463
-rw-r--r--src/cmd/plumb/mkfile20
-rw-r--r--src/cmd/plumb/plumb.c119
-rw-r--r--src/cmd/plumb/plumber.c147
-rw-r--r--src/cmd/plumb/plumber.h93
-rw-r--r--src/cmd/plumb/rules.c779
7 files changed, 2596 insertions, 0 deletions
diff --git a/src/cmd/plumb/fsys.c b/src/cmd/plumb/fsys.c
new file mode 100644
index 00000000..6f95a23a
--- /dev/null
+++ b/src/cmd/plumb/fsys.c
@@ -0,0 +1,975 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <regexp.h>
+#include <thread.h>
+#include <auth.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "plumber.h"
+
+enum
+{
+ Stack = 8*1024
+};
+
+typedef struct Dirtab Dirtab;
+typedef struct Fid Fid;
+typedef struct Holdq Holdq;
+typedef struct Readreq Readreq;
+typedef struct Sendreq Sendreq;
+
+struct Dirtab
+{
+ char *name;
+ uchar type;
+ uint qid;
+ uint perm;
+ int nopen; /* #fids open on this port */
+ Fid *fopen;
+ Holdq *holdq;
+ Readreq *readq;
+ Sendreq *sendq;
+};
+
+struct Fid
+{
+ int fid;
+ int busy;
+ int open;
+ int mode;
+ Qid qid;
+ Dirtab *dir;
+ long offset; /* zeroed at beginning of each message, read or write */
+ char *writebuf; /* partial message written so far; offset tells how much */
+ Fid *next;
+ Fid *nextopen;
+};
+
+struct Readreq
+{
+ Fid *fid;
+ Fcall *fcall;
+ uchar *buf;
+ Readreq *next;
+};
+
+struct Sendreq
+{
+ int nfid; /* number of fids that should receive this message */
+ int nleft; /* number left that haven't received it */
+ Fid **fid; /* fid[nfid] */
+ Plumbmsg *msg;
+ char *pack; /* plumbpack()ed message */
+ int npack; /* length of pack */
+ Sendreq *next;
+};
+
+struct Holdq
+{
+ Plumbmsg *msg;
+ Holdq *next;
+};
+
+struct /* needed because incref() doesn't return value */
+{
+ Lock;
+ int ref;
+} rulesref;
+
+enum
+{
+ DEBUG = 0,
+ NDIR = 50,
+ Nhash = 16,
+
+ Qdir = 0,
+ Qrules = 1,
+ Qsend = 2,
+ Qport = 3,
+ NQID = Qport
+};
+
+static Dirtab dir[NDIR] =
+{
+ { ".", QTDIR, Qdir, 0500|DMDIR },
+ { "rules", QTFILE, Qrules, 0600 },
+ { "send", QTFILE, Qsend, 0200 },
+};
+static int ndir = NQID;
+
+static int srvfd;
+static int srvclosefd; /* rock for end of pipe to close */
+static int clockfd;
+static int clock;
+static Fid *fids[Nhash];
+static QLock readlock;
+static QLock queue;
+static char srvfile[128];
+static int messagesize = 8192+IOHDRSZ; /* good start */
+
+static void fsysproc(void*);
+static void fsysrespond(Fcall*, uchar*, char*);
+static Fid* newfid(int);
+
+static Fcall* fsysflush(Fcall*, uchar*, Fid*);
+static Fcall* fsysversion(Fcall*, uchar*, Fid*);
+static Fcall* fsysauth(Fcall*, uchar*, Fid*);
+static Fcall* fsysattach(Fcall*, uchar*, Fid*);
+static Fcall* fsyswalk(Fcall*, uchar*, Fid*);
+static Fcall* fsysopen(Fcall*, uchar*, Fid*);
+static Fcall* fsyscreate(Fcall*, uchar*, Fid*);
+static Fcall* fsysread(Fcall*, uchar*, Fid*);
+static Fcall* fsyswrite(Fcall*, uchar*, Fid*);
+static Fcall* fsysclunk(Fcall*, uchar*, Fid*);
+static Fcall* fsysremove(Fcall*, uchar*, Fid*);
+static Fcall* fsysstat(Fcall*, uchar*, Fid*);
+static Fcall* fsyswstat(Fcall*, uchar*, Fid*);
+
+Fcall* (*fcall[Tmax])(Fcall*, uchar*, Fid*) =
+{
+ [Tflush] = fsysflush,
+ [Tversion] = fsysversion,
+ [Tauth] = fsysauth,
+ [Tattach] = fsysattach,
+ [Twalk] = fsyswalk,
+ [Topen] = fsysopen,
+ [Tcreate] = fsyscreate,
+ [Tread] = fsysread,
+ [Twrite] = fsyswrite,
+ [Tclunk] = fsysclunk,
+ [Tremove]= fsysremove,
+ [Tstat] = fsysstat,
+ [Twstat] = fsyswstat,
+};
+
+char Ebadfcall[] = "bad fcall type";
+char Eperm[] = "permission denied";
+char Enomem[] = "malloc failed for buffer";
+char Enotdir[] = "not a directory";
+char Enoexist[] = "plumb file does not exist";
+char Eisdir[] = "file is a directory";
+char Ebadmsg[] = "bad plumb message format";
+char Enosuchport[] ="no such plumb port";
+char Enoport[] = "couldn't find destination for message";
+char Einuse[] = "file already open";
+
+/*
+ * Add new port. A no-op if port already exists or is the null string
+ */
+void
+addport(char *port)
+{
+ int i;
+
+ if(port == nil)
+ return;
+ for(i=NQID; i<ndir; i++)
+ if(strcmp(port, dir[i].name) == 0)
+ return;
+ if(i == NDIR){
+ fprint(2, "plumb: too many ports; max %d\n", NDIR);
+ return;
+ }
+ ndir++;
+ dir[i].name = estrdup(port);
+ dir[i].qid = i;
+ dir[i].perm = 0400;
+ nports++;
+ ports = erealloc(ports, nports*sizeof(char*));
+ ports[nports-1] = dir[i].name;
+}
+
+static ulong
+getclock(void)
+{
+ char buf[32];
+
+ seek(clockfd, 0, 0);
+ read(clockfd, buf, sizeof buf);
+ return atoi(buf);
+}
+
+void
+startfsys(void)
+{
+ int p[2], fd;
+
+ fmtinstall('F', fcallfmt);
+ clockfd = open("/dev/time", OREAD|OCEXEC);
+ clock = getclock();
+ if(pipe(p) < 0)
+ error("can't create pipe: %r");
+ /* 0 will be server end, 1 will be client end */
+ srvfd = p[0];
+ srvclosefd = p[1];
+ sprint(srvfile, "/srv/plumb.%s.%d", user, getpid());
+ if(putenv("plumbsrv", srvfile) < 0)
+ error("can't write $plumbsrv: %r");
+ fd = create(srvfile, OWRITE|OCEXEC|ORCLOSE, 0600);
+ if(fd < 0)
+ error("can't create /srv file: %r");
+ if(fprint(fd, "%d", p[1]) <= 0)
+ error("can't write /srv/file: %r");
+ /* leave fd open; ORCLOSE will take care of it */
+
+ procrfork(fsysproc, nil, Stack, RFFDG);
+
+ close(p[0]);
+ if(mount(p[1], -1, "/mnt/plumb", MREPL, "") < 0)
+ error("can't mount /mnt/plumb: %r");
+ close(p[1]);
+}
+
+static void
+fsysproc(void*)
+{
+ int n;
+ Fcall *t;
+ Fid *f;
+ uchar *buf;
+
+ close(srvclosefd);
+ srvclosefd = -1;
+ t = nil;
+ for(;;){
+ buf = malloc(messagesize); /* avoid memset of emalloc */
+ if(buf == nil)
+ error("malloc failed: %r");
+ qlock(&readlock);
+ n = read9pmsg(srvfd, buf, messagesize);
+ if(n <= 0){
+ if(n < 0)
+ error("i/o error on server channel");
+ threadexitsall("unmounted");
+ }
+ if(readlock.head == nil) /* no other processes waiting to read; start one */
+ proccreate(fsysproc, nil, Stack);
+ qunlock(&readlock);
+ if(t == nil)
+ t = emalloc(sizeof(Fcall));
+ if(convM2S(buf, n, t) != n)
+ error("convert error in convM2S");
+ if(DEBUG)
+ fprint(2, "<= %F\n", t);
+ if(fcall[t->type] == nil)
+ fsysrespond(t, buf, Ebadfcall);
+ else{
+ if(t->type==Tversion || t->type==Tauth)
+ f = nil;
+ else
+ f = newfid(t->fid);
+ t = (*fcall[t->type])(t, buf, f);
+ }
+ }
+}
+
+static void
+fsysrespond(Fcall *t, uchar *buf, char *err)
+{
+ int n;
+
+ if(err){
+ t->type = Rerror;
+ t->ename = err;
+ }else
+ t->type++;
+ if(buf == nil)
+ buf = emalloc(messagesize);
+ n = convS2M(t, buf, messagesize);
+ if(n < 0)
+ error("convert error in convS2M");
+ if(write(srvfd, buf, n) != n)
+ error("write error in respond");
+ if(DEBUG)
+ fprint(2, "=> %F\n", t);
+ free(buf);
+}
+
+static
+Fid*
+newfid(int fid)
+{
+ Fid *f, *ff, **fh;
+
+ qlock(&queue);
+ ff = nil;
+ fh = &fids[fid&(Nhash-1)];
+ for(f=*fh; f; f=f->next)
+ if(f->fid == fid)
+ goto Return;
+ else if(ff==nil && !f->busy)
+ ff = f;
+ if(ff){
+ ff->fid = fid;
+ f = ff;
+ goto Return;
+ }
+ f = emalloc(sizeof *f);
+ f->fid = fid;
+ f->next = *fh;
+ *fh = f;
+ Return:
+ qunlock(&queue);
+ return f;
+}
+
+static uint
+dostat(Dirtab *dir, uchar *buf, uint nbuf, uint clock)
+{
+ Dir d;
+
+ d.qid.type = dir->type;
+ d.qid.path = dir->qid;
+ d.qid.vers = 0;
+ d.mode = dir->perm;
+ d.length = 0; /* would be nice to do better */
+ d.name = dir->name;
+ d.uid = user;
+ d.gid = user;
+ d.muid = user;
+ d.atime = clock;
+ d.mtime = clock;
+ return convD2M(&d, buf, nbuf);
+}
+
+static void
+queuesend(Dirtab *d, Plumbmsg *m)
+{
+ Sendreq *s, *t;
+ Fid *f;
+ int i;
+
+ s = emalloc(sizeof(Sendreq));
+ s->nfid = d->nopen;
+ s->nleft = s->nfid;
+ s->fid = emalloc(s->nfid*sizeof(Fid*));
+ i = 0;
+ /* build array of fids open on this channel */
+ for(f=d->fopen; f!=nil; f=f->nextopen)
+ s->fid[i++] = f;
+ s->msg = m;
+ s->next = nil;
+ /* link to end of queue; drainqueue() searches in sender order so this implements a FIFO */
+ for(t=d->sendq; t!=nil; t=t->next)
+ if(t->next == nil)
+ break;
+ if(t == nil)
+ d->sendq = s;
+ else
+ t->next = s;
+}
+
+static void
+queueread(Dirtab *d, Fcall *t, uchar *buf, Fid *f)
+{
+ Readreq *r;
+
+ r = emalloc(sizeof(Readreq));
+ r->fcall = t;
+ r->buf = buf;
+ r->fid = f;
+ r->next = d->readq;
+ d->readq = r;
+}
+
+static void
+drainqueue(Dirtab *d)
+{
+ Readreq *r, *nextr, *prevr;
+ Sendreq *s, *nexts, *prevs;
+ int i, n;
+
+ prevs = nil;
+ for(s=d->sendq; s!=nil; s=nexts){
+ nexts = s->next;
+ for(i=0; i<s->nfid; i++){
+ prevr = nil;
+ for(r=d->readq; r!=nil; r=nextr){
+ nextr = r->next;
+ if(r->fid == s->fid[i]){
+ /* pack the message if necessary */
+ if(s->pack == nil)
+ s->pack = plumbpack(s->msg, &s->npack);
+ /* exchange the stuff... */
+ r->fcall->data = s->pack+r->fid->offset;
+ n = s->npack - r->fid->offset;
+ if(n > messagesize-IOHDRSZ)
+ n = messagesize-IOHDRSZ;
+ if(n > r->fcall->count)
+ n = r->fcall->count;
+ r->fcall->count = n;
+ fsysrespond(r->fcall, r->buf, nil);
+ r->fid->offset += n;
+ if(r->fid->offset >= s->npack){
+ /* message transferred; delete this fid from send queue */
+ r->fid->offset = 0;
+ s->fid[i] = nil;
+ s->nleft--;
+ }
+ /* delete read request from queue */
+ if(prevr)
+ prevr->next = r->next;
+ else
+ d->readq = r->next;
+ free(r->fcall);
+ free(r);
+ break;
+ }else
+ prevr = r;
+ }
+ }
+ /* if no fids left, delete this send from queue */
+ if(s->nleft == 0){
+ free(s->fid);
+ plumbfree(s->msg);
+ free(s->pack);
+ if(prevs)
+ prevs->next = s->next;
+ else
+ d->sendq = s->next;
+ free(s);
+ }else
+ prevs = s;
+ }
+}
+
+/* can't flush a send because they are always answered synchronously */
+static void
+flushqueue(Dirtab *d, int oldtag)
+{
+ Readreq *r, *prevr;
+
+ prevr = nil;
+ for(r=d->readq; r!=nil; r=r->next){
+ if(oldtag == r->fcall->tag){
+ /* delete read request from queue */
+ if(prevr)
+ prevr->next = r->next;
+ else
+ d->readq = r->next;
+ free(r->fcall);
+ free(r->buf);
+ free(r);
+ return;
+ }
+ prevr = r;
+ }
+}
+
+/* remove messages awaiting delivery to now-closing fid */
+static void
+removesenders(Dirtab *d, Fid *fid)
+{
+ Sendreq *s, *nexts, *prevs;
+ int i;
+
+ prevs = nil;
+ for(s=d->sendq; s!=nil; s=nexts){
+ nexts = s->next;
+ for(i=0; i<s->nfid; i++)
+ if(fid == s->fid[i]){
+ /* delete this fid from send queue */
+ s->fid[i] = nil;
+ s->nleft--;
+ break;
+ }
+ /* if no fids left, delete this send from queue */
+ if(s->nleft == 0){
+ free(s->fid);
+ plumbfree(s->msg);
+ free(s->pack);
+ if(prevs)
+ prevs->next = s->next;
+ else
+ d->sendq = s->next;
+ free(s);
+ }else
+ prevs = s;
+ }
+}
+
+static void
+hold(Plumbmsg *m, Dirtab *d)
+{
+ Holdq *h, *q;
+
+ h = emalloc(sizeof(Holdq));
+ h->msg = m;
+ /* add to end of queue */
+ if(d->holdq == nil)
+ d->holdq = h;
+ else{
+ for(q=d->holdq; q->next!=nil; q=q->next)
+ ;
+ q->next = h;
+ }
+}
+
+static void
+queueheld(Dirtab *d)
+{
+ Holdq *h;
+
+ while(d->holdq != nil){
+ h = d->holdq;
+ d->holdq = h->next;
+ queuesend(d, h->msg);
+ /* no need to drain queue because we know no-one is reading yet */
+ free(h);
+ }
+}
+
+static void
+dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
+{
+ int i;
+ char *err;
+
+ qlock(&queue);
+ err = nil;
+ if(m->dst==nil || m->dst[0]=='\0'){
+ err = Enoport;
+ if(rs != nil)
+ err = startup(rs, e);
+ plumbfree(m);
+ }else
+ for(i=NQID; i<ndir; i++)
+ if(strcmp(m->dst, dir[i].name) == 0){
+ if(dir[i].nopen == 0){
+ err = startup(rs, e);
+ if(e!=nil && e->holdforclient)
+ hold(m, &dir[i]);
+ else
+ plumbfree(m);
+ }else{
+ queuesend(&dir[i], m);
+ drainqueue(&dir[i]);
+ }
+ break;
+ }
+ freeexec(e);
+ qunlock(&queue);
+ fsysrespond(t, buf, err);
+ free(t);
+}
+
+static Fcall*
+fsysversion(Fcall *t, uchar *buf, Fid*)
+{
+ if(t->msize < 256){
+ fsysrespond(t, buf, "version: message size too small");
+ return t;
+ }
+ if(t->msize < messagesize)
+ messagesize = t->msize;
+ t->msize = messagesize;
+ if(strncmp(t->version, "9P2000", 6) != 0){
+ fsysrespond(t, buf, "unrecognized 9P version");
+ return t;
+ }
+ t->version = "9P2000";
+ fsysrespond(t, buf, nil);
+ return t;
+}
+
+static Fcall*
+fsysauth(Fcall *t, uchar *buf, Fid*)
+{
+ fsysrespond(t, buf, "plumber: authentication not required");
+ return t;
+}
+
+static Fcall*
+fsysattach(Fcall *t, uchar *buf, Fid *f)
+{
+ Fcall out;
+
+ if(strcmp(t->uname, user) != 0){
+ fsysrespond(&out, buf, Eperm);
+ return t;
+ }
+ f->busy = 1;
+ f->open = 0;
+ f->qid.type = QTDIR;
+ f->qid.path = Qdir;
+ f->qid.vers = 0;
+ f->dir = dir;
+ memset(&out, 0, sizeof(Fcall));
+ out.type = t->type;
+ out.tag = t->tag;
+ out.fid = f->fid;
+ out.qid = f->qid;
+ fsysrespond(&out, buf, nil);
+ return t;
+}
+
+static Fcall*
+fsysflush(Fcall *t, uchar *buf, Fid*)
+{
+ int i;
+
+ qlock(&queue);
+ for(i=NQID; i<ndir; i++)
+ flushqueue(&dir[i], t->oldtag);
+ qunlock(&queue);
+ fsysrespond(t, buf, nil);
+ return t;
+}
+
+static Fcall*
+fsyswalk(Fcall *t, uchar *buf, Fid *f)
+{
+ Fcall out;
+ Fid *nf;
+ ulong path;
+ Dirtab *d, *dir;
+ Qid q;
+ int i;
+ uchar type;
+ char *err;
+
+ if(f->open){
+ fsysrespond(t, buf, "clone of an open fid");
+ return t;
+ }
+
+ nf = nil;
+ if(t->fid != t->newfid){
+ nf = newfid(t->newfid);
+ if(nf->busy){
+ fsysrespond(t, buf, "clone to a busy fid");
+ return t;
+ }
+ nf->busy = 1;
+ nf->open = 0;
+ nf->dir = f->dir;
+ nf->qid = f->qid;
+ f = nf; /* walk f */
+ }
+
+ out.nwqid = 0;
+ err = nil;
+ dir = f->dir;
+ q = f->qid;
+
+ if(t->nwname > 0){
+ for(i=0; i<t->nwname; i++){
+ if((q.type & QTDIR) == 0){
+ err = Enotdir;
+ break;
+ }
+ if(strcmp(t->wname[i], "..") == 0){
+ type = QTDIR;
+ path = Qdir;
+ Accept:
+ q.type = type;
+ q.vers = 0;
+ q.path = path;
+ out.wqid[out.nwqid++] = q;
+ continue;
+ }
+ d = dir;
+ d++; /* skip '.' */
+ for(; d->name; d++)
+ if(strcmp(t->wname[i], d->name) == 0){
+ type = d->type;
+ path = d->qid;
+ dir = d;
+ goto Accept;
+ }
+ err = Enoexist;
+ break;
+ }
+ }
+
+ out.type = t->type;
+ out.tag = t->tag;
+ if(err!=nil || out.nwqid<t->nwname){
+ if(nf)
+ nf->busy = 0;
+ }else if(out.nwqid == t->nwname){
+ f->qid = q;
+ f->dir = dir;
+ }
+
+ fsysrespond(&out, buf, err);
+ return t;
+}
+
+static Fcall*
+fsysopen(Fcall *t, uchar *buf, Fid *f)
+{
+ int m, clearrules, mode;
+
+ clearrules = 0;
+ if(t->mode & OTRUNC){
+ if(f->qid.path != Qrules)
+ goto Deny;
+ clearrules = 1;
+ }
+ /* can't truncate anything, so just disregard */
+ mode = t->mode & ~(OTRUNC|OCEXEC);
+ /* can't execute or remove anything */
+ if(mode==OEXEC || (mode&ORCLOSE))
+ goto Deny;
+ switch(mode){
+ default:
+ goto Deny;
+ case OREAD:
+ m = 0400;
+ break;
+ case OWRITE:
+ m = 0200;
+ break;
+ case ORDWR:
+ m = 0600;
+ break;
+ }
+ if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
+ goto Deny;
+ if(f->qid.path==Qrules && (mode==OWRITE || mode==ORDWR)){
+ lock(&rulesref);
+ if(rulesref.ref++ != 0){
+ rulesref.ref--;
+ unlock(&rulesref);
+ fsysrespond(t, buf, Einuse);
+ return t;
+ }
+ unlock(&rulesref);
+ }
+ if(clearrules){
+ writerules(nil, 0);
+ rules[0] = nil;
+ }
+ t->qid = f->qid;
+ t->iounit = 0;
+ qlock(&queue);
+ f->mode = mode;
+ f->open = 1;
+ f->dir->nopen++;
+ f->nextopen = f->dir->fopen;
+ f->dir->fopen = f;
+ queueheld(f->dir);
+ qunlock(&queue);
+ fsysrespond(t, buf, nil);
+ return t;
+
+ Deny:
+ fsysrespond(t, buf, Eperm);
+ return t;
+}
+
+static Fcall*
+fsyscreate(Fcall *t, uchar *buf, Fid*)
+{
+ fsysrespond(t, buf, Eperm);
+ return t;
+}
+
+static Fcall*
+fsysreadrules(Fcall *t, uchar *buf)
+{
+ char *p;
+ int n;
+
+ p = printrules();
+ n = strlen(p);
+ t->data = p;
+ if(t->offset >= n)
+ t->count = 0;
+ else{
+ t->data = p+t->offset;
+ if(t->offset+t->count > n)
+ t->count = n-t->offset;
+ }
+ fsysrespond(t, buf, nil);
+ free(p);
+ return t;
+}
+
+static Fcall*
+fsysread(Fcall *t, uchar *buf, Fid *f)
+{
+ uchar *b;
+ int i, n, o, e;
+ uint len;
+ Dirtab *d;
+ uint clock;
+
+ if(f->qid.path != Qdir){
+ if(f->qid.path == Qrules)
+ return fsysreadrules(t, buf);
+ /* read from port */
+ if(f->qid.path < NQID){
+ fsysrespond(t, buf, "internal error: unknown read port");
+ return t;
+ }
+ qlock(&queue);
+ queueread(f->dir, t, buf, f);
+ drainqueue(f->dir);
+ qunlock(&queue);
+ return nil;
+ }
+ o = t->offset;
+ e = t->offset+t->count;
+ clock = getclock();
+ b = malloc(messagesize-IOHDRSZ);
+ if(b == nil){
+ fsysrespond(t, buf, Enomem);
+ return t;
+ }
+ n = 0;
+ d = dir;
+ d++; /* first entry is '.' */
+ for(i=0; d->name!=nil && i<e; i+=len){
+ len = dostat(d, b+n, messagesize-IOHDRSZ-n, clock);
+ if(len <= BIT16SZ)
+ break;
+ if(i >= o)
+ n += len;
+ d++;
+ }
+ t->data = (char*)b;
+ t->count = n;
+ fsysrespond(t, buf, nil);
+ free(b);
+ return t;
+}
+
+static Fcall*
+fsyswrite(Fcall *t, uchar *buf, Fid *f)
+{
+ Plumbmsg *m;
+ int i, n;
+ long count;
+ char *data;
+ Exec *e;
+
+ switch((int)f->qid.path){
+ case Qdir:
+ fsysrespond(t, buf, Eisdir);
+ return t;
+ case Qrules:
+ clock = getclock();
+ fsysrespond(t, buf, writerules(t->data, t->count));
+ return t;
+ case Qsend:
+ if(f->offset == 0){
+ data = t->data;
+ count = t->count;
+ }else{
+ /* partial message already assembled */
+ f->writebuf = erealloc(f->writebuf, f->offset + t->count);
+ memmove(f->writebuf+f->offset, t->data, t->count);
+ data = f->writebuf;
+ count = f->offset+t->count;
+ }
+ m = plumbunpackpartial(data, count, &n);
+ if(m == nil){
+ if(n == 0){
+ f->offset = 0;
+ free(f->writebuf);
+ f->writebuf = nil;
+ fsysrespond(t, buf, Ebadmsg);
+ return t;
+ }
+ /* can read more... */
+ if(f->offset == 0){
+ f->writebuf = emalloc(t->count);
+ memmove(f->writebuf, t->data, t->count);
+ }
+ /* else buffer has already been grown */
+ f->offset += t->count;
+ fsysrespond(t, buf, nil);
+ return t;
+ }
+ /* release partial buffer */
+ f->offset = 0;
+ free(f->writebuf);
+ f->writebuf = nil;
+ for(i=0; rules[i]; i++)
+ if((e=matchruleset(m, rules[i])) != nil){
+ dispose(t, buf, m, rules[i], e);
+ return nil;
+ }
+ if(m->dst != nil){
+ dispose(t, buf, m, nil, nil);
+ return nil;
+ }
+ fsysrespond(t, buf, "no matching plumb rule");
+ return t;
+ }
+ fsysrespond(t, buf, "internal error: write to unknown file");
+ return t;
+}
+
+static Fcall*
+fsysstat(Fcall *t, uchar *buf, Fid *f)
+{
+ t->stat = emalloc(messagesize-IOHDRSZ);
+ t->nstat = dostat(f->dir, t->stat, messagesize-IOHDRSZ, clock);
+ fsysrespond(t, buf, nil);
+ free(t->stat);
+ t->stat = nil;
+ return t;
+}
+
+static Fcall*
+fsyswstat(Fcall *t, uchar *buf, Fid*)
+{
+ fsysrespond(t, buf, Eperm);
+ return t;
+}
+
+static Fcall*
+fsysremove(Fcall *t, uchar *buf, Fid*)
+{
+ fsysrespond(t, buf, Eperm);
+ return t;
+}
+
+static Fcall*
+fsysclunk(Fcall *t, uchar *buf, Fid *f)
+{
+ Fid *prev, *p;
+ Dirtab *d;
+
+ qlock(&queue);
+ if(f->open){
+ d = f->dir;
+ d->nopen--;
+ if(d->qid==Qrules && (f->mode==OWRITE || f->mode==ORDWR)){
+ /*
+ * just to be sure last rule is parsed; error messages will be lost, though,
+ * unless last write ended with a blank line
+ */
+ writerules(nil, 0);
+ lock(&rulesref);
+ rulesref.ref--;
+ unlock(&rulesref);
+ }
+ prev = nil;
+ for(p=d->fopen; p; p=p->nextopen){
+ if(p == f){
+ if(prev)
+ prev->nextopen = f->nextopen;
+ else
+ d->fopen = f->nextopen;
+ removesenders(d, f);
+ break;
+ }
+ prev = p;
+ }
+ }
+ f->busy = 0;
+ f->open = 0;
+ f->offset = 0;
+ if(f->writebuf != nil){
+ free(f->writebuf);
+ f->writebuf = nil;
+ }
+ qunlock(&queue);
+ fsysrespond(t, buf, nil);
+ return t;
+}
diff --git a/src/cmd/plumb/match.c b/src/cmd/plumb/match.c
new file mode 100644
index 00000000..42a9232f
--- /dev/null
+++ b/src/cmd/plumb/match.c
@@ -0,0 +1,463 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <regexp.h>
+#include <thread.h>
+#include <plumb.h>
+#include "plumber.h"
+
+static char*
+nonnil(char *s)
+{
+ if(s == nil)
+ return "";
+ return s;
+}
+
+int
+verbis(int obj, Plumbmsg *m, Rule *r)
+{
+ switch(obj){
+ default:
+ fprint(2, "unimplemented 'is' object %d\n", obj);
+ break;
+ case OData:
+ return strcmp(m->data, r->qarg) == 0;
+ case ODst:
+ return strcmp(m->dst, r->qarg) == 0;
+ case OType:
+ return strcmp(m->type, r->qarg) == 0;
+ case OWdir:
+ return strcmp(m->wdir, r->qarg) == 0;
+ case OSrc:
+ return strcmp(m->src, r->qarg) == 0;
+ }
+ return 0;
+}
+
+static void
+setvar(Resub rs[10], char *match[10])
+{
+ int i, n;
+
+ for(i=0; i<10; i++){
+ free(match[i]);
+ match[i] = nil;
+ }
+ for(i=0; i<10 && rs[i].sp!=nil; i++){
+ n = rs[i].ep-rs[i].sp;
+ match[i] = emalloc(n+1);
+ memmove(match[i], rs[i].sp, n);
+ match[i][n] = '\0';
+ }
+}
+
+int
+clickmatch(Reprog *re, char *text, Resub rs[10], int click)
+{
+ char *clickp;
+ int i, w;
+ Rune r;
+
+ /* click is in characters, not bytes */
+ for(i=0; i<click && text[i]!='\0'; i+=w)
+ w = chartorune(&r, text+i);
+ clickp = text+i;
+ for(i=0; i<=click; i++){
+ memset(rs, 0, 10*sizeof(Resub));
+ if(regexec(re, text+i, rs, 10))
+ if(rs[0].sp<=clickp && clickp<=rs[0].ep)
+ return 1;
+ }
+ return 0;
+}
+
+int
+verbmatches(int obj, Plumbmsg *m, Rule *r, Exec *e)
+{
+ Resub rs[10];
+ char *clickval, *alltext;
+ int p0, p1, ntext;
+
+ memset(rs, 0, sizeof rs);
+ ntext = -1;
+ switch(obj){
+ default:
+ fprint(2, "unimplemented 'matches' object %d\n", obj);
+ break;
+ case OData:
+ clickval = plumblookup(m->attr, "click");
+ if(clickval == nil){
+ alltext = m->data;
+ ntext = m->ndata;
+ goto caseAlltext;
+ }
+ if(!clickmatch(r->regex, m->data, rs, atoi(clickval)))
+ break;
+ p0 = rs[0].sp - m->data;
+ p1 = rs[0].ep - m->data;
+ if(e->p0 >=0 && !(p0==e->p0 && p1==e->p1))
+ break;
+ e->clearclick = 1;
+ e->setdata = 1;
+ e->p0 = p0;
+ e->p1 = p1;
+ setvar(rs, e->match);
+ return 1;
+ case ODst:
+ alltext = m->dst;
+ goto caseAlltext;
+ case OType:
+ alltext = m->type;
+ goto caseAlltext;
+ case OWdir:
+ alltext = m->wdir;
+ goto caseAlltext;
+ case OSrc:
+ alltext = m->src;
+ /* fall through */
+ caseAlltext:
+ /* must match full text */
+ if(ntext < 0)
+ ntext = strlen(alltext);
+ if(!regexec(r->regex, alltext, rs, 10) || rs[0].sp!=alltext || rs[0].ep!=alltext+ntext)
+ break;
+ setvar(rs, e->match);
+ return 1;
+ }
+ return 0;
+}
+
+int
+isfile(char *file, ulong maskon, ulong maskoff)
+{
+ Dir *d;
+ int mode;
+
+ d = dirstat(file);
+ if(d == nil)
+ return 0;
+ mode = d->mode;
+ free(d);
+ if((mode & maskon) == 0)
+ return 0;
+ if(mode & maskoff)
+ return 0;
+ return 1;
+}
+
+char*
+absolute(char *dir, char *file)
+{
+ char *p;
+
+ if(file[0] == '/')
+ return estrdup(file);
+ p = emalloc(strlen(dir)+1+strlen(file)+1);
+ sprint(p, "%s/%s", dir, file);
+ return cleanname(p);
+}
+
+int
+verbisfile(int obj, Plumbmsg *m, Rule *r, Exec *e, ulong maskon, ulong maskoff, char **var)
+{
+ char *file;
+
+ switch(obj){
+ default:
+ fprint(2, "unimplemented 'isfile' object %d\n", obj);
+ break;
+ case OArg:
+ file = absolute(m->wdir, expand(e, r->arg, nil));
+ if(isfile(file, maskon, maskoff)){
+ *var = file;
+ return 1;
+ }
+ free(file);
+ break;
+ case OData:
+ case OWdir:
+ file = absolute(m->wdir, obj==OData? m->data : m->wdir);
+ if(isfile(file, maskon, maskoff)){
+ *var = file;
+ return 1;
+ }
+ free(file);
+ break;
+ }
+ return 0;
+}
+
+int
+verbset(int obj, Plumbmsg *m, Rule *r, Exec *e)
+{
+ char *new;
+
+ switch(obj){
+ default:
+ fprint(2, "unimplemented 'is' object %d\n", obj);
+ break;
+ case OData:
+ new = estrdup(expand(e, r->arg, nil));
+ m->ndata = strlen(new);
+ free(m->data);
+ m->data = new;
+ e->p0 = -1;
+ e->p1 = -1;
+ e->setdata = 0;
+ return 1;
+ case ODst:
+ new = estrdup(expand(e, r->arg, nil));
+ free(m->dst);
+ m->dst = new;
+ return 1;
+ case OType:
+ new = estrdup(expand(e, r->arg, nil));
+ free(m->type);
+ m->type = new;
+ return 1;
+ case OWdir:
+ new = estrdup(expand(e, r->arg, nil));
+ free(m->wdir);
+ m->wdir = new;
+ return 1;
+ case OSrc:
+ new = estrdup(expand(e, r->arg, nil));
+ free(m->src);
+ m->src = new;
+ return 1;
+ }
+ return 0;
+}
+
+int
+verbadd(int obj, Plumbmsg *m, Rule *r, Exec *e)
+{
+ switch(obj){
+ default:
+ fprint(2, "unimplemented 'add' object %d\n", obj);
+ break;
+ case OAttr:
+ m->attr = plumbaddattr(m->attr, plumbunpackattr(expand(e, r->arg, nil)));
+ return 1;
+ }
+ return 0;
+}
+
+int
+verbdelete(int obj, Plumbmsg *m, Rule *r, Exec *e)
+{
+ char *a;
+
+ switch(obj){
+ default:
+ fprint(2, "unimplemented 'delete' object %d\n", obj);
+ break;
+ case OAttr:
+ a = expand(e, r->arg, nil);
+ if(plumblookup(m->attr, a) == nil)
+ break;
+ m->attr = plumbdelattr(m->attr, a);
+ return 1;
+ }
+ return 0;
+}
+
+int
+matchpat(Plumbmsg *m, Exec *e, Rule *r)
+{
+ switch(r->verb){
+ default:
+ fprint(2, "unimplemented verb %d\n", r->verb);
+ break;
+ case VAdd:
+ return verbadd(r->obj, m, r, e);
+ case VDelete:
+ return verbdelete(r->obj, m, r, e);
+ case VIs:
+ return verbis(r->obj, m, r);
+ case VIsdir:
+ return verbisfile(r->obj, m, r, e, DMDIR, 0, &e->dir);
+ case VIsfile:
+ return verbisfile(r->obj, m, r, e, ~DMDIR, DMDIR, &e->file);
+ case VMatches:
+ return verbmatches(r->obj, m, r, e);
+ case VSet:
+ verbset(r->obj, m, r, e);
+ return 1;
+ }
+ return 0;
+}
+
+void
+freeexec(Exec *exec)
+{
+ int i;
+
+ if(exec == nil)
+ return;
+ free(exec->dir);
+ free(exec->file);
+ for(i=0; i<10; i++)
+ free(exec->match[i]);
+ free(exec);
+}
+
+Exec*
+newexec(Plumbmsg *m)
+{
+ Exec *exec;
+
+ exec = emalloc(sizeof(Exec));
+ exec->msg = m;
+ exec->p0 = -1;
+ exec->p1 = -1;
+ return exec;
+}
+
+void
+rewrite(Plumbmsg *m, Exec *e)
+{
+ Plumbattr *a, *prev;
+
+ if(e->clearclick){
+ prev = nil;
+ for(a=m->attr; a!=nil; a=a->next){
+ if(strcmp(a->name, "click") == 0){
+ if(prev == nil)
+ m->attr = a->next;
+ else
+ prev->next = a->next;
+ free(a->name);
+ free(a->value);
+ free(a);
+ break;
+ }
+ prev = a;
+ }
+ if(e->setdata){
+ free(m->data);
+ m->data = estrdup(expand(e, "$0", nil));
+ m->ndata = strlen(m->data);
+ }
+ }
+}
+
+char**
+buildargv(char *s, Exec *e)
+{
+ char **av;
+ int ac;
+
+ ac = 0;
+ av = nil;
+ for(;;){
+ av = erealloc(av, (ac+1) * sizeof(char*));
+ av[ac] = nil;
+ while(*s==' ' || *s=='\t')
+ s++;
+ if(*s == '\0')
+ break;
+ av[ac++] = estrdup(expand(e, s, &s));
+ }
+ return av;
+}
+
+Exec*
+matchruleset(Plumbmsg *m, Ruleset *rs)
+{
+ int i;
+ Exec *exec;
+
+ if(m->dst!=nil && m->dst[0]!='\0' && rs->port!=nil && strcmp(m->dst, rs->port)!=0)
+ return nil;
+ exec = newexec(m);
+ for(i=0; i<rs->npat; i++)
+ if(!matchpat(m, exec, rs->pat[i])){
+ freeexec(exec);
+ return nil;
+ }
+ if(rs->port!=nil && (m->dst==nil || m->dst[0]=='\0')){
+ free(m->dst);
+ m->dst = estrdup(rs->port);
+ }
+ rewrite(m, exec);
+ return exec;
+}
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = 4096+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
+void
+stackargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
+{
+ int i, n;
+ char *s, *a;
+
+ s = args;
+ for(i=0; i<NARGS; i++){
+ a = inargv[i];
+ if(a == nil)
+ break;
+ n = strlen(a)+1;
+ if((s-args)+n >= NARGCHAR) /* too many characters */
+ break;
+ argv[i] = s;
+ memmove(s, a, n);
+ s += n;
+ free(a);
+ }
+ argv[i] = nil;
+}
+
+
+void
+execproc(void *v)
+{
+ char **av;
+ char buf[1024], *args[NARGS+1], argc[NARGCHAR];
+
+ rfork(RFFDG);
+ close(0);
+ open("/dev/null", OREAD);
+ av = v;
+ stackargv(av, args, argc);
+ free(av);
+ procexec(nil, args[0], args);
+ if(args[0][0]!='/' && strncmp(args[0], "./", 2)!=0 && strncmp(args[0], "../", 3)!=0)
+ snprint(buf, sizeof buf, "/bin/%s", args[0]);
+ procexec(nil, buf, args);
+ threadexits("can't exec");
+}
+
+char*
+startup(Ruleset *rs, Exec *e)
+{
+ char **argv;
+ int i;
+
+ if(rs != nil)
+ for(i=0; i<rs->nact; i++){
+ if(rs->act[i]->verb == VStart)
+ goto Found;
+ if(rs->act[i]->verb == VClient){
+ if(e->msg->dst==nil || e->msg->dst[0]=='\0')
+ return "no port for \"client\" rule";
+ e->holdforclient = 1;
+ goto Found;
+ }
+ }
+ return "no start action for plumb message";
+
+Found:
+ argv = buildargv(rs->act[i]->arg, e);
+ if(argv[0] == nil)
+ return "empty argument list";
+ proccreate(execproc, argv, EXECSTACK);
+ return nil;
+}
diff --git a/src/cmd/plumb/mkfile b/src/cmd/plumb/mkfile
new file mode 100644
index 00000000..d6a14654
--- /dev/null
+++ b/src/cmd/plumb/mkfile
@@ -0,0 +1,20 @@
+</$objtype/mkfile
+
+TARG=plumber plumb
+
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkmany
+
+PLUMBER=plumber.$O fsys.$O match.$O rules.$O
+PLUMB=plumb.$O
+
+$PLUMBER: $HFILES plumber.h
+$PLUMB: $HFILES
+
+$O.plumb: $PLUMB
+$O.plumber: $PLUMBER
+
+syms:V:
+ 8c -a plumber.c >syms
+ 8c -aa fsys.c match.c rules.c >>syms
diff --git a/src/cmd/plumb/plumb.c b/src/cmd/plumb/plumb.c
new file mode 100644
index 00000000..e0cff912
--- /dev/null
+++ b/src/cmd/plumb/plumb.c
@@ -0,0 +1,119 @@
+#include <u.h>
+#include <libc.h>
+#include <plumb.h>
+
+char *plumbfile = nil;
+Plumbmsg m;
+
+void
+usage(void)
+{
+ fprint(2, "usage: plumb [-p plumbfile] [-a 'attr=value ...'] [-s src] [-d dst] [-t type] [-w wdir] -i | data1\n");
+ exits("usage");
+}
+
+void
+gather(void)
+{
+ char buf[8192];
+ int n;
+
+ m.ndata = 0;
+ m.data = nil;
+ while((n = read(0, buf, sizeof buf)) > 0){
+ m.data = realloc(m.data, m.ndata+n);
+ if(m.data == nil){
+ fprint(2, "plumb: alloc failed: %r\n");
+ exits("alloc");
+ }
+ memmove(m.data+m.ndata, buf, n);
+ m.ndata += n;
+ }
+ if(n < 0){
+ fprint(2, "plumb: i/o error on input: %r\n");
+ exits("read");
+ }
+}
+
+void
+main(int argc, char *argv[])
+{
+ char buf[1024], *p;
+ int fd, i, input;
+
+ input = 0;
+ m.src = "plumb";
+ m.dst = nil;
+ m.wdir = getwd(buf, sizeof buf);
+ m.type = "text";
+ m.attr = nil;
+ ARGBEGIN{
+ case 'a':
+ p = ARGF();
+ if(p == nil)
+ usage();
+ m.attr = plumbaddattr(m.attr, plumbunpackattr(p));
+ break;
+ case 'd':
+ m.dst = ARGF();
+ if(m.dst == nil)
+ usage();
+ break;
+ case 'i':
+ input++;
+ break;
+ case 't':
+ case 'k': /* for backwards compatibility */
+ m.type = ARGF();
+ if(m.type == nil)
+ usage();
+ break;
+ case 'p':
+ plumbfile = ARGF();
+ if(plumbfile == nil)
+ usage();
+ break;
+ case 's':
+ m.src = ARGF();
+ if(m.src == nil)
+ usage();
+ break;
+ case 'w':
+ m.wdir = ARGF();
+ if(m.wdir == nil)
+ usage();
+ break;
+ }ARGEND
+
+ if((input && argc>0) || (!input && argc<1))
+ usage();
+ if(plumbfile != nil)
+ fd = open(plumbfile, OWRITE);
+ else
+ fd = plumbopen("send", OWRITE);
+ if(fd < 0){
+ fprint(2, "plumb: can't open plumb file: %r\n");
+ exits("open");
+ }
+ if(input){
+ gather();
+ if(plumblookup(m.attr, "action") == nil)
+ m.attr = plumbaddattr(m.attr, plumbunpackattr("action=showdata"));
+ if(plumbsend(fd, &m) < 0){
+ fprint(2, "plumb: can't send message: %r\n");
+ exits("error");
+ }
+ exits(nil);
+ }
+ for(i=0; i<argc; i++){
+ if(input == 0){
+ m.data = argv[i];
+ m.ndata = -1;
+ }
+ if(plumbsend(fd, &m) < 0){
+ fprint(2, "plumb: can't send message: %r\n");
+ exits("error");
+ }
+ }
+ exits(nil);
+}
diff --git a/src/cmd/plumb/plumber.c b/src/cmd/plumb/plumber.c
new file mode 100644
index 00000000..d0bd9c14
--- /dev/null
+++ b/src/cmd/plumb/plumber.c
@@ -0,0 +1,147 @@
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+#include <thread.h>
+#include <plumb.h>
+#include <auth.h>
+#include <fcall.h>
+#include "plumber.h"
+
+char *plumbfile;
+char *user;
+char *home;
+char *progname;
+Ruleset **rules;
+int printerrors=1;
+jmp_buf parsejmp;
+char *lasterror;
+
+void
+makeports(Ruleset *rules[])
+{
+ int i;
+
+ for(i=0; rules[i]; i++)
+ addport(rules[i]->port);
+}
+
+void
+mainproc(void *v)
+{
+ Channel *c;
+
+ c = v;
+ printerrors = 0;
+ makeports(rules);
+ startfsys();
+ sendp(c, nil);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char buf[512];
+ int fd;
+ Channel *c;
+
+ progname = "plumber";
+
+ ARGBEGIN{
+ case 'p':
+ plumbfile = ARGF();
+ break;
+ }ARGEND
+
+ user = getenv("user");
+ home = getenv("home");
+ if(user==nil || home==nil)
+ error("can't initialize $user or $home: %r");
+ if(plumbfile == nil){
+ sprint(buf, "%s/lib/plumbing", home);
+ plumbfile = estrdup(buf);
+ }
+
+ fd = open(plumbfile, OREAD);
+ if(fd < 0)
+ error("can't open rules file %s: %r", plumbfile);
+ if(setjmp(parsejmp))
+ error("parse error");
+
+ rules = readrules(plumbfile, fd);
+ close(fd);
+
+ /*
+ * Start all processes and threads from other proc
+ * so we (main pid) can return to user.
+ */
+ c = chancreate(sizeof(void*), 0);
+ proccreate(mainproc, c, 8192);
+ recvp(c);
+ chanfree(c);
+ threadexits(nil);
+}
+
+void
+error(char *fmt, ...)
+{
+ char buf[512];
+ va_list args;
+
+ va_start(args, fmt);
+ vseprint(buf, buf+sizeof buf, fmt, args);
+ va_end(args);
+
+ fprint(2, "%s: %s\n", progname, buf);
+ threadexitsall("error");
+}
+
+void
+parseerror(char *fmt, ...)
+{
+ char buf[512];
+ va_list args;
+
+ va_start(args, fmt);
+ vseprint(buf, buf+sizeof buf, fmt, args);
+ va_end(args);
+
+ if(printerrors){
+ printinputstack();
+ fprint(2, "%s\n", buf);
+ }
+ do; while(popinput());
+ lasterror = estrdup(buf);
+ longjmp(parsejmp, 1);
+}
+
+void*
+emalloc(long n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("malloc failed: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, long n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ error("realloc failed: %r");
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = strdup(s);
+ if(t == nil)
+ error("estrdup failed: %r");
+ return t;
+}
diff --git a/src/cmd/plumb/plumber.h b/src/cmd/plumb/plumber.h
new file mode 100644
index 00000000..0d1205f9
--- /dev/null
+++ b/src/cmd/plumb/plumber.h
@@ -0,0 +1,93 @@
+typedef struct Exec Exec;
+typedef struct Rule Rule;
+typedef struct Ruleset Ruleset;
+
+/*
+ * Object
+ */
+enum
+{
+ OArg,
+ OAttr,
+ OData,
+ ODst,
+ OPlumb,
+ OSrc,
+ OType,
+ OWdir,
+};
+
+/*
+ * Verbs
+ */
+enum
+{
+ VAdd, /* apply to OAttr only */
+ VClient,
+ VDelete, /* apply to OAttr only */
+ VIs,
+ VIsdir,
+ VIsfile,
+ VMatches,
+ VSet,
+ VStart,
+ VTo,
+};
+
+struct Rule
+{
+ int obj;
+ int verb;
+ char *arg; /* unparsed string of all arguments */
+ char *qarg; /* quote-processed arg string */
+ Reprog *regex;
+};
+
+struct Ruleset
+{
+ int npat;
+ int nact;
+ Rule **pat;
+ Rule **act;
+ char *port;
+};
+
+struct Exec
+{
+ Plumbmsg *msg;
+ char *match[10];
+ int p0; /* begin and end of match */
+ int p1;
+ int clearclick; /* click was expanded; remove attribute */
+ int setdata; /* data should be set to $0 */
+ int holdforclient; /* exec'ing client; keep message until port is opened */
+ /* values of $variables */
+ char *file;
+ char *dir;
+};
+
+void parseerror(char*, ...);
+void error(char*, ...);
+void* emalloc(long);
+void* erealloc(void*, long);
+char* estrdup(char*);
+Ruleset** readrules(char*, int);
+void startfsys(void);
+Exec* matchruleset(Plumbmsg*, Ruleset*);
+void freeexec(Exec*);
+char* startup(Ruleset*, Exec*);
+char* printrules(void);
+void addport(char*);
+char* writerules(char*, int);
+char* expand(Exec*, char*, char**);
+void makeports(Ruleset*[]);
+void printinputstack(void);
+int popinput(void);
+
+Ruleset **rules;
+char *user;
+char *home;
+jmp_buf parsejmp;
+char *lasterror;
+char **ports;
+int nports;
diff --git a/src/cmd/plumb/rules.c b/src/cmd/plumb/rules.c
new file mode 100644
index 00000000..262f6d67
--- /dev/null
+++ b/src/cmd/plumb/rules.c
@@ -0,0 +1,779 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <regexp.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "plumber.h"
+
+typedef struct Input Input;
+typedef struct Var Var;
+
+struct Input
+{
+ char *file; /* name of file */
+ Biobuf *fd; /* input buffer, if from real file */
+ uchar *s; /* input string, if from /mnt/plumb/rules */
+ uchar *end; /* end of input string */
+ int lineno;
+ Input *next; /* file to read after EOF on this one */
+};
+
+struct Var
+{
+ char *name;
+ char *value;
+ char *qvalue;
+};
+
+static int parsing;
+static int nvars;
+static Var *vars;
+static Input *input;
+
+static char ebuf[4096];
+
+char *badports[] =
+{
+ ".",
+ "..",
+ "send",
+ nil
+};
+
+char *objects[] =
+{
+ "arg",
+ "attr",
+ "data",
+ "dst",
+ "plumb",
+ "src",
+ "type",
+ "wdir",
+ nil
+};
+
+char *verbs[] =
+{
+ "add",
+ "client",
+ "delete",
+ "is",
+ "isdir",
+ "isfile",
+ "matches",
+ "set",
+ "start",
+ "to",
+ nil
+};
+
+static void
+printinputstackrev(Input *in)
+{
+ if(in == nil)
+ return;
+ printinputstackrev(in->next);
+ fprint(2, "%s:%d: ", in->file, in->lineno);
+}
+
+void
+printinputstack(void)
+{
+ printinputstackrev(input);
+}
+
+static void
+pushinput(char *name, int fd, uchar *str)
+{
+ Input *in;
+ int depth;
+
+ depth = 0;
+ for(in=input; in; in=in->next)
+ if(depth++ >= 10) /* prevent deep C stack in plumber and bad include structure */
+ parseerror("include stack too deep; max 10");
+
+ in = emalloc(sizeof(Input));
+ in->file = estrdup(name);
+ in->next = input;
+ input = in;
+ if(str)
+ in->s = str;
+ else{
+ in->fd = emalloc(sizeof(Biobuf));
+ if(Binit(in->fd, fd, OREAD) < 0)
+ parseerror("can't initialize Bio for rules file: %r");
+ }
+
+}
+
+int
+popinput(void)
+{
+ Input *in;
+
+ in = input;
+ if(in == nil)
+ return 0;
+ input = in->next;
+ if(in->fd){
+ Bterm(in->fd);
+ free(in->fd);
+ }
+ free(in);
+ return 1;
+}
+
+int
+getc(void)
+{
+ if(input == nil)
+ return Beof;
+ if(input->fd)
+ return Bgetc(input->fd);
+ if(input->s < input->end)
+ return *(input->s)++;
+ return -1;
+}
+
+char*
+getline(void)
+{
+ static int n = 0;
+ static char *s, *incl;
+ int c, i;
+
+ i = 0;
+ for(;;){
+ c = getc();
+ if(c < 0)
+ return nil;
+ if(i == n){
+ n += 100;
+ s = erealloc(s, n);
+ }
+ if(c<0 || c=='\0' || c=='\n')
+ break;
+ s[i++] = c;
+ }
+ s[i] = '\0';
+ return s;
+}
+
+int
+lookup(char *s, char *tab[])
+{
+ int i;
+
+ for(i=0; tab[i]!=nil; i++)
+ if(strcmp(s, tab[i])==0)
+ return i;
+ return -1;
+}
+
+Var*
+lookupvariable(char *s, int n)
+{
+ int i;
+
+ for(i=0; i<nvars; i++)
+ if(n==strlen(vars[i].name) && memcmp(s, vars[i].name, n)==0)
+ return vars+i;
+ return nil;
+}
+
+char*
+variable(char *s, int n)
+{
+ Var *var;
+
+ var = lookupvariable(s, n);
+ if(var)
+ return var->qvalue;
+ return nil;
+}
+
+void
+setvariable(char *s, int n, char *val, char *qval)
+{
+ Var *var;
+
+ var = lookupvariable(s, n);
+ if(var){
+ free(var->value);
+ free(var->qvalue);
+ }else{
+ vars = erealloc(vars, (nvars+1)*sizeof(Var));
+ var = vars+nvars++;
+ var->name = emalloc(n+1);
+ memmove(var->name, s, n);
+ }
+ var->value = estrdup(val);
+ var->qvalue = estrdup(qval);
+}
+
+static char*
+nonnil(char *s)
+{
+ if(s == nil)
+ return "";
+ return s;
+}
+
+static char*
+filename(Exec *e, char *name)
+{
+ static char *buf; /* rock to hold value so we don't leak the strings */
+
+ free(buf);
+ /* if name is defined, used it */
+ if(name!=nil && name[0]!='\0'){
+ buf = estrdup(name);
+ return cleanname(buf);
+ }
+ /* if data is an absolute file name, or wdir is empty, use it */
+ if(e->msg->data[0]=='/' || e->msg->wdir==nil || e->msg->wdir[0]=='\0'){
+ buf = estrdup(e->msg->data);
+ return cleanname(buf);
+ }
+ buf = emalloc(strlen(e->msg->wdir)+1+strlen(e->msg->data)+1);
+ sprint(buf, "%s/%s", e->msg->wdir, e->msg->data);
+ return cleanname(buf);
+}
+
+char*
+dollar(Exec *e, char *s, int *namelen)
+{
+ int n;
+ static char *abuf;
+ char *t;
+
+ *namelen = 1;
+ if(e!=nil && '0'<=s[0] && s[0]<='9')
+ return nonnil(e->match[s[0]-'0']);
+
+ for(t=s; isalnum(*t); t++)
+ ;
+ n = t-s;
+ *namelen = n;
+
+ if(e != nil){
+ if(n == 3){
+ if(memcmp(s, "src", 3) == 0)
+ return nonnil(e->msg->src);
+ if(memcmp(s, "dst", 3) == 0)
+ return nonnil(e->msg->dst);
+ if(memcmp(s, "dir", 3) == 0)
+ return filename(e, e->dir);
+ }
+ if(n == 4){
+ if(memcmp(s, "attr", 4) == 0){
+ free(abuf);
+ abuf = plumbpackattr(e->msg->attr);
+ return nonnil(abuf);
+ }
+ if(memcmp(s, "data", 4) == 0)
+ return nonnil(e->msg->data);
+ if(memcmp(s, "file", 4) == 0)
+ return filename(e, e->file);
+ if(memcmp(s, "type", 4) == 0)
+ return nonnil(e->msg->type);
+ if(memcmp(s, "wdir", 3) == 0)
+ return nonnil(e->msg->wdir);
+ }
+ }
+
+ return variable(s, n);
+}
+
+/* expand one blank-terminated string, processing quotes and $ signs */
+char*
+expand(Exec *e, char *s, char **ends)
+{
+ char *p, *ep, *val;
+ int namelen, quoting;
+
+ p = ebuf;
+ ep = ebuf+sizeof ebuf-1;
+ quoting = 0;
+ while(p<ep && *s!='\0' && (quoting || (*s!=' ' && *s!='\t'))){
+ if(*s == '\''){
+ s++;
+ if(!quoting)
+ quoting = 1;
+ else if(*s == '\''){
+ *p++ = '\'';
+ s++;
+ }else
+ quoting = 0;
+ continue;
+ }
+ if(quoting || *s!='$'){
+ *p++ = *s++;
+ continue;
+ }
+ s++;
+ val = dollar(e, s, &namelen);
+ if(val == nil){
+ *p++ = '$';
+ continue;
+ }
+ if(ep-p < strlen(val))
+ return "string-too-long";
+ strcpy(p, val);
+ p += strlen(val);
+ s += namelen;
+ }
+ if(ends)
+ *ends = s;
+ *p = '\0';
+ return ebuf;
+}
+
+void
+regerror(char *msg)
+{
+ if(parsing){
+ parsing = 0;
+ parseerror("%s", msg);
+ }
+ error("%s", msg);
+}
+
+void
+parserule(Rule *r)
+{
+ r->qarg = estrdup(expand(nil, r->arg, nil));
+ switch(r->obj){
+ case OArg:
+ case OAttr:
+ case OData:
+ case ODst:
+ case OType:
+ case OWdir:
+ case OSrc:
+ if(r->verb==VClient || r->verb==VStart || r->verb==VTo)
+ parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
+ if(r->obj!=OAttr && (r->verb==VAdd || r->verb==VDelete))
+ parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
+ if(r->verb == VMatches){
+ r->regex = regcomp(r->qarg);
+ return;
+ }
+ break;
+ case OPlumb:
+ if(r->verb!=VClient && r->verb!=VStart && r->verb!=VTo)
+ parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
+ break;
+ }
+}
+
+int
+assignment(char *p)
+{
+ char *var, *qval;
+ int n;
+
+ if(!isalpha(p[0]))
+ return 0;
+ for(var=p; isalnum(*p); p++)
+ ;
+ n = p-var;
+ while(*p==' ' || *p=='\t')
+ p++;
+ if(*p++ != '=')
+ return 0;
+ while(*p==' ' || *p=='\t')
+ p++;
+ qval = expand(nil, p, nil);
+ setvariable(var, n, p, qval);
+ return 1;
+}
+
+int
+include(char *s)
+{
+ char *t, *args[3], buf[128];
+ int n, fd;
+
+ if(strncmp(s, "include", 7) != 0)
+ return 0;
+ /* either an include or an error */
+ n = tokenize(s, args, nelem(args));
+ if(n < 2)
+ goto Err;
+ if(strcmp(args[0], "include") != 0)
+ goto Err;
+ if(args[1][0] == '#')
+ goto Err;
+ if(n>2 && args[2][0] != '#')
+ goto Err;
+ t = args[1];
+ fd = open(t, OREAD);
+ if(fd<0 && t[0]!='/' && strncmp(t, "./", 2)!=0 && strncmp(t, "../", 3)!=0){
+ snprint(buf, sizeof buf, "/sys/lib/plumb/%s", t);
+ t = buf;
+ fd = open(t, OREAD);
+ }
+ if(fd < 0)
+ parseerror("can't open %s for inclusion", t);
+ pushinput(t, fd, nil);
+ return 1;
+
+ Err:
+ parseerror("malformed include statement");
+ return 0;
+}
+
+Rule*
+readrule(int *eof)
+{
+ Rule *rp;
+ char *line, *p;
+ char *word;
+
+Top:
+ line = getline();
+ if(line == nil){
+ /*
+ * if input is from string, and bytes remain (input->end is within string),
+ * morerules() will pop input and save remaining data. otherwise pop
+ * the stack here, and if there's more input, keep reading.
+ */
+ if((input!=nil && input->end==nil) && popinput())
+ goto Top;
+ *eof = 1;
+ return nil;
+ }
+ input->lineno++;
+
+ for(p=line; *p==' ' || *p=='\t'; p++)
+ ;
+ if(*p=='\0' || *p=='#') /* empty or comment line */
+ return nil;
+
+ if(include(p))
+ goto Top;
+
+ if(assignment(p))
+ return nil;
+
+ rp = emalloc(sizeof(Rule));
+
+ /* object */
+ for(word=p; *p!=' ' && *p!='\t'; p++)
+ if(*p == '\0')
+ parseerror("malformed rule");
+ *p++ = '\0';
+ rp->obj = lookup(word, objects);
+ if(rp->obj < 0){
+ if(strcmp(word, "kind") == 0) /* backwards compatibility */
+ rp->obj = OType;
+ else
+ parseerror("unknown object %s", word);
+ }
+
+ /* verb */
+ while(*p==' ' || *p=='\t')
+ p++;
+ for(word=p; *p!=' ' && *p!='\t'; p++)
+ if(*p == '\0')
+ parseerror("malformed rule");
+ *p++ = '\0';
+ rp->verb = lookup(word, verbs);
+ if(rp->verb < 0)
+ parseerror("unknown verb %s", word);
+
+ /* argument */
+ while(*p==' ' || *p=='\t')
+ p++;
+ if(*p == '\0')
+ parseerror("malformed rule");
+ rp->arg = estrdup(p);
+
+ parserule(rp);
+
+ return rp;
+}
+
+void
+freerule(Rule *r)
+{
+ free(r->arg);
+ free(r->qarg);
+ free(r->regex);
+}
+
+void
+freerules(Rule **r)
+{
+ while(*r)
+ freerule(*r++);
+}
+
+void
+freeruleset(Ruleset *rs)
+{
+ freerules(rs->pat);
+ free(rs->pat);
+ freerules(rs->act);
+ free(rs->act);
+ free(rs->port);
+ free(rs);
+}
+
+Ruleset*
+readruleset(void)
+{
+ Ruleset *rs;
+ Rule *r;
+ int eof, inrule, i, ncmd;
+
+ Again:
+ eof = 0;
+ rs = emalloc(sizeof(Ruleset));
+ rs->pat = emalloc(sizeof(Rule*));
+ rs->act = emalloc(sizeof(Rule*));
+ inrule = 0;
+ ncmd = 0;
+ for(;;){
+ r = readrule(&eof);
+ if(eof)
+ break;
+ if(r==nil){
+ if(inrule)
+ break;
+ continue;
+ }
+ inrule = 1;
+ switch(r->obj){
+ case OArg:
+ case OAttr:
+ case OData:
+ case ODst:
+ case OType:
+ case OWdir:
+ case OSrc:
+ rs->npat++;
+ rs->pat = erealloc(rs->pat, (rs->npat+1)*sizeof(Rule*));
+ rs->pat[rs->npat-1] = r;
+ rs->pat[rs->npat] = nil;
+ break;
+ case OPlumb:
+ rs->nact++;
+ rs->act = erealloc(rs->act, (rs->nact+1)*sizeof(Rule*));
+ rs->act[rs->nact-1] = r;
+ rs->act[rs->nact] = nil;
+ if(r->verb == VTo){
+ if(rs->npat>0 && rs->port != nil) /* npat==0 implies port declaration */
+ parseerror("too many ports");
+ if(lookup(r->qarg, badports) >= 0)
+ parseerror("illegal port name %s", r->qarg);
+ rs->port = estrdup(r->qarg);
+ }else
+ ncmd++; /* start or client rule */
+ break;
+ }
+ }
+ if(ncmd > 1){
+ freeruleset(rs);
+ parseerror("ruleset has more than one client or start action");
+ }
+ if(rs->npat>0 && rs->nact>0)
+ return rs;
+ if(rs->npat==0 && rs->nact==0){
+ freeruleset(rs);
+ return nil;
+ }
+ if(rs->nact==0 || rs->port==nil){
+ freeruleset(rs);
+ parseerror("ruleset must have patterns and actions");
+ return nil;
+ }
+
+ /* declare ports */
+ for(i=0; i<rs->nact; i++)
+ if(rs->act[i]->verb != VTo){
+ freeruleset(rs);
+ parseerror("ruleset must have actions");
+ return nil;
+ }
+ for(i=0; i<rs->nact; i++)
+ addport(rs->act[i]->qarg);
+ freeruleset(rs);
+ goto Again;
+}
+
+Ruleset**
+readrules(char *name, int fd)
+{
+ Ruleset *rs, **rules;
+ int n;
+
+ parsing = 1;
+ pushinput(name, fd, nil);
+ rules = emalloc(sizeof(Ruleset*));
+ for(n=0; (rs=readruleset())!=nil; n++){
+ rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
+ rules[n] = rs;
+ rules[n+1] = nil;
+ }
+ popinput();
+ parsing = 0;
+ return rules;
+}
+
+char*
+concat(char *s, char *t)
+{
+ if(t == nil)
+ return s;
+ if(s == nil)
+ s = estrdup(t);
+ else{
+ s = erealloc(s, strlen(s)+strlen(t)+1);
+ strcat(s, t);
+ }
+ return s;
+}
+
+char*
+printpat(Rule *r)
+{
+ char *s;
+
+ s = emalloc(strlen(objects[r->obj])+1+strlen(verbs[r->verb])+1+strlen(r->arg)+1+1);
+ sprint(s, "%s\t%s\t%s\n", objects[r->obj], verbs[r->verb], r->arg);
+ return s;
+}
+
+char*
+printvar(Var *v)
+{
+ char *s;
+
+ s = emalloc(strlen(v->name)+1+strlen(v->value)+2+1);
+ sprint(s, "%s=%s\n\n", v->name, v->value);
+ return s;
+}
+
+char*
+printrule(Ruleset *r)
+{
+ int i;
+ char *s;
+
+ s = nil;
+ for(i=0; i<r->npat; i++)
+ s = concat(s, printpat(r->pat[i]));
+ for(i=0; i<r->nact; i++)
+ s = concat(s, printpat(r->act[i]));
+ s = concat(s, "\n");
+ return s;
+}
+
+char*
+printport(char *port)
+{
+ char *s;
+
+ s = nil;
+ s = concat(s, "plumb to ");
+ s = concat(s, port);
+ s = concat(s, "\n");
+ return s;
+}
+
+char*
+printrules(void)
+{
+ int i;
+ char *s;
+
+ s = nil;
+ for(i=0; i<nvars; i++)
+ s = concat(s, printvar(&vars[i]));
+ for(i=0; i<nports; i++)
+ s = concat(s, printport(ports[i]));
+ s = concat(s, "\n");
+ for(i=0; rules[i]; i++)
+ s = concat(s, printrule(rules[i]));
+ return s;
+}
+
+char*
+stringof(char *s, int n)
+{
+ char *t;
+
+ t = emalloc(n+1);
+ memmove(t, s, n);
+ return t;
+}
+
+uchar*
+morerules(uchar *text, int done)
+{
+ int n;
+ Ruleset *rs;
+ uchar *otext, *s, *endofrule;
+
+ pushinput("<rules input>", -1, text);
+ if(done)
+ input->end = text+strlen((char*)text);
+ else{
+ /*
+ * Help user by sending any full rules to parser so any parse errors will
+ * occur on write rather than close. A heuristic will do: blank line ends rule.
+ */
+ endofrule = nil;
+ for(s=text; *s!='\0'; s++)
+ if(*s=='\n' && *++s=='\n')
+ endofrule = s+1;
+ if(endofrule == nil)
+ return text;
+ input->end = endofrule;
+ }
+ for(n=0; rules[n]; n++)
+ ;
+ while((rs=readruleset()) != nil){
+ rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
+ rules[n++] = rs;
+ rules[n] = nil;
+ }
+ otext =text;
+ if(input == nil)
+ text = (uchar*)estrdup("");
+ else
+ text = (uchar*)estrdup((char*)input->end);
+ popinput();
+ free(otext);
+ return text;
+}
+
+char*
+writerules(char *s, int n)
+{
+ static uchar *text;
+ char *tmp;
+
+ free(lasterror);
+ lasterror = nil;
+ parsing = 1;
+ if(setjmp(parsejmp) == 0){
+ tmp = stringof(s, n);
+ text = (uchar*)concat((char*)text, tmp);
+ free(tmp);
+ text = morerules(text, s==nil);
+ }
+ if(s == nil){
+ free(text);
+ text = nil;
+ }
+ parsing = 0;
+ makeports(rules);
+ return lasterror;
+}