diff options
Diffstat (limited to 'src/cmd/plumb/rules.c')
-rw-r--r-- | src/cmd/plumb/rules.c | 779 |
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; +} |