tPlan 9 version, nothing tweaked yet. - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit b8c14089d8f4be73a908f82f62fce80ed2c14a8d
 (DIR) parent 7763a61a3582ef330bca54f225e8ec5325fbd35e
 (HTM) Author: rsc <devnull@localhost>
       Date:   Sun, 23 Nov 2003 17:58:26 +0000
       
       Plan 9 version, nothing tweaked yet.
       
       Diffstat:
         A src/cmd/plumb/fsys.c                |     975 +++++++++++++++++++++++++++++++
         A src/cmd/plumb/match.c               |     463 +++++++++++++++++++++++++++++++
         A src/cmd/plumb/mkfile                |      20 ++++++++++++++++++++
         A src/cmd/plumb/plumb.c               |     119 +++++++++++++++++++++++++++++++
         A src/cmd/plumb/plumber.c             |     147 +++++++++++++++++++++++++++++++
         A src/cmd/plumb/plumber.h             |      93 +++++++++++++++++++++++++++++++
         A src/cmd/plumb/rules.c               |     779 +++++++++++++++++++++++++++++++
       
       7 files changed, 2596 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/plumb/fsys.c b/src/cmd/plumb/fsys.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/plumb/match.c b/src/cmd/plumb/match.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/plumb/mkfile b/src/cmd/plumb/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/plumb/plumb.c b/src/cmd/plumb/plumb.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/plumb/plumber.c b/src/cmd/plumb/plumber.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/plumb/plumber.h b/src/cmd/plumb/plumber.h
       t@@ -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;
 (DIR) diff --git a/src/cmd/plumb/rules.c b/src/cmd/plumb/rules.c
       t@@ -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;
       +}