diff --git a/sys/man/4/ptrap b/sys/man/4/ptrap new file mode 100644 index 000000000..570cce83a --- /dev/null +++ b/sys/man/4/ptrap @@ -0,0 +1,75 @@ +.TH PTRAP 4 +.SH NAME +ptrap \- \fIplumber\fR(4) filter +.SH SYNOPSIS +.B ptrap +.I port +[\fB!\fR]\fIregexp\fR +[ +.I port +[\fB!\fR]\fIregexp\fR ... +] +.SH DESCRIPTION +.I Ptrap +is a program that mounts itself over a +.IR plumber (4) +service mounted at +.B /mnt/plumb +and filters incoming messages according to the rules provided on the command line. +.PP +.I Ptrap +accepts an arbitrary number of argument pairs; each pair consists of a port name +.I port +and a regular expression +.I regexp +(see +.IR regexp (6)). +Each incoming message that does not match +.I regexp +is discarded. +The +.I regexp +can be optionally prefixed by +.B ! +to indicate logical inversion (i.e. messages matching the regexp are discarded). +.SH EXAMPLES +Start a +.IR sam (1) +instance dedicated to editing kernel source code: +.IP +.EX +ptrap edit '^/sys/src/9/' +sam +.EE +.PP +In another window, start a second +.IR sam (1) +instance for all other editing jobs: +.IP +.EX +ptrap edit '!^/sys/src/9/' +sam +.EE +.SH SOURCE +.B /sys/src/cmd/ptrap.c +.SH SEE ALSO +.IR plumber (4), +.IR plumb (6) +.SH BUGS +Multiple filters specified on the same port ignore all but the last one. +.PP +.I Ptrap +would be more useful if it could inhibit sending the message to other clients. +.PP +As far as +.IR plumber (4) +is concerned, even messages dropped by +.I ptrap +are "accepted", which means rules that are supposed to apply to messages not accepted by clients are not invoked (e.g. a rule starting an editor if no one is listening to the +.I edit +port will not work if there is a +.I ptrap +on that port). +.SH HISTORY +.I Ptrap +first appeared in 9front (February, 2018). diff --git a/sys/src/cmd/ptrap.c b/sys/src/cmd/ptrap.c new file mode 100644 index 000000000..f7b43515d --- /dev/null +++ b/sys/src/cmd/ptrap.c @@ -0,0 +1,375 @@ +#include +#include +#include +#include +#include <9p.h> +#include +#include + +typedef struct IOProc IOProc; +typedef struct PFilter PFilter; +typedef struct PFid PFid; + +struct IOProc { + int id; + Channel *ch; + IOProc *next; +}; +QLock freellock; +IOProc *freel; + +struct PFid { + char *name; + PFilter *filter; + int fd; + char *msg; + int msgn, msgp; +}; +Qid rootqid = {.type QTDIR}; + +struct PFilter { + char *name; + Reprog *filt; + PFilter *next; + int invert; +}; +PFilter *filters; + +static char * +fname(char *n) +{ + static char *last; + + if(last != nil){ + free(last); + last = nil; + } + if(n == nil) + return "/mnt/plumb"; + last = smprint("/mnt/plumb/%s", n); + return last; +} + +static void theioproc(void *); +static IOProc * +getioproc(void) +{ + IOProc *p; + + qlock(&freellock); + p = freel; + if(p != nil) + freel = freel->next; + qunlock(&freellock); + if(p == nil){ + p = emalloc9p(sizeof(IOProc)); + p->ch = chancreate(sizeof(Req*), 0); + p->id = proccreate(theioproc, p, 4096); + } + return p; +} + +static void +putioproc(IOProc *p) +{ + qlock(&freellock); + p->next = freel; + freel = p; + qunlock(&freellock); +} + +static void +ptrapattach(Req *r) +{ + PFid *pf; + + pf = emalloc9p(sizeof(PFid)); + pf->fd = -1; + r->fid->aux = pf; + r->ofcall.qid = rootqid; + r->fid->qid = rootqid; + respond(r, nil); +} + +static char * +ptrapclone(Fid *old, Fid *new) +{ + PFid *pf; + + pf = emalloc9p(sizeof(PFid)); + memcpy(pf, old->aux, sizeof(PFid)); + new->aux = pf; + return nil; +} + +static void +ptrapdestroyfid(Fid *f) +{ + PFid *pf; + + pf = f->aux; + if(pf == nil) return; + free(pf->name); + if(pf->fd >= 0) + close(pf->fd); + free(pf); + f->aux = nil; +} + +static char * +ptrapwalk1(Fid *fid, char *name, Qid *qid) +{ + PFid *pf; + Dir *d; + static char err[ERRMAX]; + + pf = fid->aux; + if(pf->name != nil) + return "phase error"; + d = dirstat(fname(name)); + if(d == nil){ + rerrstr(err, ERRMAX); + return err; + } + pf->name = strdup(name); + fid->qid = d->qid; + *qid = d->qid; + free(d); + return nil; +} + +static void +ptrapopen(Req *r) +{ + PFid* pf; + PFilter *f; + + pf = r->fid->aux; + pf->fd = open(fname(pf->name), r->ifcall.mode); + if(pf->fd < 0){ + responderror(r); + return; + } + if(pf->name == nil){ + respond(r, nil); + return; + } + for(f = filters; f != nil; f = f->next) + if(strcmp(f->name, pf->name) == 0) + break; + pf->filter = f; + respond(r, nil); +} + +static int +filterread(Req *r, PFid *pf) +{ + int rc, len, more; + char *buf; + Plumbmsg *pm; + + for(;;){ + if(pf->msg != nil){ + rc = r->ifcall.count; + if(pf->msgp + rc >= pf->msgn) + rc = pf->msgn - pf->msgp; + r->ofcall.count = rc; + memmove(r->ofcall.data, pf->msg + pf->msgp, rc); + pf->msgp += rc; + if(pf->msgp >= pf->msgn){ + free(pf->msg); + pf->msg = nil; + } + return 0; + } + buf = emalloc9p(4096); + rc = read(pf->fd, buf, 4096); + if(rc < 0) goto err; + len = rc; + while(pm = plumbunpackpartial(buf, len, &more), pm == nil){ + if(more == 0) goto err; + buf = erealloc9p(buf, len + more); + rc = readn(pf->fd, buf + len, more); + if(rc < 0) goto err; + len += rc; + } + free(buf); + if(regexec(pf->filter->filt, pm->data, nil, 0) ^ pf->filter->invert){ + pf->msg = plumbpack(pm, &pf->msgn); + pf->msgp = 0; + } + plumbfree(pm); + } +err: + free(buf); + return -1; +} + +static void +theioproc(void *iopp) +{ + Req *r; + PFid *pf; + IOProc *iop; + char *buf; + int fd, rc; + + rfork(RFNOTEG); + + buf = smprint("/proc/%d/ctl", getpid()); + fd = open(buf, OWRITE); + free(buf); + + iop = iopp; + for(;;){ + if(fd >= 0) + write(fd, "nointerrupt", 11); + r = recvp(iop->ch); + r->aux = iop; + pf = r->fid->aux; + switch(r->ifcall.type){ + case Tread: + if(!pf->filter){ + rc = pread(pf->fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset); + if(rc < 0){ + responderror(r); + break; + } + r->ofcall.count = rc; + respond(r, nil); + break; + } + if(filterread(r, pf) < 0) + responderror(r); + else + respond(r, nil); + break; + case Twrite: + rc = pwrite(pf->fd, r->ifcall.data, r->ifcall.count, r->ifcall.offset); + if(rc < 0) + responderror(r); + else{ + r->ofcall.count = rc; + respond(r, nil); + } + break; + } + putioproc(iop); + } +} + +static void +ptrapread(Req *r) +{ + IOProc *iop; + + iop = getioproc(); + send(iop->ch, &r); +} + +static void +ptrapwrite(Req *r) +{ + IOProc *iop; + + iop = getioproc(); + send(iop->ch, &r); +} + +static void +ptrapstat(Req *r) +{ + PFid *pf; + Dir *d; + + pf = r->fid->aux; + if(pf->fd >= 0) + d = dirfstat(pf->fd); + else + d = dirstat(fname(pf->name)); + if(d == nil){ + responderror(r); + return; + } + memmove(&r->d, d, sizeof(Dir)); + r->d.name = strdup(d->name); + r->d.uid = strdup(d->uid); + r->d.muid = strdup(d->muid); + r->d.gid = strdup(d->gid); + free(d); + respond(r, nil); +} + +static void +ptrapwstat(Req *r) +{ + PFid *pf; + int rc; + + pf = r->fid->aux; + if(pf->fd >= 0) + rc = dirfwstat(pf->fd, &r->d); + else + rc = dirwstat(fname(pf->name), &r->d); + if(rc < 0) + responderror(r); + else + respond(r, nil); +} + +static void +ptrapflush(Req *r) +{ + if(r->oldreq->aux != nil) + threadint(((IOProc*)r->oldreq->aux)->id); + respond(r, nil); +} + +Srv ptrapsrv = { + .attach = ptrapattach, + .clone = ptrapclone, + .destroyfid = ptrapdestroyfid, + .walk1 = ptrapwalk1, + .open = ptrapopen, + .read = ptrapread, + .write = ptrapwrite, + .stat = ptrapstat, + .wstat = ptrapwstat, + .flush = ptrapflush, +}; + +void +usage(void) +{ + fprint(2, "usage: %s port regex [ port regex ... ]\n", argv0); + exits("usage"); +} + +void +threadmain(int argc, char **argv) +{ + PFilter *f; + char *p; + int i; + + ARGBEGIN{ + default: usage(); + }ARGEND; + + if(argc % 2) usage(); + for(i = 0; i < argc; i += 2){ + f = emalloc9p(sizeof(PFilter)); + f->name = strdup(argv[i]); + p = argv[i+1]; + if(*p == '!'){ + p++; + f->invert = 1; + } + f->filt = regcomp(p); + if(f->filt == nil) + sysfatal("%r"); + f->next = filters; + filters = f; + } + threadpostmountsrv(&ptrapsrv, nil, "/mnt/plumb", MREPL | MCREATE); +}