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

From:
Tracey Emery <tracey@traceyemery.net>
Subject:
Re: Make gotweb config parsing work with last diff
To:
gameoftrees@openbsd.org
Date:
Fri, 19 Jun 2020 16:48:27 -0600

Download raw body.

Thread
On Fri, Jun 19, 2020 at 02:44:49PM -0600, Tracey Emery wrote:
> Hello,
> 
> This builds on top of the last diff, making gotweb use the new library
> function.
> 
> Ok?
> 
> -- 
> 
> Tracey Emery

Sorry, I forgot that we don't require a config file for gotweb. So, I
reworked this slightly by adding a new error code and a note to remind
myself of this "feature."

Ok?

-- 

Tracey Emery

diff 340049eaf7e8e13e7a671bdc7774b91628c7863d /home/tracey/src/got
blob - 1bfc514197abcd84859f193604c899656ca61ca6
file + gotweb/gotweb.c
--- gotweb/gotweb.c
+++ gotweb/gotweb.c
@@ -31,6 +31,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <got_error.h>
 #include <got_object.h>
 #include <got_reference.h>
 #include <got_repository.h>
@@ -42,12 +43,11 @@
 #include <got_blame.h>
 #include <got_privsep.h>
 #include <got_opentemp.h>
+#include "got_config_parse.h"
 
 #include <kcgi.h>
 #include <kcgihtml.h>
 
-#include "gotweb.h"
-
 #ifndef nitems
 #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
 #endif
@@ -57,7 +57,7 @@ struct gw_trans {
 	TAILQ_HEAD(dirs, gw_dir)	 gw_dirs;
 	struct got_repository	*repo;
 	struct gw_dir		*gw_dir;
-	struct gotweb_conf	*gw_conf;
+	struct gotweb_config	*gw_conf;
 	struct ktemplate	*gw_tmpl;
 	struct khtmlreq		*gw_html_req;
 	struct kreq		*gw_req;
@@ -4656,7 +4656,7 @@ main(int argc, char *argv[])
 	}
 
 	if ((gw_trans->gw_conf =
-	    malloc(sizeof(struct gotweb_conf))) == NULL) {
+	    malloc(sizeof(struct gotweb_config))) == NULL) {
 		gw_malloc = 0;
 		error = got_error_from_errno("malloc");
 		goto done;
@@ -4680,7 +4680,8 @@ main(int argc, char *argv[])
 	gw_trans->gw_tmpl->keysz = TEMPL__MAX;
 	gw_trans->gw_tmpl->arg = gw_trans;
 	gw_trans->gw_tmpl->cb = gw_template;
-	error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
+
+	error = got_config_parse_gotweb_config(&gw_trans->gw_conf, GOTWEB_CONF);
 	if (error)
 		goto done;
 
@@ -4700,7 +4701,6 @@ done:
 		free(gw_trans->gw_conf->got_site_link);
 		free(gw_trans->gw_conf->got_logo);
 		free(gw_trans->gw_conf->got_logo_url);
-		free(gw_trans->gw_conf);
 		free(gw_trans->commit_id);
 		free(gw_trans->next_id);
 		free(gw_trans->next_prev_id);
blob - a2b02715b9a59a20e78eac7a01d868854f4fd3ac
file + /dev/null
--- gotweb/gotweb.h
+++ gotweb/gotweb.h
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2019, 2020 Tracey Emery <tracey@traceyemery.net>
- * Copyright (c) 2018, 2019 Stefan Sperling <stsp@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.
- */
-
-#ifndef GOTWEB_H
-#define GOTWEB_H
-
-#include <stdbool.h>
-
-#include <got_error.h>
-
-#define	GOTWEB_CONF	 "/etc/gotweb.conf"
-#define GOTWEB_TMPL_DIR	 "/cgi-bin/gw_tmpl"
-#define GOTWEB		 "/cgi-bin/gotweb/gotweb"
-
-#define GOTWEB_GOT_DIR	 ".got"
-#define GOTWEB_GIT_DIR	 ".git"
-
-#define D_GOTPATH	 "/got/public"
-#define D_SITENAME	 "Gotweb"
-#define D_SITEOWNER	 "Got Owner"
-#define D_SITELINK	 "Repos"
-#define D_GOTLOGO	 "got.png"
-#define D_GOTURL	 "https://gameoftrees.org"
-
-#define	D_SHOWROWNER	 true
-#define	D_SHOWSOWNER	 true
-#define D_SHOWAGE	 true
-#define D_SHOWDESC	 true
-#define D_SHOWURL	 true
-#define	D_MAXREPO	 0
-#define D_MAXREPODISP	 25
-#define D_MAXSLCOMMDISP	 10
-#define D_MAXCOMMITDISP	 25
-
-#define BUFFER_SIZE	 2048
-
-struct gotweb_conf {
-	char		*got_repos_path;
-	char		*got_site_name;
-	char		*got_site_owner;
-	char		*got_site_link;
-	char		*got_logo;
-	char		*got_logo_url;
-
-	size_t		 got_max_repos;
-	size_t		 got_max_repos_display;
-	size_t		 got_max_commits_display;
-
-	bool		 got_show_site_owner;
-	bool		 got_show_repo_owner;
-	bool		 got_show_repo_age;
-	bool		 got_show_repo_description;
-	bool		 got_show_repo_cloneurl;
-};
-
-const struct got_error*	 parse_conf(const char *, struct gotweb_conf *);
-
-#endif /* GOTWEB_H */
blob - 2271bbde9b190920695f4451cfe046353cbe8ec7
file + /dev/null
--- gotweb/parse.y
+++ gotweb/parse.y
@@ -1,517 +0,0 @@
-/*
- * Copyright (c) 2019 Tracey Emery <tracey@traceyemery.net>
- * 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 <ctype.h>
-#include <err.h>
-#include <limits.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "gotweb.h"
-
-TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
-static struct file {
-	TAILQ_ENTRY(file)	 entry;
-	FILE			*stream;
-	char			*name;
-	int			 lineno;
-	int			 errors;
-	const struct got_error*	 error;
-} *file, *topfile;
-struct file	*pushfile(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		 lgetc(int);
-int		 lungetc(int);
-int		 findeol(void);
-
-static const struct got_error*	 gerror = NULL;
-char				*syn_err;
-
-struct gotweb_conf		*gw_conf;
-
-typedef struct {
-	union {
-		int64_t			 number;
-		char			*string;
-	} v;
-	int lineno;
-} YYSTYPE;
-
-%}
-
-%token	GOT_WWW_PATH GOT_MAX_REPOS GOT_SITE_NAME GOT_SITE_OWNER GOT_SITE_LINK
-%token	GOT_LOGO GOT_LOGO_URL GOT_SHOW_REPO_OWNER GOT_SHOW_REPO_AGE
-%token	GOT_SHOW_REPO_DESCRIPTION GOT_MAX_REPOS_DISPLAY GOT_REPOS_PATH
-%token	GOT_MAX_COMMITS_DISPLAY ON ERROR GOT_SHOW_SITE_OWNER
-%token	GOT_SHOW_REPO_CLONEURL
-%token	<v.string>		STRING
-%token	<v.number>		NUMBER
-%type	<v.number>		boolean
-%%
-
-grammar		: /* empty */
-		| grammar '\n'
-		| grammar main '\n'
-		| grammar error '\n'		{ file->errors++; }
-		;
-
-boolean		: STRING			{
-			if (strcasecmp($1, "true") == 0 ||
-			    strcasecmp($1, "yes") == 0)
-				$$ = 1;
-			else if (strcasecmp($1, "false") == 0 ||
-			    strcasecmp($1, "off") == 0 ||
-			    strcasecmp($1, "no") == 0)
-				$$ = 0;
-			else {
-				yyerror("invalid boolean value '%s'", $1);
-				free($1);
-				YYERROR;
-			}
-			free($1);
-		}
-		| ON				{ $$ = 1; }
-		;
-
-main		: GOT_REPOS_PATH STRING {
-			if ((gw_conf->got_repos_path = strdup($2)) == NULL)
-				errx(1, "out of memory");
-		}
-		| GOT_MAX_REPOS NUMBER {
-			if ($2 > 0)
-				gw_conf->got_max_repos = $2;
-		}
-		| GOT_SITE_NAME STRING {
-				if ((gw_conf->got_site_name = strdup($2)) == NULL)
-				errx(1, "out of memory");
-		}
-		| GOT_SITE_OWNER STRING {
-				if ((gw_conf->got_site_owner = strdup($2)) == NULL)
-				errx(1, "out of memory");
-		}
-		| GOT_SITE_LINK STRING {
-				if ((gw_conf->got_site_link = strdup($2)) == NULL)
-				errx(1, "out of memory");
-		}
-		| GOT_LOGO STRING {
-				if ((gw_conf->got_logo = strdup($2)) == NULL)
-				errx(1, "out of memory");
-		}
-		| GOT_LOGO_URL STRING {
-				if ((gw_conf->got_logo_url = strdup($2)) == NULL)
-				errx(1, "out of memory");
-		}
-		| GOT_SHOW_SITE_OWNER boolean {
-			gw_conf->got_show_site_owner = $2;
-		}
-		| GOT_SHOW_REPO_OWNER boolean {
-			gw_conf->got_show_repo_owner = $2;
-		}
-		| GOT_SHOW_REPO_AGE boolean { gw_conf->got_show_repo_age = $2; }
-		| GOT_SHOW_REPO_DESCRIPTION boolean {
-			gw_conf->got_show_repo_description =	$2;
-		}
-		| GOT_SHOW_REPO_CLONEURL boolean {
-			gw_conf->got_show_repo_cloneurl =	$2;
-		}
-		| GOT_MAX_REPOS_DISPLAY NUMBER {
-			if ($2 > 0)
-				gw_conf->got_max_repos_display = $2;
-		}
-		| GOT_MAX_COMMITS_DISPLAY NUMBER {
-			if ($2 > 0)
-				gw_conf->got_max_commits_display = $2;
-		}
-		;
-
-%%
-
-struct keywords {
-	const char	*k_name;
-	int		 k_val;
-};
-
-int
-yyerror(const char *fmt, ...)
-{
-	va_list		 ap;
-	char		*msg = NULL;
-	static char	 err_msg[512];
-
-	file->errors++;
-	va_start(ap, fmt);
-	if (vasprintf(&msg, fmt, ap) == -1)
-		errx(1, "yyerror vasprintf");
-	va_end(ap);
-	snprintf(err_msg, sizeof(err_msg), "%s:%d: %s", file->name,
-	    yylval.lineno, msg);
-	gerror = got_error_from_errno2("parse_error", err_msg);
-
-	free(msg);
-	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[] = {
-		{ "got_logo",			GOT_LOGO },
-		{ "got_logo_url",		GOT_LOGO_URL },
-		{ "got_max_commits_display",	GOT_MAX_COMMITS_DISPLAY },
-		{ "got_max_repos",		GOT_MAX_REPOS },
-		{ "got_max_repos_display",	GOT_MAX_REPOS_DISPLAY },
-		{ "got_repos_path",		GOT_REPOS_PATH },
-		{ "got_show_repo_age",		GOT_SHOW_REPO_AGE },
-		{ "got_show_repo_cloneurl",	GOT_SHOW_REPO_CLONEURL },
-		{ "got_show_repo_description",	GOT_SHOW_REPO_DESCRIPTION },
-		{ "got_show_repo_owner",	GOT_SHOW_REPO_OWNER },
-		{ "got_show_site_owner",	GOT_SHOW_SITE_OWNER },
-		{ "got_site_link",		GOT_SITE_LINK },
-		{ "got_site_name",		GOT_SITE_NAME },
-		{ "got_site_owner",		GOT_SITE_OWNER },
-	};
-	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 MAXPUSHBACK	128
-
-u_char	*parsebuf;
-int	 parseindex;
-u_char	 pushback_buffer[MAXPUSHBACK];
-int	 pushback_index = 0;
-
-int
-lgetc(int quotec)
-{
-	int		c, next;
-
-	if (parsebuf) {
-		/* Read character from the parsebuffer instead of input. */
-		if (parseindex >= 0) {
-			c = parsebuf[parseindex++];
-			if (c != '\0')
-				return (c);
-			parsebuf = NULL;
-		} else
-			parseindex++;
-	}
-
-	if (pushback_index)
-		return (pushback_buffer[--pushback_index]);
-
-	if (quotec) {
-		if ((c = getc(file->stream)) == EOF) {
-			yyerror("reached end of file while parsing "
-			    "quoted string");
-			if (file == topfile || popfile() == EOF)
-				return (EOF);
-			return (quotec);
-		}
-		return (c);
-	}
-
-	while ((c = getc(file->stream)) == '\\') {
-		next = getc(file->stream);
-		if (next != '\n') {
-			c = next;
-			break;
-		}
-		yylval.lineno = file->lineno;
-		file->lineno++;
-	}
-
-	while (c == EOF) {
-		if (file == topfile || popfile() == EOF)
-			return (EOF);
-		c = getc(file->stream);
-	}
-	return (c);
-}
-
-int
-lungetc(int c)
-{
-	if (c == EOF)
-		return (EOF);
-	if (parsebuf) {
-		parseindex--;
-		if (parseindex >= 0)
-			return (c);
-	}
-	if (pushback_index < MAXPUSHBACK-1)
-		return (pushback_buffer[pushback_index++] = c);
-	else
-		return (EOF);
-}
-
-int
-findeol(void)
-{
-	int	c;
-
-	parsebuf = NULL;
-
-	/* skip to either EOF or the first real EOL */
-	while (1) {
-		if (pushback_index)
-			c = pushback_buffer[--pushback_index];
-		else
-			c = lgetc(0);
-		if (c == '\n') {
-			file->lineno++;
-			break;
-		}
-		if (c == EOF)
-			break;
-	}
-	return (ERROR);
-}
-
-int
-yylex(void)
-{
-	u_char	 buf[8096];
-	u_char	*p;
-	int	 quotec, next, c;
-	int	 token;
-
-	p = buf;
-	while ((c = lgetc(0)) == ' ' || c == '\t')
-		; /* nothing */
-
-	yylval.lineno = file->lineno;
-	if (c == '#')
-		while ((c = lgetc(0)) != '\n' && c != EOF)
-			; /* nothing */
-
-	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)
-			errx(1, "yylex: strdup");
-		return (STRING);
-	}
-
-#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 (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 != '=' && x != '/' && x != '#' && \
-	x != ','))
-
-	if (isalnum(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)
-			if ((yylval.v.string = strdup(buf)) == NULL)
-				errx(1, "yylex: strdup");
-		return (token);
-	}
-	if (c == '\n') {
-		yylval.lineno = file->lineno;
-		file->lineno++;
-	}
-	if (c == EOF)
-		return (0);
-	return (c);
-}
-
-struct file *
-pushfile(const char *name)
-{
-	struct file	*nfile;
-
-	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
-		gerror = got_error(GOT_ERR_NO_SPACE);
-		return (NULL);
-	}
-	if ((nfile->name = strdup(name)) == NULL) {
-		gerror = got_error(GOT_ERR_NO_SPACE);
-		free(nfile);
-		return (NULL);
-	}
-	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
-		gerror = got_error_from_errno2("parse_conf", nfile->name);
-		free(nfile->name);
-		free(nfile);
-		return (NULL);
-	}
-	nfile->lineno = 1;
-	TAILQ_INSERT_TAIL(&files, nfile, entry);
-	return (nfile);
-}
-
-int
-popfile(void)
-{
-	struct file	*prev;
-
-	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
-		prev->errors += file->errors;
-
-	TAILQ_REMOVE(&files, file, entry);
-	fclose(file->stream);
-	free(file->name);
-	free(file);
-	file = prev;
-	return (file ? 0 : EOF);
-}
-
-const struct got_error*
-parse_conf(const char *filename, struct gotweb_conf *gconf)
-{
-	static const struct got_error*	 error = NULL;
-
-	gw_conf = gconf;
-	if ((gw_conf->got_repos_path = strdup(D_GOTPATH)) == NULL)
-	err(1, "strdup");
-	if ((gw_conf->got_site_name = strdup(D_SITENAME)) == NULL)
-	err(1, "strdup");
-	if ((gw_conf->got_site_owner = strdup(D_SITEOWNER)) == NULL)
-	err(1, "strdup");
-	if ((gw_conf->got_site_link = strdup(D_SITELINK)) == NULL)
-	err(1, "strdup");
-	if ((gw_conf->got_logo = strdup(D_GOTLOGO)) == NULL)
-	err(1, "strdup");
-	if ((gw_conf->got_logo_url = strdup(D_GOTURL)) == NULL)
-	err(1, "strdup");
-	gw_conf->got_show_site_owner = D_SHOWSOWNER;
-	gw_conf->got_show_repo_owner = D_SHOWROWNER;
-	gw_conf->got_show_repo_age = D_SHOWAGE;
-	gw_conf->got_show_repo_description = D_SHOWDESC;
-	gw_conf->got_show_repo_cloneurl = D_SHOWURL;
-	gw_conf->got_max_repos = D_MAXREPO;
-	gw_conf->got_max_repos_display = D_MAXREPODISP;
-	gw_conf->got_max_commits_display = D_MAXCOMMITDISP;
-	if ((file = pushfile(filename)) == NULL) {
-		goto done;
-	}
-	topfile = file;
-
-	yyparse();
-	popfile();
-	if (gerror)
-		error = gerror;
-done:
-	return error;
-}
blob - 86074676dcb36a4dac9544e5dc676523abe57cab
file + include/got_config_parse.h
--- include/got_config_parse.h
+++ include/got_config_parse.h
@@ -14,6 +14,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <stdbool.h>
+
 struct got_config_list_entry {
 	TAILQ_ENTRY(got_config_list_entry) entry;
 	const char	*remote;
@@ -24,9 +26,59 @@ struct got_config_list_entry {
 };
 TAILQ_HEAD(got_config_list, got_config_list_entry);
 
+#define	GOTWEB_CONF	 "/etc/gotweb.conf"
+#define GOTWEB_TMPL_DIR	 "/cgi-bin/gw_tmpl"
+#define GOTWEB		 "/cgi-bin/gotweb/gotweb"
+
+#define GOTWEB_GOT_DIR	 ".got"
+#define GOTWEB_GIT_DIR	 ".git"
+
+#define D_GOTPATH	 "/got/public"
+#define D_SITENAME	 "Gotweb"
+#define D_SITEOWNER	 "Got Owner"
+#define D_SITELINK	 "Repos"
+#define D_GOTLOGO	 "got.png"
+#define D_GOTURL	 "https://gameoftrees.org"
+
+#define	D_SHOWROWNER	 true
+#define	D_SHOWSOWNER	 true
+#define D_SHOWAGE	 true
+#define D_SHOWDESC	 true
+#define D_SHOWURL	 true
+#define	D_MAXREPO	 0
+#define D_MAXREPODISP	 25
+#define D_MAXSLCOMMDISP	 10
+#define D_MAXCOMMITDISP	 25
+
+struct gotweb_config {
+	char		*got_repos_path;
+	char		*got_site_name;
+	char		*got_site_owner;
+	char		*got_site_link;
+	char		*got_logo;
+	char		*got_logo_url;
+
+	size_t		 got_max_repos;
+	size_t		 got_max_repos_display;
+	size_t		 got_max_commits_display;
+
+	bool		 got_show_site_owner;
+	bool		 got_show_repo_owner;
+	bool		 got_show_repo_age;
+	bool		 got_show_repo_description;
+	bool		 got_show_repo_cloneurl;
+};
+
 /*
  * 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);
+
+/*
+ * Parse gotweb configuration
+ * Loads gotweb_config struct
+ */
+const struct got_error*	 got_config_parse_gotweb_config(struct gotweb_config **,
     char *filename);
blob - de101511cf4ff533f885dcc7bbd634aa195dc840
file + include/got_error.h
--- include/got_error.h
+++ include/got_error.h
@@ -142,6 +142,7 @@
 #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;
@@ -290,6 +291,7 @@ static const struct got_error {
 	{ 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, "config file does not exist" },
 };
 
 /*
blob - dd1e0a6c7c9c05d864b33e126456dcef12734744
file + lib/parse.y
--- lib/parse.y
+++ lib/parse.y
@@ -37,6 +37,7 @@
 #include <ifaddrs.h>
 #include <imsg.h>
 #include <limits.h>
+#include <stdbool.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
@@ -84,10 +85,13 @@ 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_entry *got_config;
 struct got_config_list got_config_list;
-static const struct got_error*	new_remote(struct got_config_list_entry **);
+static const struct got_error* new_remote(struct got_config_list_entry **);
 
+struct gotweb_config gw_conf;
+
 typedef struct {
 	union {
 		int64_t		 number;
@@ -99,22 +103,102 @@ typedef struct {
 %}
 
 %token	ERROR
+%token	GOT_WWW_PATH GOT_MAX_REPOS GOT_SITE_NAME GOT_SITE_OWNER GOT_SITE_LINK
+%token	GOT_LOGO GOT_LOGO_URL GOT_SHOW_REPO_OWNER GOT_SHOW_REPO_AGE
+%token	GOT_SHOW_REPO_DESCRIPTION GOT_MAX_REPOS_DISPLAY GOT_REPOS_PATH
+%token	GOT_MAX_COMMITS_DISPLAY ON GOT_SHOW_SITE_OWNER
+%token	GOT_SHOW_REPO_CLONEURL
 %token	REMOTE REPOSITORY SERVER PROTOCOL USER
 %token	<v.string>	STRING
 %token	<v.number>	NUMBER
+%type	<v.number>	boolean
 
 %%
 
 grammar		: /* empty */
 		| grammar '\n'
+		| grammar main '\n'
 		| grammar remote '\n'
 		;
+boolean		: STRING			{
+			if (strcasecmp($1, "true") == 0 ||
+			    strcasecmp($1, "yes") == 0)
+				$$ = 1;
+			else if (strcasecmp($1, "false") == 0 ||
+			    strcasecmp($1, "off") == 0 ||
+			    strcasecmp($1, "no") == 0)
+				$$ = 0;
+			else {
+				yyerror("invalid boolean value '%s'", $1);
+				free($1);
+				YYERROR;
+			}
+			free($1);
+		}
+		| ON				{ $$ = 1; }
+		;
+main		: GOT_REPOS_PATH STRING {
+			gw_conf.got_repos_path = strdup($2);
+			if (gw_conf.got_repos_path== NULL)
+				errx(1, "out of memory");
+		}
+		| GOT_MAX_REPOS NUMBER {
+			if ($2 > 0)
+				gw_conf.got_max_repos = $2;
+		}
+		| GOT_SITE_NAME STRING {
+				gw_conf.got_site_name = strdup($2);
+				if (gw_conf.got_site_name == NULL)
+					errx(1, "out of memory");
+		}
+		| GOT_SITE_OWNER STRING {
+				gw_conf.got_site_owner = strdup($2);
+				if (gw_conf.got_site_owner == NULL)
+					errx(1, "out of memory");
+		}
+		| GOT_SITE_LINK STRING {
+				gw_conf.got_site_link = strdup($2);
+				if (gw_conf.got_site_link == NULL)
+					errx(1, "out of memory");
+		}
+		| GOT_LOGO STRING {
+				gw_conf.got_logo = strdup($2);
+				if (gw_conf.got_logo== NULL)
+					errx(1, "out of memory");
+		}
+		| GOT_LOGO_URL STRING {
+				gw_conf.got_logo_url = strdup($2);
+				if (gw_conf.got_logo_url== NULL)
+					errx(1, "out of memory");
+		}
+		| GOT_SHOW_SITE_OWNER boolean {
+			gw_conf.got_show_site_owner = $2;
+		}
+		| GOT_SHOW_REPO_OWNER boolean {
+			gw_conf.got_show_repo_owner = $2;
+		}
+		| GOT_SHOW_REPO_AGE boolean { gw_conf.got_show_repo_age = $2; }
+		| GOT_SHOW_REPO_DESCRIPTION boolean {
+			gw_conf.got_show_repo_description =	$2;
+		}
+		| GOT_SHOW_REPO_CLONEURL boolean {
+			gw_conf.got_show_repo_cloneurl =	$2;
+		}
+		| GOT_MAX_REPOS_DISPLAY NUMBER {
+			if ($2 > 0)
+				gw_conf.got_max_repos_display = $2;
+		}
+		| GOT_MAX_COMMITS_DISPLAY NUMBER {
+			if ($2 > 0)
+				gw_conf.got_max_commits_display = $2;
+		}
+		;
 remoteopts2	: remoteopts2 remoteopts1 nl
 	    	| remoteopts1 optnl
 		;
 remoteopts1	: REPOSITORY STRING {
-	    		gotconfig->repository = strdup($2);
-			if (gotconfig->repository == NULL) {
+	    		got_config->repository = strdup($2);
+			if (got_config->repository == NULL) {
 				free($2);
 				yyerror("strdup");
 				YYERROR;
@@ -122,8 +206,8 @@ remoteopts1	: REPOSITORY STRING {
 			free($2);
 	    	}
 	    	| SERVER STRING {
-	    		gotconfig->server = strdup($2);
-			if (gotconfig->server == NULL) {
+	    		got_config->server = strdup($2);
+			if (got_config->server == NULL) {
 				free($2);
 				yyerror("strdup");
 				YYERROR;
@@ -131,8 +215,8 @@ remoteopts1	: REPOSITORY STRING {
 			free($2);
 		}
 		| PROTOCOL STRING {
-	    		gotconfig->protocol = strdup($2);
-			if (gotconfig->protocol == NULL) {
+	    		got_config->protocol = strdup($2);
+			if (got_config->protocol == NULL) {
 				free($2);
 				yyerror("strdup");
 				YYERROR;
@@ -140,8 +224,8 @@ remoteopts1	: REPOSITORY STRING {
 			free($2);
 		}
 		| USER STRING {
-	    		gotconfig->user = strdup($2);
-			if (gotconfig->user == NULL) {
+	    		got_config->user = strdup($2);
+			if (got_config->user == NULL) {
 				free($2);
 				yyerror("strdup");
 				YYERROR;
@@ -152,21 +236,21 @@ remoteopts1	: REPOSITORY STRING {
 remote		: REMOTE STRING {
 			static const struct got_error* error;
 
-			error = new_remote(&gotconfig);
+			error = new_remote(&got_config);
 			if (error) {
 				free($2);
 				yyerror("%s", error->msg);
 				YYERROR;
 			}
-			gotconfig->remote = strdup($2);
-			if (gotconfig->remote == NULL) {
+			got_config->remote = strdup($2);
+			if (got_config->remote == NULL) {
 				free($2);
 				yyerror("strdup");
 				YYERROR;
 			}
 			free($2);
 		} '{' optnl remoteopts2 '}' {
-			TAILQ_INSERT_TAIL(&got_config_list, gotconfig, entry);
+			TAILQ_INSERT_TAIL(&got_config_list, got_config, entry);
 		}
 		;
 optnl		: '\n' optnl
@@ -215,11 +299,25 @@ lookup(char *s)
 {
 	/* This has to be sorted always. */
 	static const struct keywords keywords[] = {
-		{"protocol",		PROTOCOL},
-		{"remote",		REMOTE},
-		{"repository",		REPOSITORY},
-		{"server",		SERVER},
-		{"user",		USER},
+		{ "got_logo",			GOT_LOGO },
+		{ "got_logo_url",		GOT_LOGO_URL },
+		{ "got_max_commits_display",	GOT_MAX_COMMITS_DISPLAY },
+		{ "got_max_repos",		GOT_MAX_REPOS },
+		{ "got_max_repos_display",	GOT_MAX_REPOS_DISPLAY },
+		{ "got_repos_path",		GOT_REPOS_PATH },
+		{ "got_show_repo_age",		GOT_SHOW_REPO_AGE },
+		{ "got_show_repo_cloneurl",	GOT_SHOW_REPO_CLONEURL },
+		{ "got_show_repo_description",	GOT_SHOW_REPO_DESCRIPTION },
+		{ "got_show_repo_owner",	GOT_SHOW_REPO_OWNER },
+		{ "got_show_site_owner",	GOT_SHOW_SITE_OWNER },
+		{ "got_site_link",		GOT_SITE_LINK },
+		{ "got_site_name",		GOT_SITE_NAME },
+		{ "got_site_owner",		GOT_SITE_OWNER },
+		{ "protocol",			PROTOCOL },
+		{ "remote",			REMOTE },
+		{ "repository",			REPOSITORY },
+		{ "server",			SERVER },
+		{ "user",			USER },
 	};
 	const struct keywords	*p;
 
@@ -506,9 +604,9 @@ pushfile(struct file **nfile, const char *name)
 		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));
-		error = got_error_from_errno(msg);
 		free(msg);
 		return error;
 	}
@@ -526,10 +624,10 @@ pushfile(struct file **nfile, const char *name)
 }
 
 static const struct got_error*
-new_remote(struct got_config_list_entry **gotconfig) {
+new_remote(struct got_config_list_entry **got_config) {
 	const struct got_error* error = NULL;
 
-	if (((*gotconfig) = calloc(1, sizeof(struct got_config_list_entry))) ==
+	if (((*got_config) = calloc(1, sizeof(struct got_config_list_entry))) ==
 	    NULL)
 	    error = got_error_from_errno("calloc");
 	return error;
@@ -578,6 +676,48 @@ got_config_parse_got_config(struct got_config_list **c
 	return gerror;
 }
 
+const struct got_error*
+got_config_parse_gotweb_config(struct gotweb_config **gconf, char *filename)
+{
+	if ((gw_conf.got_repos_path = strdup(D_GOTPATH)) == NULL)
+	err(1, "strdup");
+	if ((gw_conf.got_site_name = strdup(D_SITENAME)) == NULL)
+	err(1, "strdup");
+	if ((gw_conf.got_site_owner = strdup(D_SITEOWNER)) == NULL)
+	err(1, "strdup");
+	if ((gw_conf.got_site_link = strdup(D_SITELINK)) == NULL)
+	err(1, "strdup");
+	if ((gw_conf.got_logo = strdup(D_GOTLOGO)) == NULL)
+	err(1, "strdup");
+	if ((gw_conf.got_logo_url = strdup(D_GOTURL)) == NULL)
+	err(1, "strdup");
+	gw_conf.got_show_site_owner = D_SHOWSOWNER;
+	gw_conf.got_show_repo_owner = D_SHOWROWNER;
+	gw_conf.got_show_repo_age = D_SHOWAGE;
+	gw_conf.got_show_repo_description = D_SHOWDESC;
+	gw_conf.got_show_repo_cloneurl = D_SHOWURL;
+	gw_conf.got_max_repos = D_MAXREPO;
+	gw_conf.got_max_repos_display = D_MAXREPODISP;
+	gw_conf.got_max_commits_display = D_MAXCOMMITDISP;
+
+	/*
+	 * We don't require that the gotweb config file exists
+	 * So reset gerror if it doesn't exist 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;
+	topfile = file;
+
+	yyparse();
+	popfile();
+done:
+	*gconf = &gw_conf;
+	return gerror;
+}
 int
 symset(const char *nam, const char *val, int persist)
 {