aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/plumb/rules.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2003-11-23 17:58:26 +0000
committerrsc <devnull@localhost>2003-11-23 17:58:26 +0000
commitb8c14089d8f4be73a908f82f62fce80ed2c14a8d (patch)
tree1d3db32a1ff576873d44d4bef60f13f020d5e10d /src/cmd/plumb/rules.c
parent7763a61a3582ef330bca54f225e8ec5325fbd35e (diff)
downloadplan9port-b8c14089d8f4be73a908f82f62fce80ed2c14a8d.tar.gz
plan9port-b8c14089d8f4be73a908f82f62fce80ed2c14a8d.tar.bz2
plan9port-b8c14089d8f4be73a908f82f62fce80ed2c14a8d.zip
Plan 9 version, nothing tweaked yet.
Diffstat (limited to 'src/cmd/plumb/rules.c')
-rw-r--r--src/cmd/plumb/rules.c779
1 files changed, 779 insertions, 0 deletions
diff --git a/src/cmd/plumb/rules.c b/src/cmd/plumb/rules.c
new file mode 100644
index 00000000..262f6d67
--- /dev/null
+++ b/src/cmd/plumb/rules.c
@@ -0,0 +1,779 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <regexp.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "plumber.h"
+
+typedef struct Input Input;
+typedef struct Var Var;
+
+struct Input
+{
+ char *file; /* name of file */
+ Biobuf *fd; /* input buffer, if from real file */
+ uchar *s; /* input string, if from /mnt/plumb/rules */
+ uchar *end; /* end of input string */
+ int lineno;
+ Input *next; /* file to read after EOF on this one */
+};
+
+struct Var
+{
+ char *name;
+ char *value;
+ char *qvalue;
+};
+
+static int parsing;
+static int nvars;
+static Var *vars;
+static Input *input;
+
+static char ebuf[4096];
+
+char *badports[] =
+{
+ ".",
+ "..",
+ "send",
+ nil
+};
+
+char *objects[] =
+{
+ "arg",
+ "attr",
+ "data",
+ "dst",
+ "plumb",
+ "src",
+ "type",
+ "wdir",
+ nil
+};
+
+char *verbs[] =
+{
+ "add",
+ "client",
+ "delete",
+ "is",
+ "isdir",
+ "isfile",
+ "matches",
+ "set",
+ "start",
+ "to",
+ nil
+};
+
+static void
+printinputstackrev(Input *in)
+{
+ if(in == nil)
+ return;
+ printinputstackrev(in->next);
+ fprint(2, "%s:%d: ", in->file, in->lineno);
+}
+
+void
+printinputstack(void)
+{
+ printinputstackrev(input);
+}
+
+static void
+pushinput(char *name, int fd, uchar *str)
+{
+ Input *in;
+ int depth;
+
+ depth = 0;
+ for(in=input; in; in=in->next)
+ if(depth++ >= 10) /* prevent deep C stack in plumber and bad include structure */
+ parseerror("include stack too deep; max 10");
+
+ in = emalloc(sizeof(Input));
+ in->file = estrdup(name);
+ in->next = input;
+ input = in;
+ if(str)
+ in->s = str;
+ else{
+ in->fd = emalloc(sizeof(Biobuf));
+ if(Binit(in->fd, fd, OREAD) < 0)
+ parseerror("can't initialize Bio for rules file: %r");
+ }
+
+}
+
+int
+popinput(void)
+{
+ Input *in;
+
+ in = input;
+ if(in == nil)
+ return 0;
+ input = in->next;
+ if(in->fd){
+ Bterm(in->fd);
+ free(in->fd);
+ }
+ free(in);
+ return 1;
+}
+
+int
+getc(void)
+{
+ if(input == nil)
+ return Beof;
+ if(input->fd)
+ return Bgetc(input->fd);
+ if(input->s < input->end)
+ return *(input->s)++;
+ return -1;
+}
+
+char*
+getline(void)
+{
+ static int n = 0;
+ static char *s, *incl;
+ int c, i;
+
+ i = 0;
+ for(;;){
+ c = getc();
+ if(c < 0)
+ return nil;
+ if(i == n){
+ n += 100;
+ s = erealloc(s, n);
+ }
+ if(c<0 || c=='\0' || c=='\n')
+ break;
+ s[i++] = c;
+ }
+ s[i] = '\0';
+ return s;
+}
+
+int
+lookup(char *s, char *tab[])
+{
+ int i;
+
+ for(i=0; tab[i]!=nil; i++)
+ if(strcmp(s, tab[i])==0)
+ return i;
+ return -1;
+}
+
+Var*
+lookupvariable(char *s, int n)
+{
+ int i;
+
+ for(i=0; i<nvars; i++)
+ if(n==strlen(vars[i].name) && memcmp(s, vars[i].name, n)==0)
+ return vars+i;
+ return nil;
+}
+
+char*
+variable(char *s, int n)
+{
+ Var *var;
+
+ var = lookupvariable(s, n);
+ if(var)
+ return var->qvalue;
+ return nil;
+}
+
+void
+setvariable(char *s, int n, char *val, char *qval)
+{
+ Var *var;
+
+ var = lookupvariable(s, n);
+ if(var){
+ free(var->value);
+ free(var->qvalue);
+ }else{
+ vars = erealloc(vars, (nvars+1)*sizeof(Var));
+ var = vars+nvars++;
+ var->name = emalloc(n+1);
+ memmove(var->name, s, n);
+ }
+ var->value = estrdup(val);
+ var->qvalue = estrdup(qval);
+}
+
+static char*
+nonnil(char *s)
+{
+ if(s == nil)
+ return "";
+ return s;
+}
+
+static char*
+filename(Exec *e, char *name)
+{
+ static char *buf; /* rock to hold value so we don't leak the strings */
+
+ free(buf);
+ /* if name is defined, used it */
+ if(name!=nil && name[0]!='\0'){
+ buf = estrdup(name);
+ return cleanname(buf);
+ }
+ /* if data is an absolute file name, or wdir is empty, use it */
+ if(e->msg->data[0]=='/' || e->msg->wdir==nil || e->msg->wdir[0]=='\0'){
+ buf = estrdup(e->msg->data);
+ return cleanname(buf);
+ }
+ buf = emalloc(strlen(e->msg->wdir)+1+strlen(e->msg->data)+1);
+ sprint(buf, "%s/%s", e->msg->wdir, e->msg->data);
+ return cleanname(buf);
+}
+
+char*
+dollar(Exec *e, char *s, int *namelen)
+{
+ int n;
+ static char *abuf;
+ char *t;
+
+ *namelen = 1;
+ if(e!=nil && '0'<=s[0] && s[0]<='9')
+ return nonnil(e->match[s[0]-'0']);
+
+ for(t=s; isalnum(*t); t++)
+ ;
+ n = t-s;
+ *namelen = n;
+
+ if(e != nil){
+ if(n == 3){
+ if(memcmp(s, "src", 3) == 0)
+ return nonnil(e->msg->src);
+ if(memcmp(s, "dst", 3) == 0)
+ return nonnil(e->msg->dst);
+ if(memcmp(s, "dir", 3) == 0)
+ return filename(e, e->dir);
+ }
+ if(n == 4){
+ if(memcmp(s, "attr", 4) == 0){
+ free(abuf);
+ abuf = plumbpackattr(e->msg->attr);
+ return nonnil(abuf);
+ }
+ if(memcmp(s, "data", 4) == 0)
+ return nonnil(e->msg->data);
+ if(memcmp(s, "file", 4) == 0)
+ return filename(e, e->file);
+ if(memcmp(s, "type", 4) == 0)
+ return nonnil(e->msg->type);
+ if(memcmp(s, "wdir", 3) == 0)
+ return nonnil(e->msg->wdir);
+ }
+ }
+
+ return variable(s, n);
+}
+
+/* expand one blank-terminated string, processing quotes and $ signs */
+char*
+expand(Exec *e, char *s, char **ends)
+{
+ char *p, *ep, *val;
+ int namelen, quoting;
+
+ p = ebuf;
+ ep = ebuf+sizeof ebuf-1;
+ quoting = 0;
+ while(p<ep && *s!='\0' && (quoting || (*s!=' ' && *s!='\t'))){
+ if(*s == '\''){
+ s++;
+ if(!quoting)
+ quoting = 1;
+ else if(*s == '\''){
+ *p++ = '\'';
+ s++;
+ }else
+ quoting = 0;
+ continue;
+ }
+ if(quoting || *s!='$'){
+ *p++ = *s++;
+ continue;
+ }
+ s++;
+ val = dollar(e, s, &namelen);
+ if(val == nil){
+ *p++ = '$';
+ continue;
+ }
+ if(ep-p < strlen(val))
+ return "string-too-long";
+ strcpy(p, val);
+ p += strlen(val);
+ s += namelen;
+ }
+ if(ends)
+ *ends = s;
+ *p = '\0';
+ return ebuf;
+}
+
+void
+regerror(char *msg)
+{
+ if(parsing){
+ parsing = 0;
+ parseerror("%s", msg);
+ }
+ error("%s", msg);
+}
+
+void
+parserule(Rule *r)
+{
+ r->qarg = estrdup(expand(nil, r->arg, nil));
+ switch(r->obj){
+ case OArg:
+ case OAttr:
+ case OData:
+ case ODst:
+ case OType:
+ case OWdir:
+ case OSrc:
+ if(r->verb==VClient || r->verb==VStart || r->verb==VTo)
+ parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
+ if(r->obj!=OAttr && (r->verb==VAdd || r->verb==VDelete))
+ parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
+ if(r->verb == VMatches){
+ r->regex = regcomp(r->qarg);
+ return;
+ }
+ break;
+ case OPlumb:
+ if(r->verb!=VClient && r->verb!=VStart && r->verb!=VTo)
+ parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
+ break;
+ }
+}
+
+int
+assignment(char *p)
+{
+ char *var, *qval;
+ int n;
+
+ if(!isalpha(p[0]))
+ return 0;
+ for(var=p; isalnum(*p); p++)
+ ;
+ n = p-var;
+ while(*p==' ' || *p=='\t')
+ p++;
+ if(*p++ != '=')
+ return 0;
+ while(*p==' ' || *p=='\t')
+ p++;
+ qval = expand(nil, p, nil);
+ setvariable(var, n, p, qval);
+ return 1;
+}
+
+int
+include(char *s)
+{
+ char *t, *args[3], buf[128];
+ int n, fd;
+
+ if(strncmp(s, "include", 7) != 0)
+ return 0;
+ /* either an include or an error */
+ n = tokenize(s, args, nelem(args));
+ if(n < 2)
+ goto Err;
+ if(strcmp(args[0], "include") != 0)
+ goto Err;
+ if(args[1][0] == '#')
+ goto Err;
+ if(n>2 && args[2][0] != '#')
+ goto Err;
+ t = args[1];
+ fd = open(t, OREAD);
+ if(fd<0 && t[0]!='/' && strncmp(t, "./", 2)!=0 && strncmp(t, "../", 3)!=0){
+ snprint(buf, sizeof buf, "/sys/lib/plumb/%s", t);
+ t = buf;
+ fd = open(t, OREAD);
+ }
+ if(fd < 0)
+ parseerror("can't open %s for inclusion", t);
+ pushinput(t, fd, nil);
+ return 1;
+
+ Err:
+ parseerror("malformed include statement");
+ return 0;
+}
+
+Rule*
+readrule(int *eof)
+{
+ Rule *rp;
+ char *line, *p;
+ char *word;
+
+Top:
+ line = getline();
+ if(line == nil){
+ /*
+ * if input is from string, and bytes remain (input->end is within string),
+ * morerules() will pop input and save remaining data. otherwise pop
+ * the stack here, and if there's more input, keep reading.
+ */
+ if((input!=nil && input->end==nil) && popinput())
+ goto Top;
+ *eof = 1;
+ return nil;
+ }
+ input->lineno++;
+
+ for(p=line; *p==' ' || *p=='\t'; p++)
+ ;
+ if(*p=='\0' || *p=='#') /* empty or comment line */
+ return nil;
+
+ if(include(p))
+ goto Top;
+
+ if(assignment(p))
+ return nil;
+
+ rp = emalloc(sizeof(Rule));
+
+ /* object */
+ for(word=p; *p!=' ' && *p!='\t'; p++)
+ if(*p == '\0')
+ parseerror("malformed rule");
+ *p++ = '\0';
+ rp->obj = lookup(word, objects);
+ if(rp->obj < 0){
+ if(strcmp(word, "kind") == 0) /* backwards compatibility */
+ rp->obj = OType;
+ else
+ parseerror("unknown object %s", word);
+ }
+
+ /* verb */
+ while(*p==' ' || *p=='\t')
+ p++;
+ for(word=p; *p!=' ' && *p!='\t'; p++)
+ if(*p == '\0')
+ parseerror("malformed rule");
+ *p++ = '\0';
+ rp->verb = lookup(word, verbs);
+ if(rp->verb < 0)
+ parseerror("unknown verb %s", word);
+
+ /* argument */
+ while(*p==' ' || *p=='\t')
+ p++;
+ if(*p == '\0')
+ parseerror("malformed rule");
+ rp->arg = estrdup(p);
+
+ parserule(rp);
+
+ return rp;
+}
+
+void
+freerule(Rule *r)
+{
+ free(r->arg);
+ free(r->qarg);
+ free(r->regex);
+}
+
+void
+freerules(Rule **r)
+{
+ while(*r)
+ freerule(*r++);
+}
+
+void
+freeruleset(Ruleset *rs)
+{
+ freerules(rs->pat);
+ free(rs->pat);
+ freerules(rs->act);
+ free(rs->act);
+ free(rs->port);
+ free(rs);
+}
+
+Ruleset*
+readruleset(void)
+{
+ Ruleset *rs;
+ Rule *r;
+ int eof, inrule, i, ncmd;
+
+ Again:
+ eof = 0;
+ rs = emalloc(sizeof(Ruleset));
+ rs->pat = emalloc(sizeof(Rule*));
+ rs->act = emalloc(sizeof(Rule*));
+ inrule = 0;
+ ncmd = 0;
+ for(;;){
+ r = readrule(&eof);
+ if(eof)
+ break;
+ if(r==nil){
+ if(inrule)
+ break;
+ continue;
+ }
+ inrule = 1;
+ switch(r->obj){
+ case OArg:
+ case OAttr:
+ case OData:
+ case ODst:
+ case OType:
+ case OWdir:
+ case OSrc:
+ rs->npat++;
+ rs->pat = erealloc(rs->pat, (rs->npat+1)*sizeof(Rule*));
+ rs->pat[rs->npat-1] = r;
+ rs->pat[rs->npat] = nil;
+ break;
+ case OPlumb:
+ rs->nact++;
+ rs->act = erealloc(rs->act, (rs->nact+1)*sizeof(Rule*));
+ rs->act[rs->nact-1] = r;
+ rs->act[rs->nact] = nil;
+ if(r->verb == VTo){
+ if(rs->npat>0 && rs->port != nil) /* npat==0 implies port declaration */
+ parseerror("too many ports");
+ if(lookup(r->qarg, badports) >= 0)
+ parseerror("illegal port name %s", r->qarg);
+ rs->port = estrdup(r->qarg);
+ }else
+ ncmd++; /* start or client rule */
+ break;
+ }
+ }
+ if(ncmd > 1){
+ freeruleset(rs);
+ parseerror("ruleset has more than one client or start action");
+ }
+ if(rs->npat>0 && rs->nact>0)
+ return rs;
+ if(rs->npat==0 && rs->nact==0){
+ freeruleset(rs);
+ return nil;
+ }
+ if(rs->nact==0 || rs->port==nil){
+ freeruleset(rs);
+ parseerror("ruleset must have patterns and actions");
+ return nil;
+ }
+
+ /* declare ports */
+ for(i=0; i<rs->nact; i++)
+ if(rs->act[i]->verb != VTo){
+ freeruleset(rs);
+ parseerror("ruleset must have actions");
+ return nil;
+ }
+ for(i=0; i<rs->nact; i++)
+ addport(rs->act[i]->qarg);
+ freeruleset(rs);
+ goto Again;
+}
+
+Ruleset**
+readrules(char *name, int fd)
+{
+ Ruleset *rs, **rules;
+ int n;
+
+ parsing = 1;
+ pushinput(name, fd, nil);
+ rules = emalloc(sizeof(Ruleset*));
+ for(n=0; (rs=readruleset())!=nil; n++){
+ rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
+ rules[n] = rs;
+ rules[n+1] = nil;
+ }
+ popinput();
+ parsing = 0;
+ return rules;
+}
+
+char*
+concat(char *s, char *t)
+{
+ if(t == nil)
+ return s;
+ if(s == nil)
+ s = estrdup(t);
+ else{
+ s = erealloc(s, strlen(s)+strlen(t)+1);
+ strcat(s, t);
+ }
+ return s;
+}
+
+char*
+printpat(Rule *r)
+{
+ char *s;
+
+ s = emalloc(strlen(objects[r->obj])+1+strlen(verbs[r->verb])+1+strlen(r->arg)+1+1);
+ sprint(s, "%s\t%s\t%s\n", objects[r->obj], verbs[r->verb], r->arg);
+ return s;
+}
+
+char*
+printvar(Var *v)
+{
+ char *s;
+
+ s = emalloc(strlen(v->name)+1+strlen(v->value)+2+1);
+ sprint(s, "%s=%s\n\n", v->name, v->value);
+ return s;
+}
+
+char*
+printrule(Ruleset *r)
+{
+ int i;
+ char *s;
+
+ s = nil;
+ for(i=0; i<r->npat; i++)
+ s = concat(s, printpat(r->pat[i]));
+ for(i=0; i<r->nact; i++)
+ s = concat(s, printpat(r->act[i]));
+ s = concat(s, "\n");
+ return s;
+}
+
+char*
+printport(char *port)
+{
+ char *s;
+
+ s = nil;
+ s = concat(s, "plumb to ");
+ s = concat(s, port);
+ s = concat(s, "\n");
+ return s;
+}
+
+char*
+printrules(void)
+{
+ int i;
+ char *s;
+
+ s = nil;
+ for(i=0; i<nvars; i++)
+ s = concat(s, printvar(&vars[i]));
+ for(i=0; i<nports; i++)
+ s = concat(s, printport(ports[i]));
+ s = concat(s, "\n");
+ for(i=0; rules[i]; i++)
+ s = concat(s, printrule(rules[i]));
+ return s;
+}
+
+char*
+stringof(char *s, int n)
+{
+ char *t;
+
+ t = emalloc(n+1);
+ memmove(t, s, n);
+ return t;
+}
+
+uchar*
+morerules(uchar *text, int done)
+{
+ int n;
+ Ruleset *rs;
+ uchar *otext, *s, *endofrule;
+
+ pushinput("<rules input>", -1, text);
+ if(done)
+ input->end = text+strlen((char*)text);
+ else{
+ /*
+ * Help user by sending any full rules to parser so any parse errors will
+ * occur on write rather than close. A heuristic will do: blank line ends rule.
+ */
+ endofrule = nil;
+ for(s=text; *s!='\0'; s++)
+ if(*s=='\n' && *++s=='\n')
+ endofrule = s+1;
+ if(endofrule == nil)
+ return text;
+ input->end = endofrule;
+ }
+ for(n=0; rules[n]; n++)
+ ;
+ while((rs=readruleset()) != nil){
+ rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
+ rules[n++] = rs;
+ rules[n] = nil;
+ }
+ otext =text;
+ if(input == nil)
+ text = (uchar*)estrdup("");
+ else
+ text = (uchar*)estrdup((char*)input->end);
+ popinput();
+ free(otext);
+ return text;
+}
+
+char*
+writerules(char *s, int n)
+{
+ static uchar *text;
+ char *tmp;
+
+ free(lasterror);
+ lasterror = nil;
+ parsing = 1;
+ if(setjmp(parsejmp) == 0){
+ tmp = stringof(s, n);
+ text = (uchar*)concat((char*)text, tmp);
+ free(tmp);
+ text = morerules(text, s==nil);
+ }
+ if(s == nil){
+ free(text);
+ text = nil;
+ }
+ parsing = 0;
+ makeports(rules);
+ return lasterror;
+}