| #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; |
| } |
| |
| static 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((uchar)*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((uchar)p[0])) |
| return 0; |
| for(var=p; isalnum((uchar)*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, "#9/plumb/%s", t); |
| t = unsharp(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; |
| char *plan9root; |
| |
| plan9root = get9root(); |
| if(plan9root) |
| setvariable("plan9", 5, plan9root, plan9root); |
| |
| 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; |
| } |