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