]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/plumb/rules.c
plumber: fix memory and filedescriptor leaks (thanks BurnZeZ)
[plan9front.git] / sys / src / cmd / plumb / rules.c
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"
9
10 typedef struct Input Input;
11 typedef struct Var Var;
12
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 };
22
23 struct Var
24 {
25         char    *name;
26         char    *value;
27         char *qvalue;
28 };
29
30 static int              parsing;
31 static int              nvars;
32 static Var              *vars;
33 static Input    *input;
34
35 static char     ebuf[4096];
36
37 char *badports[] =
38 {
39         ".",
40         "..",
41         "send",
42         nil
43 };
44
45 char *objects[] =
46 {
47         "arg",
48         "attr",
49         "data",
50         "dst",
51         "plumb",
52         "src",
53         "type",
54         "wdir",
55         nil
56 };
57
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 };
72
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 }
81
82 void
83 printinputstack(void)
84 {
85         printinputstackrev(input);
86 }
87
88 static void
89 pushinput(char *name, int fd, uchar *str)
90 {
91         Input *in;
92         int depth;
93
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");
98
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                 if((in->fd = Bfdopen(fd, OREAD)) == nil)
107                         parseerror("can't initialize Bio for rules file: %r");
108         }
109
110 }
111
112 int
113 popinput(void)
114 {
115         Input *in;
116
117         in = input;
118         if(in == nil)
119                 return 0;
120         input = in->next;
121         if(in->fd)
122                 Bterm(in->fd);
123         free(in->file);
124         free(in);
125         return 1;
126 }
127
128 int
129 getc(void)
130 {
131         if(input == nil)
132                 return Beof;
133         if(input->fd)
134                 return Bgetc(input->fd);
135         if(input->s < input->end)
136                 return *(input->s)++;
137         return -1;
138 }
139
140 char*
141 getline(void)
142 {
143         static int n = 0;
144         static char *s;
145         int c, i;
146
147         i = 0;
148         for(;;){
149                 c = getc();
150                 if(c < 0)
151                         return nil;
152                 if(i == n){
153                         n += 100;
154                         s = erealloc(s, n);
155                 }
156                 if(c<0 || c=='\0' || c=='\n')
157                         break;
158                 s[i++] = c;
159         }
160         s[i] = '\0';
161         return s;
162 }
163
164 int
165 lookup(char *s, char *tab[])
166 {
167         int i;
168
169         for(i=0; tab[i]!=nil; i++)
170                 if(strcmp(s, tab[i])==0)
171                         return i;
172         return -1;
173 }
174
175 Var*
176 lookupvariable(char *s, int n)
177 {
178         int i;
179
180         for(i=0; i<nvars; i++)
181                 if(n==strlen(vars[i].name) && memcmp(s, vars[i].name, n)==0)
182                         return vars+i;
183         return nil;
184 }
185
186 char*
187 variable(char *s, int n)
188 {
189         Var *var;
190
191         var = lookupvariable(s, n);
192         if(var)
193                 return var->qvalue;
194         return nil;
195 }
196
197 void
198 setvariable(char  *s, int n, char *val, char *qval)
199 {
200         Var *var;
201
202         var = lookupvariable(s, n);
203         if(var){
204                 free(var->value);
205                 free(var->qvalue);
206         }else{
207                 vars = erealloc(vars, (nvars+1)*sizeof(Var));
208                 var = vars+nvars++;
209                 var->name = emalloc(n+1);
210                 memmove(var->name, s, n);
211         }
212         var->value = estrdup(val);
213         var->qvalue = estrdup(qval);
214 }
215
216 static char*
217 nonnil(char *s)
218 {
219         if(s == nil)
220                 return "";
221         return s;
222 }
223
224 static char*
225 filename(Exec *e, char *name)
226 {
227         static char *buf;       /* rock to hold value so we don't leak the strings */
228
229         free(buf);
230         /* if name is defined, used it */
231         if(name!=nil && name[0]!='\0'){
232                 buf = estrdup(name);
233                 return cleanname(buf);
234         }
235         /* if data is an absolute file name, or wdir is empty, use it */
236         if(e->msg->data[0]=='/' || e->msg->wdir==nil || e->msg->wdir[0]=='\0'){
237                 buf = estrdup(e->msg->data);
238                 return cleanname(buf);
239         }
240         buf = emalloc(strlen(e->msg->wdir)+1+strlen(e->msg->data)+1);
241         sprint(buf, "%s/%s", e->msg->wdir, e->msg->data);
242         return cleanname(buf);
243 }
244
245 char*
246 dollar(Exec *e, char *s, int *namelen)
247 {
248         int n;
249         static char *abuf;
250         char *t;
251
252         *namelen = 1;
253         if(e!=nil && '0'<=s[0] && s[0]<='9')
254                 return nonnil(e->match[s[0]-'0']);
255
256         for(t=s; isalnum(*t); t++)
257                 ;
258         n = t-s;
259         *namelen = n;
260
261         if(e != nil){
262                 if(n == 3){
263                         if(memcmp(s, "src", 3) == 0)
264                                 return nonnil(e->msg->src);
265                         if(memcmp(s, "dst", 3) == 0)
266                                 return nonnil(e->msg->dst);
267                         if(memcmp(s, "dir", 3) == 0)
268                                 return filename(e, e->dir);
269                 }
270                 if(n == 4){
271                         if(memcmp(s, "attr", 4) == 0){
272                                 free(abuf);
273                                 abuf = plumbpackattr(e->msg->attr);
274                                 return nonnil(abuf);
275                         }
276                         if(memcmp(s, "data", 4) == 0)
277                                 return nonnil(e->msg->data);
278                         if(memcmp(s, "file", 4) == 0)
279                                 return filename(e, e->file);
280                         if(memcmp(s, "type", 4) == 0)
281                                 return nonnil(e->msg->type);
282                         if(memcmp(s, "wdir", 4) == 0)
283                                 return nonnil(e->msg->wdir);
284                 }
285         }
286
287         return variable(s, n);
288 }
289
290 /* expand one blank-terminated string, processing quotes and $ signs */
291 char*
292 expand(Exec *e, char *s, char **ends)
293 {
294         char *p, *ep, *val;
295         int namelen, quoting;
296
297         p = ebuf;
298         ep = ebuf+sizeof ebuf-1;
299         quoting = 0;
300         while(p<ep && *s!='\0' && (quoting || (*s!=' ' && *s!='\t'))){
301                 if(*s == '\''){
302                         s++;
303                         if(!quoting)
304                                 quoting = 1;
305                         else  if(*s == '\''){
306                                 *p++ = '\'';
307                                 s++;
308                         }else
309                                 quoting = 0;
310                         continue;
311                 }
312                 if(quoting || *s!='$'){
313                         *p++ = *s++;
314                         continue;
315                 }
316                 s++;
317                 val = dollar(e, s, &namelen);
318                 if(val == nil){
319                         *p++ = '$';
320                         continue;
321                 }
322                 if(ep-p < strlen(val))
323                         return "string-too-long";
324                 strcpy(p, val);
325                 p += strlen(val);
326                 s += namelen;
327         }
328         if(ends)
329                 *ends = s;
330         *p = '\0';
331         return ebuf;
332 }
333
334 void
335 regerror(char *msg)
336 {
337         if(parsing){
338                 parsing = 0;
339                 parseerror("%s", msg);
340         }
341         error("%s", msg);
342 }
343
344 void
345 parserule(Rule *r)
346 {
347         r->qarg = estrdup(expand(nil, r->arg, nil));
348         switch(r->obj){
349         case OArg:
350         case OAttr:
351         case OData:
352         case ODst:
353         case OType:
354         case OWdir:
355         case OSrc:
356                 if(r->verb==VClient || r->verb==VStart || r->verb==VTo)
357                         parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
358                 if(r->obj!=OAttr && (r->verb==VAdd || r->verb==VDelete))
359                         parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
360                 if(r->verb == VMatches){
361                         r->regex = regcomp(r->qarg);
362                         return;
363                 }
364                 break;
365         case OPlumb:
366                 if(r->verb!=VClient && r->verb!=VStart && r->verb!=VTo)
367                         parseerror("%s not valid verb for object %s", verbs[r->verb], objects[r->obj]);
368                 break;
369         }
370 }
371
372 int
373 assignment(char *p)
374 {
375         char *var, *qval;
376         int n;
377
378         if(!isalpha(p[0]))
379                 return 0;
380         for(var=p; isalnum(*p); p++)
381                 ;
382         n = p-var;
383         while(*p==' ' || *p=='\t')
384                         p++;
385         if(*p++ != '=')
386                 return 0;
387         while(*p==' ' || *p=='\t')
388                         p++;
389         qval = expand(nil, p, nil);
390         setvariable(var, n, p, qval);
391         return 1;
392 }
393
394 int
395 include(char *s)
396 {
397         char *t, *args[3], buf[128];
398         int n, fd;
399
400         if(strncmp(s, "include", 7) != 0)
401                 return 0;
402         /* either an include or an error */
403         n = tokenize(s, args, nelem(args));
404         if(n < 2)
405                 goto Err;
406         if(strcmp(args[0], "include") != 0)
407                 goto Err;
408         if(args[1][0] == '#')
409                 goto Err;
410         if(n>2 && args[2][0] != '#')
411                 goto Err;
412         t = args[1];
413         fd = open(t, OREAD);
414         if(fd<0 && t[0]!='/' && strncmp(t, "./", 2)!=0 && strncmp(t, "../", 3)!=0){
415                 snprint(buf, sizeof buf, "/sys/lib/plumb/%s", t);
416                 t = buf;
417                 fd = open(t, OREAD);
418         }
419         if(fd < 0)
420                 parseerror("can't open %s for inclusion", t);
421         pushinput(t, fd, nil);
422         return 1;
423
424     Err:
425         parseerror("malformed include statement");
426         return 0;
427 }
428
429 void
430 freerule(Rule *r)
431 {
432         free(r->arg);
433         free(r->qarg);
434         free(r->regex);
435         free(r);
436 }
437
438 Rule*
439 readrule(int *eof)
440 {
441         Rule *rp;
442         char *line, *p;
443         char *word;
444         jmp_buf ojmp;
445
446 Top:
447         line = getline();
448         if(line == nil){
449                 /*
450                  * if input is from string, and bytes remain (input->end is within string),
451                  * morerules() will pop input and save remaining data.  otherwise pop
452                  * the stack here, and if there's more input, keep reading.
453                  */
454                 if((input!=nil && input->end==nil) && popinput())
455                         goto Top;
456                 *eof = 1;
457                 return nil;
458         }
459         input->lineno++;
460
461         for(p=line; *p==' ' || *p=='\t'; p++)
462                 ;
463         if(*p=='\0' || *p=='#') /* empty or comment line */
464                 return nil;
465
466         if(include(p))
467                 goto Top;
468
469         if(assignment(p))
470                 return nil;
471
472         rp = emalloc(sizeof(Rule));
473         
474         memmove(ojmp, parsejmp, sizeof(jmp_buf));
475         if(setjmp(parsejmp)){
476                 freerule(rp);
477                 longjmp(ojmp, 1);
478         }
479
480         /* object */
481         for(word=p; *p!=' ' && *p!='\t'; p++)
482                 if(*p == '\0')
483                         parseerror("malformed rule");
484         *p++ = '\0';
485         rp->obj = lookup(word, objects);
486         if(rp->obj < 0){
487                 if(strcmp(word, "kind") == 0)   /* backwards compatibility */
488                         rp->obj = OType;
489                 else
490                         parseerror("unknown object %s", word);
491         }
492
493         /* verb */
494         while(*p==' ' || *p=='\t')
495                 p++;
496         for(word=p; *p!=' ' && *p!='\t'; p++)
497                 if(*p == '\0')
498                         parseerror("malformed rule");
499         *p++ = '\0';
500         rp->verb = lookup(word, verbs);
501         if(rp->verb < 0)
502                 parseerror("unknown verb %s", word);
503
504         /* argument */
505         while(*p==' ' || *p=='\t')
506                 p++;
507         if(*p == '\0')
508                 parseerror("malformed rule");
509         rp->arg = estrdup(p);
510
511         parserule(rp);
512
513         memmove(parsejmp, ojmp, sizeof(jmp_buf));
514
515         return rp;
516 }
517
518 void
519 freerules(Rule **r)
520 {
521         while(*r)
522                 freerule(*r++);
523 }
524
525 void
526 freeruleset(Ruleset *rs)
527 {
528         freerules(rs->pat);
529         free(rs->pat);
530         freerules(rs->act);
531         free(rs->act);
532         free(rs->port);
533         free(rs);
534 }
535
536 Ruleset*
537 readruleset(void)
538 {
539         Ruleset *rs;
540         Rule *r;
541         int eof, inrule, i, ncmd;
542
543    Again:
544         eof = 0;
545         rs = emalloc(sizeof(Ruleset));
546         rs->pat = emalloc(sizeof(Rule*));
547         rs->act = emalloc(sizeof(Rule*));
548         inrule = 0;
549         ncmd = 0;
550         for(;;){
551                 r = readrule(&eof);
552                 if(eof)
553                         break;
554                 if(r==nil){
555                         if(inrule)
556                                 break;
557                         continue;
558                 }
559                 inrule = 1;
560                 switch(r->obj){
561                 case OArg:
562                 case OAttr:
563                 case OData:
564                 case ODst:
565                 case OType:
566                 case OWdir:
567                 case OSrc:
568                         rs->npat++;
569                         rs->pat = erealloc(rs->pat, (rs->npat+1)*sizeof(Rule*));
570                         rs->pat[rs->npat-1] = r;
571                         rs->pat[rs->npat] = nil;
572                         break;
573                 case OPlumb:
574                         rs->nact++;
575                         rs->act = erealloc(rs->act, (rs->nact+1)*sizeof(Rule*));
576                         rs->act[rs->nact-1] = r;
577                         rs->act[rs->nact] = nil;
578                         if(r->verb == VTo){
579                                 if(rs->npat>0 && rs->port != nil)       /* npat==0 implies port declaration */
580                                         parseerror("too many ports");
581                                 if(lookup(r->qarg, badports) >= 0)
582                                         parseerror("illegal port name %s", r->qarg);
583                                 free(rs->port);
584                                 rs->port = estrdup(r->qarg);
585                         }else
586                                 ncmd++; /* start or client rule */
587                         break;
588                 }
589         }
590         if(ncmd > 1){
591                 freeruleset(rs);
592                 parseerror("ruleset has more than one client or start action");
593         }
594         if(rs->npat>0 && rs->nact>0)
595                 return rs;
596         if(rs->npat==0 && rs->nact==0){
597                 freeruleset(rs);
598                 return nil;
599         }
600         if(rs->nact==0 || rs->port==nil){
601                 freeruleset(rs);
602                 parseerror("ruleset must have patterns and actions");
603                 return nil;
604         }
605
606         /* declare ports */
607         for(i=0; i<rs->nact; i++)
608                 if(rs->act[i]->verb != VTo){
609                         freeruleset(rs);
610                         parseerror("ruleset must have actions");
611                         return nil;
612                 }
613         for(i=0; i<rs->nact; i++)
614                 addport(rs->act[i]->qarg);
615         freeruleset(rs);
616         goto Again;
617 }
618
619 Ruleset**
620 readrules(char *name, int fd)
621 {
622         Ruleset *rs, **rules;
623         int n;
624
625         parsing = 1;
626         pushinput(name, fd, nil);
627         rules = emalloc(sizeof(Ruleset*));
628         for(n=0; (rs=readruleset())!=nil; n++){
629                 rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
630                 rules[n] = rs;
631                 rules[n+1] = nil;
632         }
633         popinput();
634         parsing = 0;
635         return rules;
636 }
637
638 char*
639 concat(char *s, char *t)
640 {
641         if(t == nil)
642                 return s;
643         if(s == nil)
644                 s = estrdup(t);
645         else{
646                 s = erealloc(s, strlen(s)+strlen(t)+1);
647                 strcat(s, t);
648         }
649         return s;
650 }
651
652 char*
653 printpat(Rule *r)
654 {
655         char *s;
656
657         s = emalloc(strlen(objects[r->obj])+1+strlen(verbs[r->verb])+1+strlen(r->arg)+1+1);
658         sprint(s, "%s\t%s\t%s\n", objects[r->obj], verbs[r->verb], r->arg);
659         return s;
660 }
661
662 char*
663 printvar(Var *v)
664 {
665         char *s;
666
667         s = emalloc(strlen(v->name)+1+strlen(v->value)+2+1);
668         sprint(s, "%s=%s\n\n", v->name, v->value);
669         return s;
670 }
671
672 char*
673 printrule(Ruleset *r)
674 {
675         int i;
676         char *s;
677
678         s = nil;
679         for(i=0; i<r->npat; i++)
680                 s = concat(s, printpat(r->pat[i]));
681         for(i=0; i<r->nact; i++)
682                 s = concat(s, printpat(r->act[i]));
683         s = concat(s, "\n");
684         return s;
685 }
686
687 char*
688 printport(char *port)
689 {
690         char *s;
691
692         s = nil;
693         s = concat(s, "plumb to ");
694         s = concat(s, port);
695         s = concat(s, "\n");
696         return s;
697 }
698
699 char*
700 printrules(void)
701 {
702         int i;
703         char *s;
704
705         s = nil;
706         for(i=0; i<nvars; i++)
707                 s = concat(s, printvar(&vars[i]));
708         for(i=0; i<nports; i++)
709                 s = concat(s, printport(ports[i]));
710         s = concat(s, "\n");
711         for(i=0; rules[i]; i++)
712                 s = concat(s, printrule(rules[i]));
713         return s;
714 }
715
716 char*
717 stringof(char *s, int n)
718 {
719         char *t;
720
721         t = emalloc(n+1);
722         memmove(t, s, n);
723         return t;
724 }
725
726 uchar*
727 morerules(uchar *text, int done)
728 {
729         int n;
730         Ruleset *rs;
731         uchar *otext, *s, *endofrule;
732
733         pushinput("<rules input>", -1, text);
734         if(done)
735                 input->end = text+strlen((char*)text);
736         else{
737                 /*
738                  * Help user by sending any full rules to parser so any parse errors will
739                  * occur on write rather than close. A heuristic will do: blank line ends rule.
740                  */
741                 endofrule = nil;
742                 for(s=text; *s!='\0'; s++)
743                         if(*s=='\n' && *++s=='\n')
744                                 endofrule = s+1;
745                 if(endofrule == nil){
746                         popinput();
747                         return text;
748                 }
749                 input->end = endofrule;
750         }
751         for(n=0; rules[n]; n++)
752                 ;
753         while((rs=readruleset()) != nil){
754                 rules = erealloc(rules, (n+2)*sizeof(Ruleset*));
755                 rules[n++] = rs;
756                 rules[n] = nil;
757         }
758         otext = text;
759         if(input == nil)
760                 text = (uchar*)estrdup("");
761         else
762                 text = (uchar*)estrdup((char*)input->end);
763         popinput();
764         free(otext);
765         return text;
766 }
767
768 char*
769 writerules(char *s, int n)
770 {
771         static uchar *text;
772         char *tmp;
773
774         free(lasterror);
775         lasterror = nil;
776         parsing = 1;
777         if(setjmp(parsejmp) == 0){
778                 tmp = stringof(s, n);
779                 text = (uchar*)concat((char*)text, tmp);
780                 free(tmp);
781                 text = morerules(text, s==nil);
782         }
783         if(s == nil){
784                 free(text);
785                 text = nil;
786         }
787         parsing = 0;
788         makeports(rules);
789         return lasterror;
790 }