blob: 4da4bb23c56400ccf76963775d7c304f8fbcef00 [file] [log] [blame]
#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;
}