Blob


1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <regexp.h>
5 #include <thread.h>
6 #include <ctype.h>
7 #include <plumb.h>
8 #include "plumber.h"
10 typedef struct Input Input;
11 typedef struct Var Var;
13 struct Input
14 {
15 char *file; /* name of file */
16 Biobuf *fd; /* input buffer, if from real file */
17 uchar *s; /* input string, if from /mnt/plumb/rules */
18 uchar *end; /* end of input string */
19 int lineno;
20 Input *next; /* file to read after EOF on this one */
21 };
23 struct Var
24 {
25 char *name;
26 char *value;
27 char *qvalue;
28 };
30 static int parsing;
31 static int nvars;
32 static Var *vars;
33 static Input *input;
35 static char ebuf[4096];
37 char *badports[] =
38 {
39 ".",
40 "..",
41 "send",
42 nil
43 };
45 char *objects[] =
46 {
47 "arg",
48 "attr",
49 "data",
50 "dst",
51 "plumb",
52 "src",
53 "type",
54 "wdir",
55 nil
56 };
58 char *verbs[] =
59 {
60 "add",
61 "client",
62 "delete",
63 "is",
64 "isdir",
65 "isfile",
66 "matches",
67 "set",
68 "start",
69 "to",
70 nil
71 };
73 static void
74 printinputstackrev(Input *in)
75 {
76 if(in == nil)
77 return;
78 printinputstackrev(in->next);
79 fprint(2, "%s:%d: ", in->file, in->lineno);
80 }
82 void
83 printinputstack(void)
84 {
85 printinputstackrev(input);
86 }
88 static void
89 pushinput(char *name, int fd, uchar *str)
90 {
91 Input *in;
92 int depth;
94 depth = 0;
95 for(in=input; in; in=in->next)
96 if(depth++ >= 10) /* prevent deep C stack in plumber and bad include structure */
97 parseerror("include stack too deep; max 10");
99 in = emalloc(sizeof(Input));
100 in->file = estrdup(name);
101 in->next = input;
102 input = in;
103 if(str)
104 in->s = str;
105 else{
106 in->fd = emalloc(sizeof(Biobuf));
107 if(Binit(in->fd, fd, OREAD) < 0)
108 parseerror("can't initialize Bio for rules file: %r");
113 int
114 popinput(void)
116 Input *in;
118 in = input;
119 if(in == nil)
120 return 0;
121 input = in->next;
122 if(in->fd){
123 Bterm(in->fd);
124 free(in->fd);
126 free(in);
127 return 1;
130 static int
131 getc(void)
133 if(input == nil)
134 return Beof;
135 if(input->fd)
136 return Bgetc(input->fd);
137 if(input->s < input->end)
138 return *(input->s)++;
139 return -1;
142 char*
143 getline(void)
145 static int n = 0;
146 static char *s /*, *incl*/;
147 int c, i;
149 i = 0;
150 for(;;){
151 c = getc();
152 if(c < 0)
153 return nil;
154 if(i == n){
155 n += 100;
156 s = erealloc(s, n);
158 if(c<0 || c=='\0' || c=='\n')
159 break;
160 s[i++] = c;
162 s[i] = '\0';
163 return s;
166 int
167 lookup(char *s, char *tab[])
169 int i;
171 for(i=0; tab[i]!=nil; i++)
172 if(strcmp(s, tab[i])==0)
173 return i;
174 return -1;
177 Var*
178 lookupvariable(char *s, int n)
180 int i;
182 for(i=0; i<nvars; i++)
183 if(n==strlen(vars[i].name) && memcmp(s, vars[i].name, n)==0)
184 return vars+i;
185 return nil;
188 char*
189 variable(char *s, int n)
191 Var *var;
193 var = lookupvariable(s, n);
194 if(var)
195 return var->qvalue;
196 return nil;
199 void
200 setvariable(char *s, int n, char *val, char *qval)
202 Var *var;
204 var = lookupvariable(s, n);
205 if(var){
206 free(var->value);
207 free(var->qvalue);
208 }else{
209 vars = erealloc(vars, (nvars+1)*sizeof(Var));
210 var = vars+nvars++;
211 var->name = emalloc(n+1);
212 memmove(var->name, s, n);
214 var->value = estrdup(val);
215 var->qvalue = estrdup(qval);
218 static char*
219 nonnil(char *s)
221 if(s == nil)
222 return "";
223 return s;
226 static char*
227 filename(Exec *e, char *name)
229 static char *buf; /* rock to hold value so we don't leak the strings */
231 free(buf);
232 /* if name is defined, used it */
233 if(name!=nil && name[0]!='\0'){
234 buf = estrdup(name);
235 return cleanname(buf);
237 /* if data is an absolute file name, or wdir is empty, use it */
238 if(e->msg->data[0]=='/' || e->msg->wdir==nil || e->msg->wdir[0]=='\0'){
239 buf = estrdup(e->msg->data);
240 return cleanname(buf);
242 buf = emalloc(strlen(e->msg->wdir)+1+strlen(e->msg->data)+1);
243 sprint(buf, "%s/%s", e->msg->wdir, e->msg->data);
244 return cleanname(buf);
247 char*
248 dollar(Exec *e, char *s, int *namelen)
250 int n;
251 static char *abuf;
252 char *t;
254 *namelen = 1;
255 if(e!=nil && '0'<=s[0] && s[0]<='9')
256 return nonnil(e->match[s[0]-'0']);
258 for(t=s; isalnum(*t); t++)
260 n = t-s;
261 *namelen = n;
263 if(e != nil){
264 if(n == 3){
265 if(memcmp(s, "src", 3) == 0)
266 return nonnil(e->msg->src);
267 if(memcmp(s, "dst", 3) == 0)
268 return nonnil(e->msg->dst);
269 if(memcmp(s, "dir", 3) == 0)
270 return filename(e, e->dir);
272 if(n == 4){
273 if(memcmp(s, "attr", 4) == 0){
274 free(abuf);
275 abuf = plumbpackattr(e->msg->attr);
276 return nonnil(abuf);
278 if(memcmp(s, "data", 4) == 0)
279 return nonnil(e->msg->data);
280 if(memcmp(s, "file", 4) == 0)
281 return filename(e, e->file);
282 if(memcmp(s, "type", 4) == 0)
283 return nonnil(e->msg->type);
284 if(memcmp(s, "wdir", 3) == 0)
285 return nonnil(e->msg->wdir);
289 return variable(s, n);
292 /* expand one blank-terminated string, processing quotes and $ signs */
293 char*
294 expand(Exec *e, char *s, char **ends)
296 char *p, *ep, *val;
297 int namelen, quoting;
299 p = ebuf;
300 ep = ebuf+sizeof ebuf-1;
301 quoting = 0;
302 while(p<ep && *s!='\0' && (quoting || (*s!=' ' && *s!='\t'))){
303 if(*s == '\''){
304 s++;
305 if(!quoting)
306 quoting = 1;
307 else if(*s == '\''){
308 *p++ = '\'';
309 s++;
310 }else
311 quoting = 0;
312 continue;
314 if(quoting || *s!='$'){
315 *p++ = *s++;
316 continue;
318 s++;
319 val = dollar(e, s, &namelen);
320 if(val == nil){
321 *p++ = '$';
322 continue;
324 if(ep-p < strlen(val))
325 return "string-too-long";
326 strcpy(p, val);
327 p += strlen(val);
328 s += namelen;
330 if(ends)
331 *ends = s;
332 *p = '\0';
333 return ebuf;
336 void
337 regerror(char *msg)
339 if(parsing){
340 parsing = 0;
341 parseerror("%s", msg);
343 error("%s", msg);
346 void
347 parserule(Rule *r)
349 r->qarg = estrdup(expand(nil, r->arg, nil));
350 switch(r->obj){
351 case OArg:
352 case OAttr:
353 case OData:
354 case ODst:
355 case OType:
356 case OWdir:
357 case OSrc:
358 if(r->verb==VClient || r->verb==VStart || r->verb==VTo)
359 parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
360 if(r->obj!=OAttr && (r->verb==VAdd || r->verb==VDelete))
361 parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
362 if(r->verb == VMatches){
363 r->regex = regcomp(r->qarg);
364 return;
366 break;
367 case OPlumb:
368 if(r->verb!=VClient && r->verb!=VStart && r->verb!=VTo)
369 parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
370 break;
374 int
375 assignment(char *p)
377 char *var, *qval;
378 int n;
380 if(!isalpha(p[0]))
381 return 0;
382 for(var=p; isalnum(*p); p++)
384 n = p-var;
385 while(*p==' ' || *p=='\t')
386 p++;
387 if(*p++ != '=')
388 return 0;
389 while(*p==' ' || *p=='\t')
390 p++;
391 qval = expand(nil, p, nil);
392 setvariable(var, n, p, qval);
393 return 1;
396 int
397 include(char *s)
399 char *t, *args[3], buf[128];
400 int n, fd;
402 if(strncmp(s, "include", 7) != 0)
403 return 0;
404 /* either an include or an error */
405 n = tokenize(s, args, nelem(args));
406 if(n < 2)
407 goto Err;
408 if(strcmp(args[0], "include") != 0)
409 goto Err;
410 if(args[1][0] == '#')
411 goto Err;
412 if(n>2 && args[2][0] != '#')
413 goto Err;
414 t = args[1];
415 fd = open(t, OREAD);
416 if(fd<0 && t[0]!='/' && strncmp(t, "./", 2)!=0 && strncmp(t, "../", 3)!=0){
417 snprint(buf, sizeof buf, "#9/plumb/%s", t);
418 t = unsharp(buf);
419 fd = open(t, OREAD);
421 if(fd < 0)
422 parseerror("can't open %s for inclusion", t);
423 pushinput(t, fd, nil);
424 return 1;
426 Err:
427 parseerror("malformed include statement");
428 return 0;
431 Rule*
432 readrule(int *eof)
434 Rule *rp;
435 char *line, *p;
436 char *word;
438 Top:
439 line = getline();
440 if(line == nil){
441 /*
442 * if input is from string, and bytes remain (input->end is within string),
443 * morerules() will pop input and save remaining data. otherwise pop
444 * the stack here, and if there's more input, keep reading.
445 */
446 if((input!=nil && input->end==nil) && popinput())
447 goto Top;
448 *eof = 1;
449 return nil;
451 input->lineno++;
453 for(p=line; *p==' ' || *p=='\t'; p++)
455 if(*p=='\0' || *p=='#') /* empty or comment line */
456 return nil;
458 if(include(p))
459 goto Top;
461 if(assignment(p))
462 return nil;
464 rp = emalloc(sizeof(Rule));
466 /* object */
467 for(word=p; *p!=' ' && *p!='\t'; p++)
468 if(*p == '\0')
469 parseerror("malformed rule");
470 *p++ = '\0';
471 rp->obj = lookup(word, objects);
472 if(rp->obj < 0){
473 if(strcmp(word, "kind") == 0) /* backwards compatibility */
474 rp->obj = OType;
475 else
476 parseerror("unknown object %s", word);
479 /* verb */
480 while(*p==' ' || *p=='\t')
481 p++;
482 for(word=p; *p!=' ' && *p!='\t'; p++)
483 if(*p == '\0')
484 parseerror("malformed rule");
485 *p++ = '\0';
486 rp->verb = lookup(word, verbs);
487 if(rp->verb < 0)
488 parseerror("unknown verb %s", word);
490 /* argument */
491 while(*p==' ' || *p=='\t')
492 p++;
493 if(*p == '\0')
494 parseerror("malformed rule");
495 rp->arg = estrdup(p);
497 parserule(rp);
499 return rp;
502 void
503 freerule(Rule *r)
505 free(r->arg);
506 free(r->qarg);
507 free(r->regex);
510 void
511 freerules(Rule **r)
513 while(*r)
514 freerule(*r++);
517 void
518 freeruleset(Ruleset *rs)
520 freerules(rs->pat);
521 free(rs->pat);
522 freerules(rs->act);
523 free(rs->act);
524 free(rs->port);
525 free(rs);
528 Ruleset*
529 readruleset(void)
531 Ruleset *rs;
532 Rule *r;
533 int eof, inrule, i, ncmd;
535 Again:
536 eof = 0;
537 rs = emalloc(sizeof(Ruleset));
538 rs->pat = emalloc(sizeof(Rule*));
539 rs->act = emalloc(sizeof(Rule*));
540 inrule = 0;
541 ncmd = 0;
542 for(;;){
543 r = readrule(&eof);
544 if(eof)
545 break;
546 if(r==nil){
547 if(inrule)
548 break;
549 continue;
551 inrule = 1;
552 switch(r->obj){
553 case OArg:
554 case OAttr:
555 case OData:
556 case ODst:
557 case OType:
558 case OWdir:
559 case OSrc:
560 rs->npat++;
561 rs->pat = erealloc(rs->pat, (rs->npat+1)*sizeof(Rule*));
562 rs->pat[rs->npat-1] = r;
563 rs->pat[rs->npat] = nil;
564 break;
565 case OPlumb:
566 rs->nact++;
567 rs->act = erealloc(rs->act, (rs->nact+1)*sizeof(Rule*));
568 rs->act[rs->nact-1] = r;
569 rs->act[rs->nact] = nil;
570 if(r->verb == VTo){
571 if(rs->npat>0 && rs->port != nil) /* npat==0 implies port declaration */
572 parseerror("too many ports");
573 if(lookup(r->qarg, badports) >= 0)
574 parseerror("illegal port name %s", r->qarg);
575 rs->port = estrdup(r->qarg);
576 }else
577 ncmd++; /* start or client rule */
578 break;
581 if(ncmd > 1){
582 freeruleset(rs);
583 parseerror("ruleset has more than one client or start action");
585 if(rs->npat>0 && rs->nact>0)
586 return rs;
587 if(rs->npat==0 && rs->nact==0){
588 freeruleset(rs);
589 return nil;
591 if(rs->nact==0 || rs->port==nil){
592 freeruleset(rs);
593 parseerror("ruleset must have patterns and actions");
594 return nil;
597 /* declare ports */
598 for(i=0; i<rs->nact; i++)
599 if(rs->act[i]->verb != VTo){
600 freeruleset(rs);
601 parseerror("ruleset must have actions");
602 return nil;
604 for(i=0; i<rs->nact; i++)
605 addport(rs->act[i]->qarg);
606 freeruleset(rs);
607 goto Again;
610 Ruleset**
611 readrules(char *name, int fd)
613 Ruleset *rs, **rules;
614 int n;
616 parsing = 1;
617 pushinput(name, fd, nil);
618 rules = emalloc(sizeof(Ruleset*));
619 for(n=0; (rs=readruleset())!=nil; n++){
620 rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
621 rules[n] = rs;
622 rules[n+1] = nil;
624 popinput();
625 parsing = 0;
626 return rules;
629 char*
630 concat(char *s, char *t)
632 if(t == nil)
633 return s;
634 if(s == nil)
635 s = estrdup(t);
636 else{
637 s = erealloc(s, strlen(s)+strlen(t)+1);
638 strcat(s, t);
640 return s;
643 char*
644 printpat(Rule *r)
646 char *s;
648 s = emalloc(strlen(objects[r->obj])+1+strlen(verbs[r->verb])+1+strlen(r->arg)+1+1);
649 sprint(s, "%s\t%s\t%s\n", objects[r->obj], verbs[r->verb], r->arg);
650 return s;
653 char*
654 printvar(Var *v)
656 char *s;
658 s = emalloc(strlen(v->name)+1+strlen(v->value)+2+1);
659 sprint(s, "%s=%s\n\n", v->name, v->value);
660 return s;
663 char*
664 printrule(Ruleset *r)
666 int i;
667 char *s;
669 s = nil;
670 for(i=0; i<r->npat; i++)
671 s = concat(s, printpat(r->pat[i]));
672 for(i=0; i<r->nact; i++)
673 s = concat(s, printpat(r->act[i]));
674 s = concat(s, "\n");
675 return s;
678 char*
679 printport(char *port)
681 char *s;
683 s = nil;
684 s = concat(s, "plumb to ");
685 s = concat(s, port);
686 s = concat(s, "\n");
687 return s;
690 char*
691 printrules(void)
693 int i;
694 char *s;
696 s = nil;
697 for(i=0; i<nvars; i++)
698 s = concat(s, printvar(&vars[i]));
699 for(i=0; i<nports; i++)
700 s = concat(s, printport(ports[i]));
701 s = concat(s, "\n");
702 for(i=0; rules[i]; i++)
703 s = concat(s, printrule(rules[i]));
704 return s;
707 char*
708 stringof(char *s, int n)
710 char *t;
712 t = emalloc(n+1);
713 memmove(t, s, n);
714 return t;
717 uchar*
718 morerules(uchar *text, int done)
720 int n;
721 Ruleset *rs;
722 uchar *otext, *s, *endofrule;
724 pushinput("<rules input>", -1, text);
725 if(done)
726 input->end = text+strlen((char*)text);
727 else{
728 /*
729 * Help user by sending any full rules to parser so any parse errors will
730 * occur on write rather than close. A heuristic will do: blank line ends rule.
731 */
732 endofrule = nil;
733 for(s=text; *s!='\0'; s++)
734 if(*s=='\n' && *++s=='\n')
735 endofrule = s+1;
736 if(endofrule == nil)
737 return text;
738 input->end = endofrule;
740 for(n=0; rules[n]; n++)
742 while((rs=readruleset()) != nil){
743 rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
744 fprint(2, "initialize rules %d %d\n", n, n+1);
745 rules[n++] = rs;
746 rules[n] = nil;
748 otext =text;
749 if(input == nil)
750 text = (uchar*)estrdup("");
751 else
752 text = (uchar*)estrdup((char*)input->end);
753 popinput();
754 free(otext);
755 return text;
758 char*
759 writerules(char *s, int n)
761 static uchar *text;
762 char *tmp;
764 free(lasterror);
765 lasterror = nil;
766 parsing = 1;
767 if(setjmp(parsejmp) == 0){
768 tmp = stringof(s, n);
769 text = (uchar*)concat((char*)text, tmp);
770 free(tmp);
771 text = morerules(text, s==nil);
773 if(s == nil){
774 free(text);
775 text = nil;
777 parsing = 0;
778 makeports(rules);
779 return lasterror;