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

From:
Stefan Sperling <stsp@stsp.name>
Subject:
gotwebd fcgi/gotweb process split
To:
gameoftrees@openbsd.org
Date:
Wed, 9 Apr 2025 18:31:42 +0200

Download raw body.

Thread
I would like gotwebd to be able to browse repositories outside the web
server's chroot directory in order to save disk space on servers and
avoid the synchronization delay between the gotd repository and the
gotwebd copy in /var/www.

And Omar pointed out that the gotwebd-specific libexec helper binaries
are causing problems in -portable.

This patch addresses both problems by splitting the FCGI server part
and the part which reads Git repositories into separate processes.

The FCGI part still runs in chroot as before, but the gotweb part runs
outside chroot. gotwebd will need a run-time dependency on the main got
package since libexec helpers are now shared between got/tog and gotwebd.

Since gotwebd doesn't really need to read anything from var/www, default
to a new _gotwebd user instead of www. www can still be used if desired.

This patch is still work-in-progress. It lacks documentation updates,
and one TODO is mentioned in the code.

However, since this is working for me with a simple configuration file
I want to share it for testing and review feedback.

I am testing with the following gotwebd.conf file:

[[[
chroot "/var/www"
prefork 1
user www

server "localhost" {
	repos_path "/git"
}
]]]


The diff:

M  gotwebd/Makefile                             |    0+   2-
M  gotwebd/Makefile.inc                         |    1+   1-
M  gotwebd/config.c                             |    9+  15-
M  gotwebd/fcgi.c                               |  102+   5-
M  gotwebd/gotweb.c                             |  384+  14-
M  gotwebd/gotwebd.c                            |  236+  82-
M  gotwebd/gotwebd.h                            |   29+  10-
D  gotwebd/libexec/Makefile                     |    0+   4-
D  gotwebd/libexec/Makefile.inc                 |    0+  11-
D  gotwebd/libexec/got-read-blob/Makefile       |    0+  15-
D  gotwebd/libexec/got-read-commit/Makefile     |    0+  15-
D  gotwebd/libexec/got-read-gitconfig/Makefile  |    0+  16-
D  gotwebd/libexec/got-read-gotconfig/Makefile  |    0+  19-
D  gotwebd/libexec/got-read-object/Makefile     |    0+  15-
D  gotwebd/libexec/got-read-pack/Makefile       |    0+  16-
D  gotwebd/libexec/got-read-tag/Makefile        |    0+  15-
D  gotwebd/libexec/got-read-tree/Makefile       |    0+  15-
M  gotwebd/sockets.c                            |  165+  38-

18 files changed, 926 insertions(+), 308 deletions(-)

commit - cbe39f11829176889cb350bc903e0f339cff366e
commit + 4230a2ade42b54c9e07602ba86c6532f698b15d8
blob - 6b6e9e1815bb7b6d738165292b522056eee96e22
blob + 545a13d2e5aba6dc9df486f7300af0acf23c4df7
--- gotwebd/Makefile
+++ gotwebd/Makefile
@@ -1,8 +1,6 @@
 .PATH:${.CURDIR}/../lib
 .PATH:${.CURDIR}/../template
 
-SUBDIR = libexec
-
 .include "../got-version.mk"
 .include "Makefile.inc"
 
blob - 8140d7d3e8b25972b9b347173294bd3b671a0da1
blob + 22466be67cc8ab44489af620bed8558a24ed17c9
--- gotwebd/Makefile.inc
+++ gotwebd/Makefile.inc
@@ -1,9 +1,9 @@
 LDADD +=	-lz -lutil
 PREFIX ?=	/usr/local
 BINDIR ?=	${PREFIX}/sbin
+LIBEXECDIR ?=	${PREFIX}/libexec
 CHROOT_DIR ?=	/var/www
 GOTWEB_DIR =	/bin/gotwebd
-LIBEXECDIR =	${GOTWEB_DIR}/libexec
 HTTPD_DIR =	${CHROOT_DIR}/htdocs
 PROG_DIR =	${HTTPD_DIR}/${PROG}
 WWWUSR ?=	www
blob - 980ecf25520da4497a36f585459ebecbf7b1d3f5
blob + 90eb143afce8e50c7ba53d141294ca8616d7a074
--- gotwebd/config.c
+++ gotwebd/config.c
@@ -74,15 +74,6 @@ config_getcfg(struct gotwebd *env, struct imsg *imsg)
 }
 
 int
-config_setserver(struct gotwebd *env, struct server *srv)
-{
-	if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_SRV,
-	    -1, srv, sizeof(*srv)) == -1)
-		fatal("main_compose_sockets GOTWEBD_IMSG_CFG_SRV");
-	return 0;
-}
-
-int
 config_getserver(struct gotwebd *env, struct imsg *imsg)
 {
 	struct server *srv;
@@ -106,17 +97,20 @@ config_getserver(struct gotwebd *env, struct imsg *ims
 }
 
 int
-config_setsock(struct gotwebd *env, struct socket *sock)
+config_setsock(struct gotwebd *env, struct socket *sock, uid_t uid, gid_t gid)
 {
 	/* open listening sockets */
-	if (sockets_privinit(env, sock) == -1)
+	if (sockets_privinit(env, sock, uid, gid) == -1)
 		return -1;
 
 	if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_SOCK, sock->fd,
 	    &sock->conf, sizeof(sock->conf)) == -1)
 		fatal("main_compose_sockets GOTWEBD_IMSG_CFG_SOCK");
 
-	sock->fd = -1;
+	if (main_compose_gotweb(env, GOTWEBD_IMSG_CFG_SOCK, sock->fd,
+	    &sock->conf, sizeof(sock->conf)) == -1)
+		fatal("main_compose_gotweb GOTWEBD_IMSG_CFG_SOCK");
+
 	return 0;
 }
 
@@ -172,13 +166,13 @@ config_setfd(struct gotwebd *env)
 			fd = got_opentempfd();
 			if (fd == -1)
 				fatal("got_opentemp");
-			if (imsg_compose_event(&env->iev_server[j],
+			if (imsg_compose_event(&env->iev_gotweb[j],
 			    GOTWEBD_IMSG_CFG_FD, 0, -1, fd, NULL, 0) == -1)
 				fatal("imsg_compose_event GOTWEBD_IMSG_CFG_FD");
 
-			if (imsgbuf_flush(&env->iev_server[j].ibuf) == -1)
+			if (imsgbuf_flush(&env->iev_gotweb[j].ibuf) == -1)
 				fatal("imsgbuf_flush");
-			imsg_event_add(&env->iev_server[j]);
+			imsg_event_add(&env->iev_gotweb[j]);
 		}
 	}
 
blob - fe58b1f12ab25d401bc5d3c10aaecf29d138f7bd
blob + 02d64b76e9201d76470c48fc07e0b6cd3a8ba35a
--- gotwebd/fcgi.c
+++ gotwebd/fcgi.c
@@ -54,6 +54,7 @@ void	 dump_fcgi_end_request_body(const char *,
 
 extern int	 cgi_inflight;
 extern volatile int client_cnt;
+extern struct requestlist requests;
 
 void
 fcgi_request(int fd, short events, void *arg)
@@ -142,6 +143,7 @@ fcgi_parse_record(uint8_t *buf, size_t n, struct reque
 		    ntohs(h->content_len), c, ntohs(h->id));
 		break;
 	case FCGI_STDIN:
+		return 0;
 	case FCGI_ABORT_REQUEST:
 		fcgi_create_end_record(c);
 		fcgi_cleanup_request(c);
@@ -175,6 +177,87 @@ fcgi_parse_begin_request(uint8_t *buf, uint16_t n,
 	c->id = id;
 }
 
+static void
+fcgi_forward_response(int fd, short event, void *arg)
+{
+	struct request *c = arg;
+	uint8_t outbuf[GOTWEBD_CACHESIZE];
+	ssize_t r, w;
+
+	if ((event & EV_READ) == 0)
+		return;
+
+	r = read(fd, outbuf, sizeof(outbuf));
+	if (r == 0)
+		return;
+
+	if (r == -1) {
+		log_warn("read response");
+	} else {
+		log_info("%s: %zd bytes", __func__, r);
+		w = write(c->fd, outbuf, r);
+		if (w != r)
+			log_warn("%s: wrote %zd of %zd bytes",
+			    __func__, w, r);
+	}
+	
+	event_add(c->resp_event, NULL);
+}
+
+static void
+process_request(struct request *c)
+{
+	struct gotwebd *env = gotwebd_env;
+	int ret, i, pipe[2];
+	struct request ic;
+	struct event *resp_event = NULL;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) {
+		log_warn("socketpair");
+		return;
+	}
+
+	memcpy(&ic, c, sizeof(ic));
+
+	/* Don't leak pointers from our address space to another process. */
+	ic.sock = NULL;
+	ic.srv = NULL;
+	ic.t = NULL;
+	ic.tp = NULL;
+	ic.buf = NULL;
+	ic.outbuf = NULL;
+
+	/* Other process will use its own set of temp files. */
+	for (i = 0; i < nitems(c->priv_fd); i++)
+		ic.priv_fd[i] = -1;
+	ic.fd = -1;
+	ic.resp_fd = -1;
+
+	resp_event = calloc(1, sizeof(*resp_event));
+	if (resp_event == NULL) {
+		log_warn("calloc");
+		close(pipe[0]);
+		close(pipe[1]);
+		return;
+	}
+
+	ret = imsg_compose_event(env->iev_gotweb, GOTWEBD_IMSG_REQ_PROCESS,
+	    GOTWEBD_PROC_SERVER, getpid(), pipe[0], &ic, sizeof(ic));
+	if (ret == -1) {
+		log_warn("imsg_compose_event");
+		close(pipe[0]);
+		close(pipe[1]);
+		free(resp_event);
+		return;
+	}
+
+	event_set(resp_event, pipe[1], EV_READ, fcgi_forward_response, c);
+	event_add(resp_event, NULL);
+
+	c->resp_fd = pipe[1];
+	c->resp_event = resp_event;
+}
+
 void
 fcgi_parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
 {
@@ -192,8 +275,7 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req
 	}
 
 	if (n == 0) {
-		gotweb_process_request(c);
-		template_flush(c->tp);
+		process_request(c);
 		return;
 	}
 
@@ -398,17 +480,32 @@ fcgi_create_end_record(struct request *c)
 void
 fcgi_cleanup_request(struct request *c)
 {
+	log_info("%s: %d", __func__, c->request_id);
+
 	cgi_inflight--;
 	client_cnt--;
 
-	evtimer_del(&c->tmo);
+	TAILQ_REMOVE(&requests, c, entry);
+
+	if (evtimer_initialized(&c->tmo))
+		evtimer_del(&c->tmo);
 	if (event_initialized(&c->ev))
 		event_del(&c->ev);
 
-	close(c->fd);
-	template_free(c->tp);
+	if (c->fd != -1)
+		close(c->fd);
+	if (c->resp_fd != -1)
+		close(c->resp_fd);
+	if (c->tp != NULL)
+		template_free(c->tp);
 	if (c->t != NULL)
 		gotweb_free_transport(c->t);
+	if (c->resp_event) {
+		event_del(c->resp_event);
+		free(c->resp_event);
+	}
+	free(c->buf);
+	free(c->outbuf);
 	free(c);
 }
 
blob - 1a1a8770b26108d09399eb43a781fa0e4d63c1b9
blob + 91799176b5f93d034ae4014bd3a97128c31aeee4
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -33,6 +33,7 @@
 #include <imsg.h>
 #include <sha1.h>
 #include <sha2.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -142,11 +143,124 @@ gotweb_reply_file(struct request *c, const char *ctype
 	return gotweb_reply(c, 200, ctype, NULL);
 }
 
+static void
+free_request(struct request *c)
+{
+	if (c->fd != -1)
+		close(c->fd);
+	if (c->tp != NULL)
+		template_free(c->tp);
+	if (c->t != NULL)
+		gotweb_free_transport(c->t);
+	free(c->buf);
+	free(c->outbuf);
+	free(c);
+}
+
+static struct socket *
+gotweb_get_socket(int sock_id)
+{
+	struct socket *sock;
+
+	TAILQ_FOREACH(sock, &gotwebd_env->sockets, entry) {
+		if (sock->conf.id == sock_id)
+			return sock;
+	}
+
+	return NULL;
+}
+
+static struct request *
+recv_request(struct imsg *imsg)
+{
+	const struct got_error *error;
+	struct request *c;
+	struct server *srv;
+	size_t datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+	int fd = -1;
+	uint8_t *outbuf = NULL;
+
+	if (datalen != sizeof(*c)) {
+		log_warnx("bad request size received over imsg");
+		return NULL;
+	}
+
+	fd = imsg_get_fd(imsg);
+	if (fd == -1) {
+		log_warnx("no client file descriptor");
+		return NULL;
+	}
+
+	c = calloc(1, sizeof(*c));
+	if (c == NULL) {
+		log_warn("calloc");
+		return NULL;
+	}
+
+	outbuf = calloc(1, GOTWEBD_CACHESIZE);
+	if (outbuf == NULL) {
+		log_warn("calloc");
+		free(c);
+		return NULL;
+	}
+
+	memcpy(c, imsg->data, sizeof(*c));
+
+	/* Non-NULL pointers, if any, are not from our address space. */
+	c->sock = NULL;
+	c->srv = NULL;
+	c->t = NULL;
+	c->tp = NULL;
+	c->buf = NULL;
+	c->outbuf = outbuf;
+
+	memset(&c->ev, 0, sizeof(c->ev));
+	memset(&c->tmo, 0, sizeof(c->tmo));
+
+	/* Use our own temporary file descriptors. */
+	memcpy(c->priv_fd, gotwebd_env->priv_fd, sizeof(c->priv_fd));
+
+	c->fd = fd;
+
+	c->tp = template(c, fcgi_write, c->outbuf, GOTWEBD_CACHESIZE);
+	if (c->tp == NULL) {
+		log_warn("gotweb init template");
+		free_request(c);
+		return NULL;
+	}
+
+	c->sock = gotweb_get_socket(c->sock_id);
+	if (c->sock == NULL) {
+		log_warn("socket id '%d' not found", c->sock_id);
+		free_request(c);
+		return NULL;
+	}
+
+	/* init the transport */
+	error = gotweb_init_transport(&c->t);
+	if (error) {
+		log_warnx("gotweb init transport: %s", error->msg);
+		free_request(c);
+		return NULL;
+	}
+
+	/* get the gotwebd server */
+	srv = gotweb_get_server(c->server_name);
+	if (srv == NULL) {
+		log_warnx("server '%s' not found", c->server_name);
+		free_request(c);
+		return NULL;
+	}
+	c->srv = srv;
+
+	return c;
+}
+
 void
 gotweb_process_request(struct request *c)
 {
 	const struct got_error *error = NULL;
-	struct server *srv = NULL;
+	struct server *srv = c->srv;;
 	struct querystring *qs = NULL;
 	struct repo_dir *repo_dir = NULL;
 	struct repo_commit *commit;
@@ -155,19 +269,6 @@ gotweb_process_request(struct request *c)
 	size_t len;
 	int r, binary = 0;
 
-	/* init the transport */
-	error = gotweb_init_transport(&c->t);
-	if (error) {
-		log_warnx("%s: %s", __func__, error->msg);
-		return;
-	}
-	/* get the gotwebd server */
-	srv = gotweb_get_server(c->server_name);
-	if (srv == NULL) {
-		log_warnx("%s: error server is NULL", __func__);
-		goto err;
-	}
-	c->srv = srv;
 	/* parse our querystring */
 	error = gotweb_init_querystring(&qs);
 	if (error) {
@@ -1297,3 +1398,272 @@ gotweb_render_age(struct template *tp, time_t committe
 	}
 	return 0;
 }
+
+static void
+gotweb_shutdown(void)
+{
+	imsgbuf_clear(&gotwebd_env->iev_parent->ibuf);
+	free(gotwebd_env->iev_parent);
+	free(gotwebd_env);
+
+	exit(0);
+}
+
+static void
+gotweb_sighdlr(int sig, short event, void *arg)
+{
+	switch (sig) {
+	case SIGHUP:
+		log_info("%s: ignoring SIGHUP", __func__);
+		break;
+	case SIGPIPE:
+		log_info("%s: ignoring SIGPIPE", __func__);
+		break;
+	case SIGUSR1:
+		log_info("%s: ignoring SIGUSR1", __func__);
+		break;
+	case SIGCHLD:
+		break;
+	case SIGINT:
+	case SIGTERM:
+		gotweb_shutdown();
+		break;
+	default:
+		log_warn("unhandled signal %d", sig);
+	}
+}
+
+static void
+gotweb_launch(struct gotwebd *env)
+{
+	struct server *srv;
+	const struct got_error *error;
+
+	if (env->iev_server == NULL)
+		fatal("server process not connected");
+
+#ifndef PROFILE
+	if (pledge("stdio rpath recvfd sendfd proc exec unveil", NULL) == -1)
+		fatal("pledge");
+#endif
+
+	TAILQ_FOREACH(srv, &gotwebd_env->servers, entry) {
+		if (unveil(srv->repos_path, "r") == -1)
+			fatal("unveil %s", srv->repos_path);
+	}
+
+	error = got_privsep_unveil_exec_helpers();
+	if (error)
+		fatalx("%s", error->msg);
+
+	if (unveil(NULL, NULL) == -1)
+		fatal("unveil");
+
+	event_add(&env->iev_server->ev, NULL);
+}
+
+static void
+send_request_done(struct imsgev *iev, int request_id)
+{
+	struct gotwebd		*env = gotwebd_env;
+
+	if (imsg_compose_event(env->iev_server, GOTWEBD_IMSG_REQ_DONE,
+	    GOTWEBD_PROC_GOTWEB, getpid(), -1,
+	    &request_id, sizeof(request_id)) == -1)
+		log_warn("imsg_compose_event");
+}
+
+static void
+gotweb_dispatch_server(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	struct request		*c;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsgbuf_read(ibuf)) == -1)
+			fatal("imsgbuf_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if (imsgbuf_write(ibuf) == -1)
+			fatal("imsgbuf_write");
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case GOTWEBD_IMSG_REQ_PROCESS:
+			c = recv_request(&imsg);
+			if (c) {
+				int request_id = c->request_id;
+				gotweb_process_request(c);
+				template_flush(c->tp);
+				free_request(c);
+				send_request_done(iev, request_id);
+			}
+			break;
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+}
+
+static void
+recv_server_pipe(struct gotwebd *env, struct imsg *imsg)
+{
+	struct imsgev *iev;
+	int fd;
+
+	if (env->iev_server != NULL) {
+		log_warn("server pipe already received"); 
+		return;
+	}
+
+	fd = imsg_get_fd(imsg);
+	if (fd == -1)
+		fatalx("invalid server pipe fd");
+
+	iev = calloc(1, sizeof(*iev));
+	if (iev == NULL)
+		fatal("calloc");
+
+	if (imsgbuf_init(&iev->ibuf, fd) == -1)
+		fatal("imsgbuf_init");
+	imsgbuf_allow_fdpass(&iev->ibuf);
+
+	iev->handler = gotweb_dispatch_server;
+	iev->data = iev;
+	event_set(&iev->ev, fd, EV_READ, gotweb_dispatch_server, iev);
+
+	env->iev_server = iev;
+}
+
+static void
+gotweb_dispatch_main(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	struct gotwebd		*env = gotwebd_env;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsgbuf_read(ibuf)) == -1)
+			fatal("imsgbuf_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if (imsgbuf_write(ibuf) == -1)
+			fatal("imsgbuf_write");
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case GOTWEBD_IMSG_CFG_SRV:
+			config_getserver(env, &imsg);
+			break;
+		case GOTWEBD_IMSG_CFG_FD:
+			config_getfd(env, &imsg);
+			break;
+		case GOTWEBD_IMSG_CFG_SOCK:
+			config_getsock(env, &imsg);
+			break;
+		case GOTWEBD_IMSG_CFG_DONE:
+			config_getcfg(env, &imsg);
+			break;
+		case GOTWEBD_IMSG_CTL_PIPE:
+			recv_server_pipe(env, &imsg);
+			break;
+		case GOTWEBD_IMSG_CTL_START:
+			gotweb_launch(env);
+			break;
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+}
+
+void
+gotweb(struct gotwebd *env, int fd)
+{
+	struct event	 sighup, sigint, sigusr1, sigchld, sigterm;
+	struct event_base *evb;
+
+	evb = event_init();
+
+	sockets_rlimit(-1);
+
+	if ((env->iev_parent = malloc(sizeof(*env->iev_parent))) == NULL)
+		fatal("malloc");
+	if (imsgbuf_init(&env->iev_parent->ibuf, fd) == -1)
+		fatal("imsgbuf_init");
+	imsgbuf_allow_fdpass(&env->iev_parent->ibuf);
+	env->iev_parent->handler = gotweb_dispatch_main;
+	env->iev_parent->data = env->iev_parent;
+	event_set(&env->iev_parent->ev, fd, EV_READ, gotweb_dispatch_main,
+	    env->iev_parent);
+	event_add(&env->iev_parent->ev, NULL);
+
+	signal(SIGPIPE, SIG_IGN);
+
+	signal_set(&sighup, SIGHUP, gotweb_sighdlr, env);
+	signal_add(&sighup, NULL);
+	signal_set(&sigint, SIGINT, gotweb_sighdlr, env);
+	signal_add(&sigint, NULL);
+	signal_set(&sigusr1, SIGUSR1, gotweb_sighdlr, env);
+	signal_add(&sigusr1, NULL);
+	signal_set(&sigchld, SIGCHLD, gotweb_sighdlr, env);
+	signal_add(&sigchld, NULL);
+	signal_set(&sigterm, SIGTERM, gotweb_sighdlr, env);
+	signal_add(&sigterm, NULL);
+
+#ifndef PROFILE
+	if (pledge("stdio rpath recvfd sendfd proc exec unveil", NULL) == -1)
+		fatal("pledge");
+#endif
+	event_dispatch();
+	event_base_free(evb);
+	gotweb_shutdown();
+}
blob - 61a08a5b8aa69b071a02be23f9794bab66c345fc
blob + 00aabaed5092fea490a2c5a9b0dd40dc01ffab52
--- gotwebd/gotwebd.c
+++ gotwebd/gotwebd.c
@@ -48,11 +48,12 @@
 __dead void usage(void);
 
 int	 main(int, char **);
-int	 gotwebd_configure(struct gotwebd *);
+int	 gotwebd_configure(struct gotwebd *, uid_t, gid_t);
 void	 gotwebd_configure_done(struct gotwebd *);
 void	 gotwebd_sighdlr(int sig, short event, void *arg);
 void	 gotwebd_shutdown(void);
-void	 gotwebd_dispatch_sockets(int, short, void *);
+void	 gotwebd_dispatch_server(int, short, void *);
+void	 gotwebd_dispatch_gotweb(int, short, void *);
 
 struct gotwebd	*gotwebd_env;
 
@@ -75,7 +76,7 @@ imsg_event_add(struct imsgev *iev)
 
 int
 imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
-    pid_t pid, int fd, const void *data, uint16_t datalen)
+    pid_t pid, int fd, const void *data, size_t datalen)
 {
 	int ret;
 
@@ -86,42 +87,67 @@ imsg_compose_event(struct imsgev *iev, uint16_t type, 
 	return (ret);
 }
 
-int
-main_compose_sockets(struct gotwebd *env, uint32_t type, int fd,
-    const void *data, uint16_t len)
+static int
+send_imsg(struct imsgev *iev, uint32_t type, int fd, const void *data,
+    uint16_t len)
 {
-	size_t	 i;
-	int	 ret, d;
+	int	 ret, d = -1;
 
-	for (i = 0; i < env->nserver; ++i) {
+	if (fd != -1 && (d = dup(fd)) == -1)
+		goto err;
+
+	ret = imsg_compose_event(iev, type, 0, -1, d, data, len);
+	if (ret == -1)
+		goto err;
+
+	if (d != -1) {
 		d = -1;
-		if (fd != -1 && (d = dup(fd)) == -1)
+		/* Flush imsg to prevent fd exhaustion. 'd' will be closed. */
+		if (imsgbuf_flush(&iev->ibuf) == -1)
 			goto err;
-
-		ret = imsg_compose_event(&env->iev_server[i], type, 0, -1,
-		    d, data, len);
-		if (ret == -1)
-			goto err;
-
-		/* prevent fd exhaustion */
-		if (d != -1) {
-			if (imsgbuf_flush(&env->iev_server[i].ibuf) == -1)
-				goto err;
-			imsg_event_add(&env->iev_server[i]);
-		}
+		imsg_event_add(iev);
 	}
 
-	if (fd != -1)
-		close(fd);
 	return 0;
-
 err:
-	if (fd != -1)
-		close(fd);
+	if (d != -1)
+		close(d);
 	return -1;
 }
 
 int
+main_compose_sockets(struct gotwebd *env, uint32_t type, int fd,
+    const void *data, uint16_t len)
+{
+	size_t i;
+	int ret = 0;
+
+	for (i = 0; i < env->nserver; ++i) {
+		ret = send_imsg(&env->iev_server[i], type, fd, data, len);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+int
+main_compose_gotweb(struct gotwebd *env, uint32_t type, int fd,
+    const void *data, uint16_t len)
+{
+	size_t i;
+	int ret = 0;
+
+	for (i = 0; i < env->nserver; ++i) {
+		ret = send_imsg(&env->iev_gotweb[i], type, fd, data, len);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+int
 sockets_compose_main(struct gotwebd *env, uint32_t type, const void *d,
     uint16_t len)
 {
@@ -129,7 +155,7 @@ sockets_compose_main(struct gotwebd *env, uint32_t typ
 }
 
 void
-gotwebd_dispatch_sockets(int fd, short event, void *arg)
+gotwebd_dispatch_server(int fd, short event, void *arg)
 {
 	struct imsgev		*iev = arg;
 	struct imsgbuf		*ibuf;
@@ -179,6 +205,56 @@ gotwebd_dispatch_sockets(int fd, short event, void *ar
 }
 
 void
+gotwebd_dispatch_gotweb(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	struct gotwebd		*env = gotwebd_env;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsgbuf_read(ibuf)) == -1)
+			fatal("imsgbuf_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if (imsgbuf_write(ibuf) == -1)
+			fatal("imsgbuf_write");
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case GOTWEBD_IMSG_CFG_DONE:
+			gotwebd_configure_done(env);
+			break;
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+}
+
+void
 gotwebd_sighdlr(int sig, short event, void *arg)
 {
 	/* struct privsep	*ps = arg; */
@@ -203,10 +279,11 @@ gotwebd_sighdlr(int sig, short event, void *arg)
 	}
 }
 
-static int
-spawn_socket_process(struct gotwebd *env, const char *argv0, int n)
+static void
+spawn_process(struct gotwebd *env, const char *argv0, struct imsgev *iev,
+    enum gotwebd_proc_type proc_type, void (*handler)(int, short, void *))
 {
-	const char	*argv[8];
+	const char	*argv[9];
 	int		 argc = 0;
 	int		 p[2];
 	pid_t		 pid;
@@ -221,21 +298,26 @@ spawn_socket_process(struct gotwebd *env, const char *
 		break;
 	default:	/* parent */
 		close(p[0]);
-		if (imsgbuf_init(&env->iev_server[n].ibuf, p[1]) == -1)
+		if (imsgbuf_init(&iev->ibuf, p[1]) == -1)
 			fatal("imsgbuf_init");
-		imsgbuf_allow_fdpass(&env->iev_server[n].ibuf);
-		env->iev_server[n].handler = gotwebd_dispatch_sockets;
-		env->iev_server[n].data = &env->iev_server[n];
-		event_set(&env->iev_server[n].ev, p[1], EV_READ,
-		    gotwebd_dispatch_sockets, &env->iev_server[n]);
-		event_add(&env->iev_server[n].ev, NULL);
-		return 0;
+		imsgbuf_allow_fdpass(&iev->ibuf);
+		iev->handler = handler;
+		iev->data = iev;
+		event_set(&iev->ev, p[1], EV_READ, handler, iev);
+		event_add(&iev->ev, NULL);
+		return;
 	}
 
 	close(p[1]);
 
 	argv[argc++] = argv0;
-	argv[argc++] = "-S";
+	if (proc_type == GOTWEBD_PROC_SERVER) {
+		argv[argc++] = "-S";
+		argv[argc++] = env->user;
+	} else if (proc_type == GOTWEBD_PROC_GOTWEB) {
+		argv[argc++] = "-G";
+		argv[argc++] = env->user;
+	}
 	if (strcmp(env->gotwebd_conffile, GOTWEBD_CONF) != 0) {
 		argv[argc++] = "-f";
 		argv[argc++] = env->gotwebd_conffile;
@@ -273,12 +355,12 @@ main(int argc, char **argv)
 	struct event		 sigint, sigterm, sighup, sigpipe, sigusr1;
 	struct event_base	*evb;
 	struct gotwebd		*env;
-	struct passwd		*pw;
+	struct passwd		*pw_gotwebd;
 	int			 ch, i;
 	int			 no_action = 0;
-	int			 server_proc = 0;
+	int			 proc_type = GOTWEBD_PROC_PARENT;
 	const char		*conffile = GOTWEBD_CONF;
-	const char		*username = GOTWEBD_DEFAULT_USER;
+	const char		*gotwebd_username = GOTWEBD_DEFAULT_USER;
 	const char		*argv0;
 
 	if ((argv0 = argv[0]) == NULL)
@@ -292,7 +374,7 @@ main(int argc, char **argv)
 		fatal("%s: calloc", __func__);
 	config_init(env);
 
-	while ((ch = getopt(argc, argv, "D:df:nSv")) != -1) {
+	while ((ch = getopt(argc, argv, "D:dG:f:nS:vW:")) != -1) {
 		switch (ch) {
 		case 'D':
 			if (cmdline_symset(optarg) < 0)
@@ -302,6 +384,10 @@ main(int argc, char **argv)
 		case 'd':
 			env->gotwebd_debug = 1;
 			break;
+		case 'G':
+			proc_type = GOTWEBD_PROC_GOTWEB;
+			gotwebd_username = optarg;
+			break;
 		case 'f':
 			conffile = optarg;
 			break;
@@ -309,7 +395,8 @@ main(int argc, char **argv)
 			no_action = 1;
 			break;
 		case 'S':
-			server_proc = 1;
+			proc_type = GOTWEBD_PROC_SERVER;
+			gotwebd_username = optarg;
 			break;
 		case 'v':
 			if (env->gotwebd_verbose < 3)
@@ -327,43 +414,64 @@ main(int argc, char **argv)
 	gotwebd_env = env;
 	env->gotwebd_conffile = conffile;
 
-	if (parse_config(env->gotwebd_conffile, env) == -1)
-		exit(1);
+	if (proc_type == GOTWEBD_PROC_PARENT) {
+		if (parse_config(env->gotwebd_conffile, env) == -1)
+			exit(1);
 
-	if (no_action) {
-		fprintf(stderr, "configuration OK\n");
-		exit(0);
+		if (no_action) {
+			fprintf(stderr, "configuration OK\n");
+			exit(0);
+		}
+
+		if (env->user)
+			gotwebd_username = env->user;
 	}
 
+	pw_gotwebd = getpwnam(gotwebd_username);
+	if (pw_gotwebd == NULL)
+		fatalx("unknown user %s", gotwebd_username);
+
 	/* check for root privileges */
 	if (geteuid())
 		fatalx("need root privileges");
 
-	if (env->user)
-		username = env->user;
-	pw = getpwnam(username);
-	if (pw == NULL)
-		fatalx("unknown user %s", username);
-	env->pw = pw;
-
 	log_init(env->gotwebd_debug, LOG_DAEMON);
 	log_setverbose(env->gotwebd_verbose);
 
-	if (server_proc) {
-		setproctitle("server");
-		log_procinit("server");
+	switch (proc_type) {
+	case GOTWEBD_PROC_SERVER:
+		setproctitle("fcgiserver");
+		log_procinit("fcgiserver");
 
 		if (chroot(env->httpd_chroot) == -1)
 			fatal("chroot %s", env->httpd_chroot);
 		if (chdir("/") == -1)
 			fatal("chdir /");
-		if (setgroups(1, &pw->pw_gid) == -1 ||
-		    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
-		    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
+
+		if (setgroups(1, &pw_gotwebd->pw_gid) == -1 ||
+		    setresgid(pw_gotwebd->pw_gid, pw_gotwebd->pw_gid,
+		    pw_gotwebd->pw_gid) == -1 ||
+		    setresuid(pw_gotwebd->pw_uid, pw_gotwebd->pw_uid,
+		    pw_gotwebd->pw_uid) == -1)
 			fatal("failed to drop privileges");
 
 		sockets(env, GOTWEBD_SOCK_FILENO);
 		return 1;
+	case GOTWEBD_PROC_GOTWEB:
+		setproctitle("gotweb");
+		log_procinit("gotweb");
+
+		if (setgroups(1, &pw_gotwebd->pw_gid) == -1 ||
+		    setresgid(pw_gotwebd->pw_gid, pw_gotwebd->pw_gid,
+		    pw_gotwebd->pw_gid) == -1 ||
+		    setresuid(pw_gotwebd->pw_uid, pw_gotwebd->pw_uid,
+		    pw_gotwebd->pw_uid) == -1)
+			fatal("failed to drop privileges");
+
+		gotweb(env, GOTWEBD_SOCK_FILENO);
+		return 1;
+	default:
+		break;
 	}
 
 	if (!env->gotwebd_debug && daemon(1, 0) == -1)
@@ -375,12 +483,16 @@ main(int argc, char **argv)
 	env->iev_server = calloc(env->nserver, sizeof(*env->iev_server));
 	if (env->iev_server == NULL)
 		fatal("calloc");
+	env->iev_gotweb = calloc(env->nserver, sizeof(*env->iev_gotweb));
+	if (env->iev_gotweb == NULL)
+		fatal("calloc");
 
 	for (i = 0; i < env->nserver; ++i) {
-		if (spawn_socket_process(env, argv0, i) == -1)
-			fatal("spawn_socket_process");
+		spawn_process(env, argv0, &env->iev_server[i],
+		    GOTWEBD_PROC_SERVER, gotwebd_dispatch_server);
+		spawn_process(env, argv0, &env->iev_gotweb[i],
+		    GOTWEBD_PROC_GOTWEB, gotwebd_dispatch_gotweb);
 	}
-
 	if (chdir("/") == -1)
 		fatal("chdir /");
 
@@ -400,12 +512,15 @@ main(int argc, char **argv)
 	signal_add(&sigpipe, NULL);
 	signal_add(&sigusr1, NULL);
 
-	if (gotwebd_configure(env) == -1)
+	if (gotwebd_configure(env,
+	    pw_gotwebd->pw_uid, pw_gotwebd->pw_gid) == -1)
 		fatalx("configuration failed");
 
-	if (setgroups(1, &pw->pw_gid) == -1 ||
-	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
-	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
+	if (setgroups(1, &pw_gotwebd->pw_gid) == -1 ||
+	    setresgid(pw_gotwebd->pw_gid, pw_gotwebd->pw_gid,
+	    pw_gotwebd->pw_gid) == -1 ||
+	    setresuid(pw_gotwebd->pw_uid, pw_gotwebd->pw_uid,
+	    pw_gotwebd->pw_uid) == -1)
 		fatal("failed to drop privileges");
 
 #ifdef PROFILE
@@ -432,24 +547,54 @@ main(int argc, char **argv)
 	return (0);
 }
 
+static void
+connect_children(struct gotwebd *env)
+{
+	struct imsgev *iev1, *iev2;
+	int pipe[2];
+	int i;
+
+	for (i = 0; i < env->nserver; i++) {
+		iev1 = &env->iev_server[i];
+		iev2 = &env->iev_gotweb[i];
+	
+		if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
+			fatal("socketpair");
+
+		if (send_imsg(iev1, GOTWEBD_IMSG_CTL_PIPE, pipe[0], NULL, 0))
+			fatal("send_imsg");
+
+		if (send_imsg(iev2, GOTWEBD_IMSG_CTL_PIPE, pipe[1], NULL, 0))
+			fatal("send_imsg");
+
+		close(pipe[0]);
+		close(pipe[1]);
+	}
+}
+
 int
-gotwebd_configure(struct gotwebd *env)
+gotwebd_configure(struct gotwebd *env, uid_t uid, gid_t gid)
 {
 	struct server *srv;
 	struct socket *sock;
 
 	/* gotweb need to reload its config. */
-	env->gotwebd_reload = env->prefork_gotwebd;
+	env->servers_pending = env->prefork_gotwebd;
+	env->gotweb_pending = env->prefork_gotwebd;
 
 	/* send our gotweb servers */
 	TAILQ_FOREACH(srv, &env->servers, entry) {
-		if (config_setserver(env, srv) == -1)
-			fatalx("%s: send server error", __func__);
+		if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_SRV,
+		    -1, srv, sizeof(*srv)) == -1)
+			fatal("main_compose_sockets GOTWEBD_IMSG_CFG_SRV");
+		if (main_compose_gotweb(env, GOTWEBD_IMSG_CFG_SRV,
+		    -1, srv, sizeof(*srv)) == -1)
+			fatal("main_compose_gotweb GOTWEBD_IMSG_CFG_SRV");
 	}
 
 	/* send our sockets */
 	TAILQ_FOREACH(sock, &env->sockets, entry) {
-		if (config_setsock(env, sock) == -1)
+		if (config_setsock(env, sock, uid, gid) == -1)
 			fatalx("%s: send socket error", __func__);
 	}
 
@@ -457,6 +602,9 @@ gotwebd_configure(struct gotwebd *env)
 	if (config_setfd(env) == -1)
 		fatalx("%s: send priv_fd error", __func__);
 
+	/* Connect servers and gotwebs. */
+	connect_children(env);
+
 	if (main_compose_sockets(env, GOTWEBD_IMSG_CFG_DONE, -1, NULL, 0) == -1)
 		fatal("main_compose_sockets GOTWEBD_IMSG_CFG_DONE");
 
@@ -466,15 +614,21 @@ gotwebd_configure(struct gotwebd *env)
 void
 gotwebd_configure_done(struct gotwebd *env)
 {
-	if (env->gotwebd_reload == 0) {
-		log_warnx("%s: configuration already finished", __func__);
-		return;
+	if (env->servers_pending > 0) {
+		env->servers_pending--;
+		if (env->servers_pending == 0 &&
+		    main_compose_sockets(env, GOTWEBD_IMSG_CTL_START,
+		        -1, NULL, 0) == -1)
+			fatal("main_compose_sockets GOTWEBD_IMSG_CTL_START");
 	}
 
-	env->gotwebd_reload--;
-	if (env->gotwebd_reload == 0 &&
-	    main_compose_sockets(env, GOTWEBD_CTL_START, -1, NULL, 0) == -1)
-		fatal("main_compose_sockets GOTWEBD_CTL_START");
+	if (env->gotweb_pending > 0) {
+		env->gotweb_pending--;
+		if (env->gotweb_pending == 0 &&
+		    main_compose_gotweb(env, GOTWEBD_IMSG_CTL_START,
+		        -1, NULL, 0) == -1)
+			fatal("main_compose_sockets GOTWEBD_IMSG_CTL_START");
+	}
 }
 
 void
blob - 7dd491f1a07f53e526bf9e5df13d9e0767649384
blob + 297a5497cc363872f0e505bdc4e40713efd212f8
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -32,7 +32,7 @@
 #define GOTWEBD_CONF		 "/etc/gotwebd.conf"
 
 #ifndef GOTWEBD_DEFAULT_USER
-#define GOTWEBD_DEFAULT_USER	 "www"
+#define GOTWEBD_DEFAULT_USER	 "_gotwebd"
 #endif
 
 #define GOTWEBD_MAXDESCRSZ	 1024
@@ -117,12 +117,21 @@ struct got_blob_object;
 struct got_tree_entry;
 struct got_reflist_head;
 
+enum gotwebd_proc_type {
+	GOTWEBD_PROC_PARENT,
+	GOTWEBD_PROC_SERVER,
+	GOTWEBD_PROC_GOTWEB,
+};
+
 enum imsg_type {
 	GOTWEBD_IMSG_CFG_SRV,
 	GOTWEBD_IMSG_CFG_SOCK,
 	GOTWEBD_IMSG_CFG_FD,
 	GOTWEBD_IMSG_CFG_DONE,
-	GOTWEBD_CTL_START,
+	GOTWEBD_IMSG_CTL_PIPE,
+	GOTWEBD_IMSG_CTL_START,
+	GOTWEBD_IMSG_REQ_PROCESS,
+	GOTWEBD_IMSG_REQ_DONE,
 };
 
 struct imsgev {
@@ -229,6 +238,7 @@ enum socket_priv_fds {
 
 struct template;
 struct request {
+	TAILQ_ENTRY(request)		entry;
 	struct socket			*sock;
 	struct server			*srv;
 	struct transport		*t;
@@ -239,12 +249,16 @@ struct request {
 	uint16_t			 id;
 	int				 fd;
 	int				 priv_fd[PRIV_FDS__MAX];
+	int				 resp_fd;
+	struct event			*resp_event;
+	int				 sock_id;
+	uint32_t			 request_id;
 
-	uint8_t				 buf[FCGI_RECORD_SIZE];
+	uint8_t				 *buf;
 	size_t				 buf_pos;
 	size_t				 buf_len;
 
-	uint8_t				 outbuf[GOTWEBD_CACHESIZE];
+	uint8_t				 *outbuf;
 
 	char				 querystring[MAX_QUERYSTRING];
 	char				 document_uri[MAX_DOCUMENT_URI];
@@ -253,6 +267,7 @@ struct request {
 
 	uint8_t				 request_started;
 };
+TAILQ_HEAD(requestlist, request);
 
 struct fcgi_begin_request_body {
 	uint16_t	role;
@@ -349,12 +364,12 @@ struct gotwebd {
 
 	struct imsgev	*iev_parent;
 	struct imsgev	*iev_server;
+	struct imsgev	*iev_gotweb;
 	size_t		 nserver;
 
-	struct passwd	*pw;
-
 	uint16_t	 prefork_gotwebd;
-	int		 gotwebd_reload;
+	int		 servers_pending;
+	int		 gotweb_pending;
 
 	int		 server_cnt;
 
@@ -430,18 +445,21 @@ typedef int (*got_render_blame_line_cb)(struct templat
 /* gotwebd.c */
 void	 imsg_event_add(struct imsgev *);
 int	 imsg_compose_event(struct imsgev *, uint16_t, uint32_t,
-	    pid_t, int, const void *, uint16_t);
+	    pid_t, int, const void *, size_t);
 int	 main_compose_sockets(struct gotwebd *, uint32_t, int,
 	    const void *, uint16_t);
 int	 sockets_compose_main(struct gotwebd *, uint32_t,
 	    const void *, uint16_t);
+int	 main_compose_gotweb(struct gotwebd *, uint32_t, int,
+	    const void *, uint16_t);
 
 /* sockets.c */
 void sockets(struct gotwebd *, int);
 void sockets_parse_sockets(struct gotwebd *);
 void sockets_socket_accept(int, short, void *);
-int sockets_privinit(struct gotwebd *, struct socket *);
+int sockets_privinit(struct gotwebd *, struct socket *, uid_t, gid_t);
 void sockets_purge(struct gotwebd *);
+void sockets_rlimit(int);
 
 /* gotweb.c */
 void gotweb_index_navs(struct request *, struct gotweb_url *, int *,
@@ -455,6 +473,7 @@ void gotweb_free_repo_commit(struct repo_commit *);
 void gotweb_free_repo_tag(struct repo_tag *);
 void gotweb_process_request(struct request *);
 void gotweb_free_transport(struct transport *);
+void gotweb(struct gotwebd *, int);
 
 /* pages.tmpl */
 int	gotweb_render_page(struct template *, int (*)(struct template *));
@@ -507,7 +526,7 @@ const struct got_error *got_output_file_blame(struct r
 /* config.c */
 int config_setserver(struct gotwebd *, struct server *);
 int config_getserver(struct gotwebd *, struct imsg *);
-int config_setsock(struct gotwebd *, struct socket *);
+int config_setsock(struct gotwebd *, struct socket *, uid_t, gid_t);
 int config_getsock(struct gotwebd *, struct imsg *);
 int config_setfd(struct gotwebd *);
 int config_getfd(struct gotwebd *, struct imsg *);
blob - 5fd34708bd3654bc05060446ff5d55557747cfd3 (mode 644)
blob + /dev/null
--- gotwebd/libexec/Makefile
+++ /dev/null
@@ -1,4 +0,0 @@
-SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \
-	got-read-tag got-read-pack got-read-gitconfig got-read-gotconfig
-
-.include <bsd.subdir.mk>
blob - 85bee26728643214f5d4570f003572ac1fc36d05 (mode 644)
blob + /dev/null
--- gotwebd/libexec/Makefile.inc
+++ /dev/null
@@ -1,11 +0,0 @@
-.include "../Makefile.inc"
-
-realinstall:
-	if [ ! -d ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}/. ]; then \
-		${INSTALL} -d -o root -g daemon -m 755 \
-		    ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}; \
-	fi
-	${INSTALL} ${INSTALL_COPY} -o root -g daemon -m 755 ${PROG} \
-	    ${DESTDIR}${CHROOT_DIR}${LIBEXECDIR}/${PROG}
-
-NOMAN = Yes
blob - 9daacd33712bbd6a9ded56d7a03ed143ce6642d3 (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-blob/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-blob
-SRCS=		got-read-blob.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-blob
-
-.include <bsd.prog.mk>
blob - f45137c3ce56dbaadeaf0a5f0ffffd3b4204668c (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-commit/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-commit
-SRCS=		got-read-commit.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-commit
-
-.include <bsd.prog.mk>
blob - 44760613f030f1f1f62c831825d7bb85b07c9a46 (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-gitconfig/Makefile
+++ /dev/null
@@ -1,16 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-gitconfig
-SRCS=		got-read-gitconfig.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c gitconfig.c pollfd.c \
-		object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-gitconfig
-
-.include <bsd.prog.mk>
blob - 26dec69ed1be0648baa16cabea96c54dc3731c54 (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-gotconfig/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-gotconfig
-SRCS=		got-read-gotconfig.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c parse.y pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib \
-	-I${.CURDIR}/../../../libexec/got-read-gotconfig
-YFLAGS =
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-CLEANFILES = parse.h
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-gotconfig
-
-.include <bsd.prog.mk>
blob - 85841212056443f99c873f1089e3fef315ab7d2a (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-object/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-object
-SRCS=		got-read-object.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-object
-
-.include <bsd.prog.mk>
blob - 6ae7cd0c813fcc65f4256be6d06040df2d2880ba (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-pack/Makefile
+++ /dev/null
@@ -1,16 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-pack
-SRCS=		got-read-pack.c delta.c error.c inflate.c object_cache.c \
-		object_idset.c object_parse.c opentemp.c pack.c path.c \
-		privsep.c hash.c delta_cache.c pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-pack
-
-.include <bsd.prog.mk>
blob - eb7b682a069470aa6b445adfc142e1d7bdf7c4a4 (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-tag/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-tag
-SRCS=		got-read-tag.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-tag
-
-.include <bsd.prog.mk>
blob - 497d2ff1cd06d53bcc607653bb658219cd8ea982 (mode 644)
blob + /dev/null
--- gotwebd/libexec/got-read-tree/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-.include "../../../got-version.mk"
-.include "../Makefile.inc"
-
-PROG=		got-read-tree
-SRCS=		got-read-tree.c error.c inflate.c object_parse.c \
-		path.c privsep.c hash.c pollfd.c object_qid.c
-
-CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib
-LDADD = -lutil -lz
-DPADD = ${LIBZ} ${LIBUTIL}
-LDSTATIC = ${STATIC}
-
-.PATH:	${.CURDIR}/../../../lib ${.CURDIR}/../../../libexec/got-read-tree
-
-.include <bsd.prog.mk>
blob - 4ca6de208a73860f721177a094834ffc846d1aaf
blob + 313a8625005f26b7351b119e50c6bad61b49a5b0
--- gotwebd/sockets.c
+++ gotwebd/sockets.c
@@ -49,11 +49,7 @@
 #include <unistd.h>
 #include <util.h>
 
-#include "got_error.h"
-#include "got_opentemp.h"
 #include "got_reference.h"
-#include "got_repository.h"
-#include "got_privsep.h"
 
 #include "gotwebd.h"
 #include "log.h"
@@ -63,17 +59,17 @@
 #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
 
 volatile int client_cnt;
+volatile uint32_t request_id;
 
 static struct timeval	timeout = { TIMEOUT_DEFAULT, 0 };
 
 static void	 sockets_sighdlr(int, short, void *);
 static void	 sockets_shutdown(void);
-static void	 sockets_launch(void);
+static void	 sockets_launch(struct gotwebd *);
 static void	 sockets_accept_paused(int, short, void *);
-static void	 sockets_rlimit(int);
 
 static void	 sockets_dispatch_main(int, short, void *);
-static int	 sockets_unix_socket_listen(struct gotwebd *, struct socket *);
+static int	 sockets_unix_socket_listen(struct gotwebd *, struct socket *, uid_t, gid_t);
 static int	 sockets_create_socket(struct address *);
 static int	 sockets_accept_reserve(int, struct sockaddr *, socklen_t *,
 		    int, volatile int *);
@@ -83,6 +79,8 @@ static struct socket *sockets_conf_new_socket(struct g
 
 int cgi_inflight = 0;
 
+struct requestlist requests = TAILQ_HEAD_INITIALIZER(requests);
+
 void
 sockets(struct gotwebd *env, int fd)
 {
@@ -118,8 +116,7 @@ sockets(struct gotwebd *env, int fd)
 	signal_add(&sigterm, NULL);
 
 #ifndef PROFILE
-	if (pledge("stdio rpath inet recvfd proc exec sendfd unveil",
-	    NULL) == -1)
+	if (pledge("stdio inet recvfd sendfd", NULL) == -1)
 		fatal("pledge");
 #endif
 
@@ -190,12 +187,13 @@ sockets_conf_new_socket(struct gotwebd *env, int id, s
 }
 
 static void
-sockets_launch(void)
+sockets_launch(struct gotwebd *env)
 {
 	struct socket *sock;
-	struct server *srv;
-	const struct got_error *error;
 
+	if (env->iev_gotweb == NULL)
+		fatal("gotweb process not connected");
+
 	TAILQ_FOREACH(sock, &gotwebd_env->sockets, entry) {
 		log_info("%s: configuring socket %d (%d)", __func__,
 		    sock->conf.id, sock->fd);
@@ -212,17 +210,12 @@ sockets_launch(void)
 		    sock->conf.id);
 	}
 
-	TAILQ_FOREACH(srv, &gotwebd_env->servers, entry) {
-		if (unveil(srv->repos_path, "r") == -1)
-			fatal("unveil %s", srv->repos_path);
-	}
+#ifndef PROFILE
+	if (pledge("stdio inet sendfd", NULL) == -1)
+		fatal("pledge");
+#endif
+	event_add(&env->iev_gotweb->ev, NULL);
 
-	error = got_privsep_unveil_exec_helpers();
-	if (error)
-		fatal("%s", error->msg);
-
-	if (unveil(NULL, NULL) == -1)
-		fatal("unveil");
 }
 
 void
@@ -245,7 +238,126 @@ sockets_purge(struct gotwebd *env)
 	}
 }
 
+static struct request *
+find_request(uint32_t request_id)
+{
+	struct request *c;
+
+	TAILQ_FOREACH(c, &requests, entry) {
+		if (c->request_id == request_id)
+			return c;
+	}
+
+	return NULL;
+}
+
 static void
+request_done(struct imsg *imsg)
+{
+	struct request *c;
+	uint32_t request_id;
+	size_t datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+
+	if (datalen != sizeof(request_id)) {
+		log_warn("IMSG_REQ_DONE with bad data length");
+		return;
+	}
+
+	memcpy(&request_id, imsg->data, sizeof(request_id));
+
+	log_info("%s: request_id: %d", __func__, request_id);
+
+	c = find_request(request_id);
+	if (c == NULL) {
+		log_warnx("no request to clean up found for ID '%d'",
+		    request_id);
+		return;
+	}
+
+	fcgi_create_end_record(c);
+	fcgi_cleanup_request(c);
+}
+
+static void
+server_dispatch_gotweb(int fd, short event, void *arg)
+{
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	ssize_t			 n;
+	int			 shut = 0;
+
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsgbuf_read(ibuf)) == -1)
+			fatal("imsgbuf_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if (imsgbuf_write(ibuf) == -1)
+			fatal("imsgbuf_write");
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case GOTWEBD_IMSG_REQ_DONE:
+			request_done(&imsg);
+			break;
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
+	}
+
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
+}
+
+static void
+recv_gotweb_pipe(struct gotwebd *env, struct imsg *imsg)
+{
+	struct imsgev *iev;
+	int fd;
+
+	if (env->iev_gotweb != NULL) {
+		log_warn("gotweb pipe already received"); 
+		return;
+	}
+
+	fd = imsg_get_fd(imsg);
+	if (fd == -1)
+		fatalx("invalid gotweb pipe fd");
+
+	iev = calloc(1, sizeof(*iev));
+	if (iev == NULL)
+		fatal("calloc");
+
+	if (imsgbuf_init(&iev->ibuf, fd) == -1)
+		fatal("imsgbuf_init");
+	imsgbuf_allow_fdpass(&iev->ibuf);
+
+	iev->handler = server_dispatch_gotweb;
+	iev->data = iev;
+	event_set(&iev->ev, fd, EV_READ, server_dispatch_gotweb, iev);
+
+	env->iev_gotweb = iev;
+}
+
+static void
 sockets_dispatch_main(int fd, short event, void *arg)
 {
 	struct imsgev		*iev = arg;
@@ -281,15 +393,15 @@ sockets_dispatch_main(int fd, short event, void *arg)
 		case GOTWEBD_IMSG_CFG_SOCK:
 			config_getsock(env, &imsg);
 			break;
-		case GOTWEBD_IMSG_CFG_FD:
-			config_getfd(env, &imsg);
-			break;
 		case GOTWEBD_IMSG_CFG_DONE:
 			config_getcfg(env, &imsg);
 			break;
-		case GOTWEBD_CTL_START:
-			sockets_launch();
+		case GOTWEBD_IMSG_CTL_PIPE:
+			recv_gotweb_pipe(env, &imsg);
 			break;
+		case GOTWEBD_IMSG_CTL_START:
+			sockets_launch(env);
+			break;
 		default:
 			fatalx("%s: unknown imsg type %d", __func__,
 			    imsg.hdr.type);
@@ -354,6 +466,13 @@ sockets_shutdown(void)
 		free(h);
 	}
 
+	while (!TAILQ_EMPTY(&requests)) {
+		struct request *c;
+
+		c = TAILQ_FIRST(&requests);
+		fcgi_cleanup_request(c);
+	}
+
 	imsgbuf_clear(&gotwebd_env->iev_parent->ibuf);
 	free(gotwebd_env->iev_parent);
 	free(gotwebd_env);
@@ -362,12 +481,12 @@ sockets_shutdown(void)
 }
 
 int
-sockets_privinit(struct gotwebd *env, struct socket *sock)
+sockets_privinit(struct gotwebd *env, struct socket *sock, uid_t uid, gid_t gid)
 {
 	if (sock->conf.af_type == AF_UNIX) {
 		log_info("%s: initializing unix socket %s", __func__,
 		    sock->conf.unix_socket_name);
-		sock->fd = sockets_unix_socket_listen(env, sock);
+		sock->fd = sockets_unix_socket_listen(env, sock, uid, gid);
 		if (sock->fd == -1)
 			return -1;
 	}
@@ -385,7 +504,8 @@ sockets_privinit(struct gotwebd *env, struct socket *s
 }
 
 static int
-sockets_unix_socket_listen(struct gotwebd *env, struct socket *sock)
+sockets_unix_socket_listen(struct gotwebd *env, struct socket *sock,
+    uid_t uid, gid_t gid)
 {
 	int u_fd = -1;
 	mode_t old_umask, mode;
@@ -425,8 +545,7 @@ sockets_unix_socket_listen(struct gotwebd *env, struct
 		return -1;
 	}
 
-	if (chown(sock->conf.unix_socket_name, env->pw->pw_uid,
-	    env->pw->pw_gid) == -1) {
+	if (chown(sock->conf.unix_socket_name, uid, gid) == -1) {
 		log_warn("%s: chown", __func__);
 		close(u_fd);
 		(void)unlink(sock->conf.unix_socket_name);
@@ -521,6 +640,7 @@ sockets_socket_accept(int fd, short event, void *arg)
 	struct sockaddr_storage ss;
 	struct timeval backoff;
 	struct request *c = NULL;
+	uint8_t *buf = NULL;
 	socklen_t len;
 	int s;
 
@@ -557,28 +677,33 @@ sockets_socket_accept(int fd, short event, void *arg)
 
 	c = calloc(1, sizeof(struct request));
 	if (c == NULL) {
-		log_warn("%s", __func__);
+		log_warn("%s: calloc", __func__);
 		close(s);
 		cgi_inflight--;
 		return;
 	}
 
-	c->tp = template(c, &fcgi_write, c->outbuf, sizeof(c->outbuf));
-	if (c->tp == NULL) {
-		log_warn("%s", __func__);
+	buf = calloc(1, FCGI_RECORD_SIZE);
+	if (buf == NULL) {
+		log_warn("%s: calloc", __func__);
 		close(s);
-		cgi_inflight--;
 		free(c);
+		cgi_inflight--;
 		return;
 	}
 
+	c->buf = buf;
 	c->fd = s;
+	c->resp_fd = -1;
 	c->sock = sock;
 	memcpy(c->priv_fd, gotwebd_env->priv_fd, sizeof(c->priv_fd));
+	c->sock_id = sock->conf.id;
 	c->buf_pos = 0;
 	c->buf_len = 0;
 	c->request_started = 0;
 	c->sock->client_status = CLIENT_CONNECT;
+	c->request_id = ++request_id; /* TODO: use a client hash table like gotd does */
+	log_info("%s: new request_id: %d", __func__, request_id);
 
 	event_set(&c->ev, s, EV_READ|EV_PERSIST, fcgi_request, c);
 	event_add(&c->ev, NULL);
@@ -586,6 +711,8 @@ sockets_socket_accept(int fd, short event, void *arg)
 	evtimer_set(&c->tmo, fcgi_timeout, c);
 	evtimer_add(&c->tmo, &timeout);
 
+	TAILQ_INSERT_TAIL(&requests, c, entry);
+
 	client_cnt++;
 
 	return;
@@ -596,7 +723,7 @@ err:
 		free(c);
 }
 
-static void
+void
 sockets_rlimit(int maxfd)
 {
 	struct rlimit rl;