commit b8c14089d8f4be73a908f82f62fce80ed2c14a8d from: rsc date: Sun Nov 23 17:58:26 2003 UTC Plan 9 version, nothing tweaked yet. commit - 7763a61a3582ef330bca54f225e8ec5325fbd35e commit + b8c14089d8f4be73a908f82f62fce80ed2c14a8d blob - /dev/null blob + 6f95a23a8ab6b9d354f809fe41ca29270e2c510e (mode 644) --- /dev/null +++ src/cmd/plumb/fsys.c @@ -0,0 +1,975 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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; itype] == 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; infid; 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; infid; 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; idst, 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; ioldtag); + 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; inwname; 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.nwqidnwname){ + 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= 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; +} blob - /dev/null blob + 42a9232ff77a245681a9e294e09e2f18de8a7504 (mode 644) --- /dev/null +++ src/cmd/plumb/match.c @@ -0,0 +1,463 @@ +#include +#include +#include +#include +#include +#include +#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; iattr, "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; inpat; 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= 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; inact; 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; +} blob - /dev/null blob + d6a146541af7fba17ccb02ce4329d7bcd78ea783 (mode 644) --- /dev/null +++ src/cmd/plumb/mkfile @@ -0,0 +1,20 @@ +syms + 8c -aa fsys.c match.c rules.c >>syms blob - /dev/null blob + e0cff91279529ac1f68fa230001e1c1d6e763242 (mode 644) --- /dev/null +++ src/cmd/plumb/plumb.c @@ -0,0 +1,119 @@ +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#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; +} blob - /dev/null blob + 0d1205f988acdf04ae3d7e1ed02b176948ad500d (mode 644) --- /dev/null +++ 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; blob - /dev/null blob + 262f6d67f53928c16aeccc2964e46cc974dc3fbe (mode 644) --- /dev/null +++ src/cmd/plumb/rules.c @@ -0,0 +1,779 @@ +#include +#include +#include +#include +#include +#include +#include +#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; iqvalue; + 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(pqarg = 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; inact; i++) + if(rs->act[i]->verb != VTo){ + freeruleset(rs); + parseerror("ruleset must have actions"); + return nil; + } + for(i=0; inact; 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; inpat; i++) + s = concat(s, printpat(r->pat[i])); + for(i=0; inact; 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", -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; +}