From: Tracey Emery Subject: New library config parse.y start To: gameoftrees@openbsd.org Date: Fri, 19 Jun 2020 10:34:39 -0600 Hello, The following diff integrates config parsing to the library. This is the first step to adding gotconfig to individual git repos for git push and git fetch, as discussed with Stefan off list. This also paves the way for centralized parsing for gotweb and gotd. After this, I'll be working on moving gotweb grammar and getting that to work. Once in, we can start tightening up functions, as this is mostly stock from newd. Ok? -- Tracey Emery diff 2c2d5c5f4cce08cb69d03aea1a1c747fd27f9e39 /home/tracey/src/got blob - /dev/null file + include/got_config_parse.h --- include/got_config_parse.h +++ include/got_config_parse.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Tracey Emery + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct got_config_list_entry { + TAILQ_ENTRY(got_config_list_entry) entry; + const char *remote; + const char *repository; + const char *server; + const char *protocol; + const char *user; +}; +TAILQ_HEAD(got_config_list, got_config_list_entry); + +/* + * Parse individual gotconfig repository files + * Load got_config_list_entry struct and insert to got_config_list TAILQ + */ +const struct got_error* got_config_parse_got_config(struct got_config_list **, + char *filename); blob - /dev/null file + lib/parse.y --- lib/parse.y +++ lib/parse.y @@ -0,0 +1,655 @@ +/* + * Copyright (c) 2020, Tracey Emery + * 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 + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_error.h" +#include "got_config_parse.h" + +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; +} *file, *topfile; +static const struct got_error* pushfile(struct file**, const char *); +int popfile(void); +int yyparse(void); +int yylex(void); +int 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); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); + +const struct got_error* gerror = NULL; +struct got_config_list_entry *gotconfig; +struct got_config_list got_config_list; +static const struct got_error* new_remote(struct got_config_list_entry **); + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ERROR +%token REMOTE REPOSITORY SERVER PROTOCOL USER +%token STRING +%token NUMBER + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar remote '\n' + ; +remoteopts2 : remoteopts2 remoteopts1 nl + | remoteopts1 optnl + ; +remoteopts1 : REPOSITORY STRING { + gotconfig->repository = strdup($2); + if (gotconfig->repository == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + | SERVER STRING { + gotconfig->server = strdup($2); + if (gotconfig->server == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + | PROTOCOL STRING { + gotconfig->protocol = strdup($2); + if (gotconfig->protocol == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + | USER STRING { + gotconfig->user = strdup($2); + if (gotconfig->user == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } + ; +remote : REMOTE STRING { + static const struct got_error* error; + + error = new_remote(&gotconfig); + if (error) { + free($2); + yyerror("%s", error->msg); + YYERROR; + } + gotconfig->remote = strdup($2); + if (gotconfig->remote == NULL) { + free($2); + yyerror("strdup"); + YYERROR; + } + free($2); + } '{' optnl remoteopts2 '}' { + TAILQ_INSERT_TAIL(&got_config_list, gotconfig, entry); + } + ; +optnl : '\n' optnl + | /* empty */ + ; +nl : '\n' optnl + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + char *err = NULL; + + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) { + gerror = got_error_from_errno("vasprintf"); + return 0; + } + va_end(ap); + if (asprintf(&err, "%s:%d: %s", file->name, yylval.lineno, msg) == -1) { + gerror = got_error_from_errno("asprintf"); + return(0); + } + gerror = got_error_msg(GOT_ERR_PARSE_Y_YY, strdup(err)); + free(msg); + free(err); + return(0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"protocol", PROTOCOL}, + {"remote", REMOTE}, + {"repository", REPOSITORY}, + {"server", SERVER}, + {"user", USER}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + 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); +} + +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); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + 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, "%s", __func__); + 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; + } + if (c == EOF) + break; + } + 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; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + 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 || c == ' ' || c == '\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, "%s", __func__); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(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 (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(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) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "%s", __func__); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +static const struct got_error* +pushfile(struct file **nfile, const char *name) +{ + const struct got_error* error = NULL; + + if (((*nfile) = calloc(1, sizeof(struct file))) == NULL) + return got_error_from_errno2(__func__, "calloc"); + if (((*nfile)->name = strdup(name)) == NULL) { + free(nfile); + return got_error_from_errno2(__func__, "strdup"); + } + if (((*nfile)->stream = fopen((*nfile)->name, "r")) == NULL) { + char *msg = NULL; + if (asprintf(&msg, "%s", (*nfile)->name) == -1) + return got_error_from_errno("asprintf"); + free((*nfile)->name); + free((*nfile)); + error = got_error_from_errno(msg); + free(msg); + return error; + } + (*nfile)->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + (*nfile)->ungetsize = 16; + (*nfile)->ungetbuf = malloc((*nfile)->ungetsize); + if ((*nfile)->ungetbuf == NULL) { + fclose((*nfile)->stream); + free((*nfile)->name); + free((*nfile)); + return got_error_from_errno2(__func__, "malloc"); + } + TAILQ_INSERT_TAIL(&files, (*nfile), entry); + return error; +} + +static const struct got_error* +new_remote(struct got_config_list_entry **gotconfig) { + const struct got_error* error = NULL; + + if (((*gotconfig) = calloc(1, sizeof(struct got_config_list_entry))) == + NULL) + error = got_error_from_errno("calloc"); + return error; +} + +int +popfile(void) +{ + struct file *prev = NULL; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +const struct got_error* +got_config_parse_got_config(struct got_config_list **conf_list, char *filename) +{ + struct sym *sym, *next; + + *conf_list = NULL; + TAILQ_INIT(&got_config_list); + gerror = pushfile(&file, filename); + if (gerror) + return gerror; + topfile = file; + + yyparse(); + popfile(); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + *conf_list = &got_config_list; + return gerror; +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + 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->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +}