Plan 9 version, nothing tweaked yet.

This commit is contained in:
rsc 2003-11-23 17:58:26 +00:00
parent 7763a61a35
commit b8c14089d8
7 changed files with 2596 additions and 0 deletions

975
src/cmd/plumb/fsys.c Normal file
View file

@ -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;
}

463
src/cmd/plumb/match.c Normal file
View file

@ -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;
}

20
src/cmd/plumb/mkfile Normal file
View file

@ -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

119
src/cmd/plumb/plumb.c Normal file
View file

@ -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);
}

147
src/cmd/plumb/plumber.c Normal file
View file

@ -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;
}

93
src/cmd/plumb/plumber.h Normal file
View file

@ -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;

779
src/cmd/plumb/rules.c Normal file
View file

@ -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;
}