"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: parse.y for got
To:
Tracey Emery <tracey@traceyemery.net>
Cc:
gameoftrees@openbsd.org
Date:
Mon, 22 Jun 2020 17:38:51 +0200

Download raw body.

Thread
On Mon, Jun 22, 2020 at 08:52:10AM -0600, Tracey Emery wrote:
> Hello,
> 
> This diff rewinds parse.y back to strictly worrying about gotconfig in
> individual git repos. I tested this in got, but for this diff, I did not
> include got.h to got.c and did not add parse.y to the Makefile, since we
> aren't using it yet.
> 
> Ok?

Yes. This is a great starting point!

I'm wondering if this code is prepared to deal with 'repository-wide' settings,
such as an author handle to use for commits? It seems to be designed around
a list of per-remote-repository information. Which is fine, but we might want
to extend this later to cover a list of remote repository plus any other
settings we might want to parse from the config file.

> diff 2c2d5c5f4cce08cb69d03aea1a1c747fd27f9e39 /home/tracey/src/got
> blob - /dev/null
> file + got/got.h
> --- got/got.h
> +++ got/got.h
> @@ -0,0 +1,32 @@
> +/*
> + * Copyright (c) 2020 Tracey Emery <tracey@openbsd.org>
> + *
> + * 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* parse_got_config(struct got_config_list **,
> +    char *filename);
> blob - /dev/null
> file + got/parse.y
> --- got/parse.y
> +++ got/parse.y
> @@ -0,0 +1,665 @@
> +/*
> + * Copyright (c) 2020, Tracey Emery <tracey@openbsd.org>
> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
> + * 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 <sys/types.h>
> +#include <sys/queue.h>
> +#include <sys/socket.h>
> +#include <sys/stat.h>
> +
> +#include <netinet/in.h>
> +
> +#include <arpa/inet.h>
> +
> +#include <ctype.h>
> +#include <err.h>
> +#include <errno.h>
> +#include <event.h>
> +#include <ifaddrs.h>
> +#include <imsg.h>
> +#include <limits.h>
> +#include <stdarg.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <syslog.h>
> +#include <unistd.h>
> +
> +#include "got_error.h"
> +#include "got.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	<v.string>	STRING
> +%token	<v.number>	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");
> +		error = got_error_msg(GOT_ERR_NO_CONFIG_FILE, msg);
> +		free((*nfile)->name);
> +		free((*nfile));
> +		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*
> +parse_got_config(struct got_config_list **conf_list, char *filename)
> +{
> +	struct sym	*sym, *next;
> +
> +	*conf_list = NULL;
> +
> +	/*
> +	 * We don't require that gotconfig exists
> +	 * So, null gerror and goto done
> +	 */
> +	gerror = pushfile(&file, filename);
> +	if (gerror && gerror->code == GOT_ERR_NO_CONFIG_FILE) {
> +		gerror = NULL;
> +		goto done;
> +	}
> +	else if (gerror)
> +		return gerror;
> +
> +	TAILQ_INIT(&got_config_list);
> +	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);
> +		}
> +	}
> +done:
> +	*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);
> +}
> blob - 50fe098796823909a7aa2c98d1b90d66536e434c
> file + include/got_error.h
> --- include/got_error.h
> +++ include/got_error.h
> @@ -141,6 +141,8 @@
>  #define GOT_ERR_FETCH_NO_BRANCH	124
>  #define GOT_ERR_FETCH_BAD_REF	125
>  #define GOT_ERR_TREE_ENTRY_TYPE	126
> +#define GOT_ERR_PARSE_Y_YY	127
> +#define GOT_ERR_NO_CONFIG_FILE	128
>  
>  static const struct got_error {
>  	int code;
> @@ -288,6 +290,8 @@ static const struct got_error {
>  	{ GOT_ERR_FETCH_NO_BRANCH, "could not find any branches to fetch" },
>  	{ GOT_ERR_FETCH_BAD_REF, "reference cannot be fetched" },
>  	{ GOT_ERR_TREE_ENTRY_TYPE, "unexpected tree entry type" },
> +	{ GOT_ERR_PARSE_Y_YY, "yyerror error" },
> +	{ GOT_ERR_NO_CONFIG_FILE, "configuration file doesn't exit" },
>  };
>  
>  /*
> 
>