commit c39be742cf8348232f6a527b19c42f764e80aae0 from: Omar Polo date: Fri Jul 09 11:25:25 2021 UTC parsing: bring lots of goodies from OpenBSD' parse.y This allows to solve the problem with the \n in the grammar (before two following macro declaration were treated as invalid. This also brings in a nice `include' keyword. commit - a5d822e542a927e88f655842273356225370c11f commit + c39be742cf8348232f6a527b19c42f764e80aae0 blob - d60c900a549371abb7163f884116e2e8af7303ed blob + bc44e2438232742dab541f12aadbc2b1760a7726 --- ChangeLog +++ ChangeLog @@ -1,3 +1,7 @@ +2021-07-09 Omar Polo + + * parse.y (STRING): add `include' directive + 2021-07-08 Omar Polo * parse.y (option): rename `mime MIME EXT' to `map MIME to-ext EXT', but retain the old `mime' for compatibility. blob - 457ea98b2f921158136c1d15640307c71480ab92 blob + 8f450de756554371b618add63490d625cf99dc3a --- parse.y +++ parse.y @@ -2,6 +2,13 @@ /* * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -25,26 +32,38 @@ #include "gmid.h" -FILE *yyfp; +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; -typedef struct { - union { - char *str; - int num; - } v; - int lineno; - int colno; -} yystype; -#define YYSTYPE yystype +struct file *pushfile(const char *, int); +int popfile(void); +int yyparse(void); +int yylex(void); +void yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); /* * #define YYDEBUG 1 * int yydebug = 1; */ -/* - * The idea behind this implementation of macros is from rad/parse.y - */ TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); struct sym { TAILQ_ENTRY(sym) entry; @@ -54,19 +73,12 @@ struct sym { char *val; }; -struct vhost *host; -struct location *loc; +int symset(const char *, const char *, int); +char *symget(const char *); -static int goterror; - -static struct vhost *new_vhost(void); -static struct location *new_location(void); - -void yyerror(const char*, ...); -int kw_cmp(const void *, const void *); -static int yylex(void); +struct vhost *new_vhost(void); +struct location *new_location(void); int parse_portno(const char*); -void parse_conf(const char*); char *ensure_absolute_path(char*); int check_block_code(int); char *check_block_fmt(char*); @@ -77,9 +89,19 @@ void only_once(const void*, const char*); void only_oncei(int, const char*); int fastcgi_conf(char *, char *, char *); void add_param(char *, char *, int); -int symset(const char *, const char *, int); -char *symget(const char *); +static struct vhost *host; +static struct location *loc; +static int errors; + +typedef struct { + union { + char *string; + int number; + } v; + int lineno; +} YYSTYPE; + %} /* for bison: */ @@ -88,25 +110,47 @@ char *symget(const char *); %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TCHROOT TUSER TSERVER %token TPREFORK TLOCATION TCERT TKEY TROOT TCGI TENV TLANG TLOG TINDEX TAUTO %token TSTRIP TBLOCK TRETURN TENTRYPOINT TREQUIRE TCLIENT TCA TALIAS TTCP -%token TFASTCGI TSPAWN TPARAM TMAP TTOEXT +%token TFASTCGI TSPAWN TPARAM TMAP TTOEXT INCLUDE TON TOFF -%token TERR +%token ERROR -%token TSTRING -%token TNUM -%token TBOOL +%token STRING +%token NUM -%type string +%type bool +%type string %% conf : /* empty */ - | conf var - | conf option - | conf vhost + | conf include '\n' + | conf '\n' + | conf varset '\n' + | conf option '\n' + | conf vhost '\n' + | conf error '\n' { file->errors++; } ; -string : string TSTRING { +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +bool : TON { $$ = 1; } + | TOFF { $$ = 0; } + ; + +string : string STRING { if (asprintf(&$$, "%s%s", $1, $2) == -1) { free($1); free($2); @@ -116,13 +160,13 @@ string : string TSTRING { free($1); free($2); } - | TSTRING + | STRING ; -var : TSTRING '=' string { +varset : STRING '=' string { char *s = $1; while (*s++) { - if (isspace(*s)) { + if (isspace((unsigned char)*s)) { yyerror("macro name cannot contain " "whitespaces"); free($1); @@ -137,8 +181,8 @@ var : TSTRING '=' string { ; option : TCHROOT string { conf.chroot = $2; } - | TIPV6 TBOOL { conf.ipv6 = $2; } - | TMIME TSTRING string { + | TIPV6 bool { conf.ipv6 = $2; } + | TMIME STRING string { fprintf(stderr, "%s:%d: `mime MIME EXT' is deprecated and " "will be removed in a future version, " "please use the new syntax: `map MIME to-ext EXT'", @@ -146,8 +190,8 @@ option : TCHROOT string { conf.chroot = $2; } add_mime(&conf.mime, $2, $3); } | TMAP string TTOEXT string { add_mime(&conf.mime, $2, $4); } - | TPORT TNUM { conf.port = $2; } - | TPREFORK TNUM { conf.prefork = check_prefork_num($2); } + | TPORT NUM { conf.port = $2; } + | TPREFORK NUM { conf.prefork = check_prefork_num($2); } | TPROTOCOLS string { if (tls_config_parse_protocols(&conf.protos, $2) == -1) yyerror("invalid protocols string \"%s\"", $2); @@ -166,13 +210,11 @@ vhost : TSERVER string { host->domain = $2; if (strstr($2, "xn--") != NULL) { - warnx("%s:%d:%d \"%s\" looks like punycode: " + warnx("%s:%d \"%s\" looks like punycode: " "you should use the decoded hostname.", - config_path, yylval.lineno+1, yylval.colno, - $2); + config_path, yylval.lineno+1, $2); } - } '{' servopts locations '}' { - + } '{' optnl servopts locations '}' { if (host->cert == NULL || host->key == NULL) yyerror("invalid vhost definition: %s", $2); } @@ -180,7 +222,7 @@ vhost : TSERVER string { ; servopts : /* empty */ - | servopts servopt + | servopts servopt optnl ; servopt : TALIAS string { @@ -224,10 +266,10 @@ servopt : TALIAS string { ; locations : /* empty */ - | locations location + | locations location optnl ; -location : TLOCATION { advance_loc(); } string '{' locopts '}' { +location : TLOCATION { advance_loc(); } string '{' optnl locopts '}' { /* drop the starting '/' if any */ if (*$3 == '/') memmove($3, $3+1, strlen($3)); @@ -237,16 +279,16 @@ location : TLOCATION { advance_loc(); } string '{' loc ; locopts : /* empty */ - | locopts locopt + | locopts locopt optnl ; -locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; } - | TBLOCK TRETURN TNUM string { +locopt : TAUTO TINDEX bool { loc->auto_index = $3 ? 1 : -1; } + | TBLOCK TRETURN NUM string { only_once(loc->block_fmt, "block"); loc->block_fmt = check_block_fmt($4); loc->block_code = check_block_code($3); } - | TBLOCK TRETURN TNUM { + | TBLOCK TRETURN NUM { only_once(loc->block_fmt, "block"); loc->block_fmt = xstrdup("temporary failure"); loc->block_code = check_block_code($3); @@ -271,7 +313,7 @@ locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? only_once(loc->lang, "lang"); loc->lang = $2; } - | TLOG TBOOL { loc->disable_log = !$2; } + | TLOG bool { loc->disable_log = !$2; } | TREQUIRE TCLIENT TCA string { only_once(loc->reqca, "require client ca"); ensure_absolute_path($4); @@ -283,7 +325,7 @@ locopt : TAUTO TINDEX TBOOL { loc->auto_index = $3 ? only_once(loc->dir, "root"); loc->dir = ensure_absolute_path($2); } - | TSTRIP TNUM { loc->strip = check_strip_no($2); } + | TSTRIP NUM { loc->strip = check_strip_no($2); } ; fastcgi : TSPAWN string { @@ -294,7 +336,7 @@ fastcgi : TSPAWN string { only_oncei(loc->fcgi, "fastcgi"); loc->fcgi = fastcgi_conf($1, NULL, NULL); } - | TTCP string TPORT TNUM { + | TTCP string TPORT NUM { char *c; if (asprintf(&c, "%d", $4) == -1) err(1, "asprintf"); @@ -311,39 +353,12 @@ fastcgi : TSPAWN string { } ; -%% - -static struct vhost * -new_vhost(void) -{ - return xcalloc(1, sizeof(struct vhost)); -} - -static struct location * -new_location(void) -{ - struct location *l; +optnl : '\n' optnl /* zero or more newlines */ + | /*empty*/ + ; - l = xcalloc(1, sizeof(*l)); - l->dirfd = -1; - l->fcgi = -1; - return l; -} +%% -void -yyerror(const char *msg, ...) -{ - va_list ap; - - goterror = 1; - - va_start(ap, msg); - fprintf(stderr, "%s:%d: ", config_path, yylval.lineno); - vfprintf(stderr, msg, ap); - fprintf(stderr, "\n"); - va_end(ap); -} - static struct keyword { const char *word; int token; @@ -369,6 +384,8 @@ static struct keyword { {"log", TLOG}, {"map", TMAP}, {"mime", TMIME}, + {"off", TOFF}, + {"on", TON}, {"param", TPARAM}, {"port", TPORT}, {"prefork", TPREFORK}, @@ -385,227 +402,352 @@ static struct keyword { {"user", TUSER}, }; +void +yyerror(const char *msg, ...) +{ + va_list ap; + + file->errors++; + + va_start(ap, msg); + fprintf(stderr, "%s:%d: ", config_path, yylval.lineno); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + int kw_cmp(const void *k, const void *e) { return strcmp(k, ((struct keyword *)e)->word); } -/* - * Taken an adapted from doas' parse.y - */ -static int -yylex(void) +int +lookup(char *s) { - struct keyword *kw; - char buf[8096], *ebuf, *p, *str, *v, *val; - int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0; - size_t len; + const struct keyword *p; - p = buf; - ebuf = buf + sizeof(buf); + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); -repeat: - /* skip whitespace first */ - for (c = getc(yyfp); isspace(c); c = getc(yyfp)) { - yylval.colno++; - if (c == '\n') { - yylval.lineno++; - yylval.colno = 0; - } + if (p) + return p->token; + else + return STRING; +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; } + return c; +} - /* check for special one-character constructions */ - switch (c) { - case '{': - case '}': +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return EOF; + return quotec; + } return c; - case '#': - /* skip comments; NUL is allowed; no continuation */ - while ((c = getc(yyfp)) != '\n') - if (c == EOF) - goto eof; - yylval.colno = 0; - yylval.lineno++; - goto repeat; - case '=': - return c; - case EOF: - goto eof; } - /* parsing next word */ - for (;; c = getc(yyfp), yylval.colno++) { - switch (c) { - case '\0': - yyerror("unallowed character NULL in column %d", - yylval.colno+1); - escape = 0; - continue; - case '\\': - escape = !escape; - if (escape) - continue; + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; break; + } + yylval.lineno = file->lineno; + file->lineno++; + } - /* expand macros in-place */ - case '$': - if (!escape && !quotes) { - v = p; - while (1) { - if ((c = getc(yyfp)) == EOF) { - yyerror("EOF during macro expansion"); - return 0; - } - if (p + 1 >= ebuf - 1) { - yyerror("string too long"); - return 0; - } - if (isalnum(c) || c == '_') { - *p++ = c; - continue; - } - *p = 0; - break; - } - p = v; - if ((val = symget(p)) == NULL) { - yyerror("macro '%s' not defined", v); - return TERR; - } - len = strlen(val); - if (p + len >= ebuf - 1) { - yyerror("after macro-expansion, " - "string too long"); - return TERR; - } - *p = '\0'; - strlcat(p, val, ebuf - p); - p += len; - nonkw = 1; - goto eow; - } + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return '\n'; + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return EOF; + c = igetc(); + } + } + return c; +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "lungetc"); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; break; - case '\n': - if (quotes) - yyerror("unterminated quotes in column %d", - yylval.colno+1); - if (escape) { - nonkw = 1; - escape = 0; - yylval.colno = 0; - yylval.lineno++; - } - goto eow; - case EOF: - if (escape) - yyerror("unterminated escape in column %d", - yylval.colno); - if (quotes) - yyerror("unterminated quotes in column %d", - qpos+1); - goto eow; - case '{': - case '}': - case '#': - case ' ': - case '\t': - if (!escape && !quotes) - goto eow; + } + if (c == EOF) break; - case '"': - if (!escape) { - quotes = !quotes; - if (quotes) { - nonkw = 1; - qpos = yylval.colno; - } + } + return ERROR; +} + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return 0; + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + if (isalnum(c) || c == '_') { + *p++ = c; continue; } + *p = '\0'; + lungetc(c); + break; } - *p++ = c; - if (p == ebuf) { - yyerror("line too long"); - p = buf; + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return findeol(); } - escape = 0; + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; } -eow: - *p = 0; - if (c != EOF) - ungetc(c, yyfp); - if (p == buf) { - /* - * There could be a number of reason for empty buffer, - * and we handle all of them here, to avoid cluttering - * the main loop. - */ - if (c == EOF) - goto eof; - else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */ - goto repeat; + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return 0; + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return findeol(); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return STRING; } - if (!nonkw) { - kw = bsearch(buf, keywords, sizeof(keywords)/sizeof(keywords[0]), - sizeof(keywords[0]), kw_cmp); - if (kw != NULL) - return kw->token; + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return findeol(); + } + return NUM; + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return c; + } } - c = *buf; - if (!nonkw && (c == '-' || isdigit(c))) { - yylval.v.num = parse_portno(buf); - return TNUM; + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '/' || c == '.') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + yylval.v.string = xstrdup(buf); + return token; } - if (!nonkw && !strcmp(buf, "on")) { - yylval.v.num = 1; - return TBOOL; + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; } - if (!nonkw && !strcmp(buf, "off")) { - yylval.v.num = 0; - return TBOOL; + if (c == EOF) + return 0; + return c; +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + nfile = xcalloc(1, sizeof(*nfile)); + nfile->name = xstrdup(name); + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + yyerror("can't open %s: %s", nfile->name, + strerror(errno)); + free(nfile->name); + free(nfile); + return NULL; } - if ((str = strdup(buf)) == NULL) - err(1, "%s", __func__); - yylval.v.str = str; - return TSTRING; + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = xcalloc(1, nfile->ungetsize); + TAILQ_INSERT_TAIL(&files, nfile, entry); + return nfile; +} -eof: - if (ferror(yyfp)) - yyerror("input error reading config"); - return 0; -} +int +popfile(void) +{ + struct file *prev; -int -parse_portno(const char *p) -{ - const char *errstr; - int n; + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; - n = strtonum(p, 0, UINT16_MAX, &errstr); - if (errstr != NULL) - yyerror("port number is %s: %s", errstr, p); - return n; + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return file ? 0 : EOF; } void -parse_conf(const char *path) +parse_conf(const char *filename) { - struct sym *sym, *next; + struct sym *sym, *next; - config_path = path; - if ((yyfp = fopen(path, "r")) == NULL) - err(1, "cannot open config: %s", path); + file = pushfile(filename, 0); + if (file == NULL) + return; + topfile = file; + yyparse(); - fclose(yyfp); + errors = file->errors; + popfile(); - if (goterror) - exit(1); - - if (TAILQ_FIRST(&hosts)->domain == NULL) - errx(1, "no vhost defined in %s", path); - - /* free unused macros */ + /* Free macros and check which have not been used. */ TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { /* TODO: warn if !sym->used */ if (!sym->persist) { @@ -615,9 +757,101 @@ parse_conf(const char *path) free(sym); } } + + if (errors) + exit(1); } +int +symset(const char *name, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (!strcmp(name, sym->name)) + break; + } + + if (sym != NULL) { + if (sym->persist) + return 0; + else { + free(sym->name); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + sym = xcalloc(1, sizeof(*sym)); + sym->name = xstrdup(name); + sym->val = xstrdup(val); + sym->used = 0; + sym->persist = persist; + + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return 0; +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return -1; + sym = xcalloc(1, val - s + 1); + memcpy(sym, s, val - s); + ret = symset(sym, val + 1, 1); + free(sym); + return ret; +} + char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->name) == 0) { + sym->used = 1; + return sym->val; + } + } + return NULL; +} + +struct vhost * +new_vhost(void) +{ + return xcalloc(1, sizeof(struct vhost)); +} + +struct location * +new_location(void) +{ + struct location *l; + + l = xcalloc(1, sizeof(*l)); + l->dirfd = -1; + l->fcgi = -1; + return l; +} + +int +parse_portno(const char *p) +{ + const char *errstr; + int n; + + n = strtonum(p, 0, UINT16_MAX, &errstr); + if (errstr != NULL) + yyerror("port number is %s: %s", errstr, p); + return n; +} + +char * ensure_absolute_path(char *path) { if (path == NULL || *path != '/') @@ -743,64 +977,3 @@ add_param(char *name, char *val, int env) else TAILQ_INSERT_TAIL(h, e, envs); } - -int -symset(const char *name, const char *val, int persist) -{ - struct sym *sym; - - TAILQ_FOREACH(sym, &symhead, entry) { - if (!strcmp(name, sym->name)) - break; - } - - if (sym != NULL) { - if (sym->persist) - return 0; - else { - free(sym->name); - free(sym->val); - TAILQ_REMOVE(&symhead, sym, entry); - free(sym); - } - } - - sym = xcalloc(1, sizeof(*sym)); - sym->name = xstrdup(name); - sym->val = xstrdup(val); - sym->used = 0; - sym->persist = persist; - - TAILQ_INSERT_TAIL(&symhead, sym, entry); - return 0; -} - -int -cmdline_symset(char *s) -{ - char *sym, *val; - int ret; - - if ((val = strrchr(s, '=')) == NULL) - return -1; - sym = xcalloc(1, val - s + 1); - memcpy(sym, s, val - s); - ret = symset(sym, val + 1, 1); - free(sym); - return ret; -} - -char * -symget(const char *name) -{ - struct sym *sym; - - TAILQ_FOREACH(sym, &symhead, entry) { - if (!strcmp(name, sym->name)) { - sym->used = 1; - return sym->val; - } - } - - return NULL; -}