ref: 4d69aacea023546a7150d92e147e531c38de822f
dir: /sys/src/cmd/plumb/rules.c/
#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{ if((in->fd = Bfdopen(fd, OREAD)) == nil) 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->file); 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; 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", 4) == 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|OCEXEC); 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|OCEXEC); } if(fd < 0) parseerror("can't open %s for inclusion", t); pushinput(t, fd, nil); return 1; Err: parseerror("malformed include statement"); return 0; } void freerule(Rule *r) { free(r->arg); free(r->qarg); free(r->regex); free(r); } Rule* readrule(int *eof) { Rule *rp; char *line, *p; char *word; jmp_buf ojmp; 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)); memmove(ojmp, parsejmp, sizeof(jmp_buf)); if(setjmp(parsejmp)){ freerule(rp); longjmp(ojmp, 1); } /* 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); memmove(parsejmp, ojmp, sizeof(jmp_buf)); return rp; } 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); free(rs->port); 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, *p; 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. */ if((p = (uchar*)strstr((char*)text, "\n\n")) == nil){ popinput(); return text; } input->end = p+2; } 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; }