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

From:
Omar Polo <op@omarpolo.com>
Subject:
gotwebd: get rid of proc.[ch]
To:
gameoftrees@openbsd.org
Date:
Wed, 15 Nov 2023 18:42:14 +0100

Download raw body.

Thread
proc.c really shines when there's a net of different kinds of processes
with potentially many instances that needs to talk.  gotwebd instead is
just a main process for the privileged operations plus a bunch of
unpriviled workers.

using proc.c here only really caused some headaces, so here's a proposal
to remove it.  it needs some testing, but some preliminary testing
locally promises good :)

There's something regarding the verbose logging that I still need to
figure out exactly, but otherwise seems to be working just as before.

There are now two main routines that I expect will be used to send
messages:

 - main_compose_sockets(): send an imsg (with optional fd) to the
   sockets processes.  The file descriptor is dup'ed as needed.
   This alone semplifies most of the loops in config.c

 - sockets_compose_main(): to send messages the other ways.

There is no iov variant since we don't need them so far, will be easy to
add eventually.

The imsgev structs now live in the main gotwebd struct.  The parent
allocates enough entries for the children it needs to run, while each
child only initialize one to talk with the parent.

Maybe my hands slipped and there's more fixing that strictly needed...

I've omitted `got rm proc.[ch]' to keep the diff short, but I intend to
do so before committing.


diff /home/op/w/got
commit - 5abbba2d467df0d641100b74fbe24428fbb1c2c6
path + /home/op/w/got
blob - 8a18107cdc7ef357964ae5a8c38a6e1205874e12
file + gotwebd/Makefile
--- gotwebd/Makefile
+++ gotwebd/Makefile
@@ -7,7 +7,7 @@ SUBDIR = libexec
 .include "Makefile.inc"
 
 PROG =		gotwebd
-SRCS =		config.c sockets.c log.c gotwebd.c parse.y proc.c \
+SRCS =		config.c sockets.c log.c gotwebd.c parse.y \
 		fcgi.c gotweb.c got_operations.c tmpl.c pages.c
 SRCS +=		blame.c commit_graph.c delta.c diff.c \
 		diffreg.c error.c object.c object_cache.c \
blob - 1c8057cb99a030e7a3b45871f35124b82101e918
file + gotwebd/config.c
--- gotwebd/config.c
+++ gotwebd/config.c
@@ -39,31 +39,18 @@
 #include "got_opentemp.h"
 #include "got_reference.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 
 int
 config_init(struct gotwebd *env)
 {
-	struct privsep *ps = env->gotwebd_ps;
-	unsigned int what;
-
 	strlcpy(env->httpd_chroot, D_HTTPD_CHROOT, sizeof(env->httpd_chroot));
 
-	/* Global configuration. */
-	if (privsep_process == PROC_GOTWEBD)
-		env->prefork_gotwebd = GOTWEBD_NUMPROC;
+	env->prefork_gotwebd = GOTWEBD_NUMPROC;
+	env->server_cnt = 0;
+	TAILQ_INIT(&env->servers);
+	TAILQ_INIT(&env->sockets);
 
-	ps->ps_what[PROC_GOTWEBD] = CONFIG_ALL;
-	ps->ps_what[PROC_SOCKS] = CONFIG_SOCKS;
-
-	/* Other configuration. */
-	what = ps->ps_what[privsep_process];
-	if (what & CONFIG_SOCKS) {
-		env->server_cnt = 0;
-		TAILQ_INIT(&env->servers);
-		TAILQ_INIT(&env->sockets);
-	}
 	return 0;
 }
 
@@ -71,23 +58,17 @@ int
 config_getcfg(struct gotwebd *env, struct imsg *imsg)
 {
 	/* nothing to do but tell gotwebd configuration is done */
-	if (privsep_process != PROC_GOTWEBD)
-		proc_compose(env->gotwebd_ps, PROC_GOTWEBD,
-		    IMSG_CFG_DONE, NULL, 0);
-
+	if (sockets_compose_main(env, IMSG_CFG_DONE, NULL, 0) == -1)
+		fatal("sockets_compose_main IMSG_CFG_DONE");
 	return 0;
 }
 
 int
 config_setserver(struct gotwebd *env, struct server *srv)
 {
-	struct server ssrv;
-	struct privsep *ps = env->gotwebd_ps;
-
-	memcpy(&ssrv, srv, sizeof(ssrv));
-	if (proc_compose(ps, PROC_SOCKS, IMSG_CFG_SRV, &ssrv, sizeof(ssrv))
+	if (main_compose_sockets(env, IMSG_CFG_SRV, -1, srv, sizeof(*srv))
 	    == -1)
-		fatal("proc_compose");
+		fatal("main_compose_sockets IMSG_CFG_SRV");
 	return 0;
 }
 
@@ -97,17 +78,11 @@ config_getserver(struct gotwebd *env, struct imsg *ims
 	struct server *srv;
 	uint8_t *p = imsg->data;
 
-	IMSG_SIZE_CHECK(imsg, &srv);
-
 	srv = calloc(1, sizeof(*srv));
 	if (srv == NULL)
 		fatalx("%s: calloc", __func__);
 
-	if (IMSG_DATA_SIZE(imsg) != sizeof(*srv)) {
-		log_debug("%s: imsg size error", __func__);
-		free(srv);
-		return 1;
-	}
+	IMSG_SIZE_CHECK(imsg, srv);
 
 	memcpy(srv, p, sizeof(*srv));
 	srv->cached_repos = calloc(GOTWEBD_REPO_CACHESIZE,
@@ -129,62 +104,15 @@ config_getserver(struct gotwebd *env, struct imsg *ims
 int
 config_setsock(struct gotwebd *env, struct socket *sock)
 {
-	struct privsep *ps = env->gotwebd_ps;
-	struct socket_conf s;
-	int id;
-	int fd = -1, n, m;
-	struct iovec iov[6];
-	size_t c;
-	unsigned int what;
-
 	/* open listening sockets */
 	if (sockets_privinit(env, sock) == -1)
 		return -1;
 
-	for (id = 0; id < PROC_MAX; id++) {
-		what = ps->ps_what[id];
+	if (main_compose_sockets(env, IMSG_CFG_SOCK, sock->fd,
+	    &sock->conf, sizeof(sock->conf)) == -1)
+		fatal("main_compose_sockets IMSG_CFG_SOCK");
 
-		if ((what & CONFIG_SOCKS) == 0 || id == privsep_process)
-			continue;
-
-		memcpy(&s, &sock->conf, sizeof(s));
-
-		c = 0;
-		iov[c].iov_base = &s;
-		iov[c++].iov_len = sizeof(s);
-
-		if (id == PROC_SOCKS) {
-			/* XXX imsg code will close the fd after 1st call */
-			n = -1;
-			proc_range(ps, id, &n, &m);
-			for (n = 0; n < m; n++) {
-				if (sock->fd == -1)
-					fd = -1;
-				else if ((fd = dup(sock->fd)) == -1)
-					return 1;
-				if (proc_composev_imsg(ps, id, n, IMSG_CFG_SOCK,
-				    -1, fd, iov, c) != 0) {
-					log_warn("%s: failed to compose "
-					    "IMSG_CFG_SOCK imsg",
-					    __func__);
-					return 1;
-				}
-				if (proc_flush_imsg(ps, id, n) == -1) {
-					log_warn("%s: failed to flush "
-					    "IMSG_CFG_SOCK imsg",
-					    __func__);
-					return 1;
-				}
-			}
-		}
-	}
-
-	/* Close socket early to prevent fd exhaustion in gotwebd. */
-	if (sock->fd != -1) {
-		close(sock->fd);
-		sock->fd = -1;
-	}
-
+	sock->fd = -1;
 	return 0;
 }
 
@@ -237,60 +165,20 @@ config_getsock(struct gotwebd *env, struct imsg *imsg)
 int
 config_setfd(struct gotwebd *env, struct socket *sock)
 {
-	struct privsep *ps = env->gotwebd_ps;
-	int id, s;
-	int fd = -1, n, m, j;
-	struct iovec iov[6];
-	size_t c;
-	unsigned int what;
+	int i, fd;
 
 	log_debug("%s: Allocating %d file descriptors",
 	    __func__, PRIV_FDS__MAX + GOTWEB_PACK_NUM_TEMPFILES);
 
-	for (j = 0; j < PRIV_FDS__MAX + GOTWEB_PACK_NUM_TEMPFILES; j++) {
-		for (id = 0; id < PROC_MAX; id++) {
-			what = ps->ps_what[id];
-
-			if ((what & CONFIG_SOCKS) == 0 || id == privsep_process)
-				continue;
-
-			s = sock->conf.id;
-			c = 0;
-			iov[c].iov_base = &s;
-			iov[c++].iov_len = sizeof(s);
-
-			if (id == PROC_SOCKS) {
-				/*
-				 * XXX imsg code will close the fd
-				 * after 1st call
-				 */
-				n = -1;
-				proc_range(ps, id, &n, &m);
-				for (n = 0; n < m; n++) {
-					fd = got_opentempfd();
-					if (fd == -1)
-						return 1;
-					if (proc_composev_imsg(ps, id, n,
-					    IMSG_CFG_FD, -1, fd, iov, c) != 0) {
-						log_warn("%s: failed to compose "
-						    "IMSG_CFG_FD imsg",
-						    __func__);
-						return 1;
-					}
-					if (proc_flush_imsg(ps, id, n) == -1) {
-						log_warn("%s: failed to flush "
-						    "IMSG_CFG_FD imsg",
-						    __func__);
-						return 1;
-					}
-				}
-			}
-		}
-
-		/* Close fd early to prevent fd exhaustion in gotwebd. */
-		if (fd != -1)
-			close(fd);
+	for (i = 0; i < PRIV_FDS__MAX + GOTWEB_PACK_NUM_TEMPFILES; i++) {
+		fd = got_opentempfd();
+		if (fd == -1)
+			fatal("got_opentemp");
+		if (main_compose_sockets(env, IMSG_CFG_FD, fd,
+		    &sock->conf.id, sizeof(sock->conf.id)) == -1)
+			fatal("main_compose_sockets IMSG_CFG_FD");
 	}
+
 	return 0;
 }
 
blob - b6905f7fda842f60988300371ff61ab63db68a4c
file + gotwebd/fcgi.c
--- gotwebd/fcgi.c
+++ gotwebd/fcgi.c
@@ -35,7 +35,6 @@
 #include "got_error.h"
 #include "got_reference.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 #include "tmpl.h"
 
blob - 6d9d5cf8b5507c4d545eca925df5fe128789c73a
file + gotwebd/got_operations.c
--- gotwebd/got_operations.c
+++ gotwebd/got_operations.c
@@ -39,7 +39,6 @@
 #include "got_blame.h"
 #include "got_privsep.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 
 static const struct got_error *got_init_repo_commit(struct repo_commit **);
blob - 5d1283460327c8a36e76c5f8ed29b5fd573a5ce8
file + gotwebd/gotweb.c
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -49,7 +49,6 @@
 #include "got_blame.h"
 #include "got_privsep.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 #include "tmpl.h"
 
blob - 9b7b6853658f6996141e0355f9a0a6f08fca0c7c
file + gotwebd/gotwebd.c
--- gotwebd/gotwebd.c
+++ gotwebd/gotwebd.c
@@ -42,7 +42,6 @@
 #include "got_opentemp.h"
 #include "got_reference.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 
 __dead void usage(void);
@@ -52,40 +51,138 @@ int	 gotwebd_configure(struct gotwebd *);
 void	 gotwebd_configure_done(struct gotwebd *);
 void	 gotwebd_sighdlr(int sig, short event, void *arg);
 void	 gotwebd_shutdown(void);
-int	 gotwebd_dispatch_sockets(int, struct privsep_proc *, struct imsg *);
+void	 gotwebd_dispatch_sockets(int, short, void *);
 
 struct gotwebd	*gotwebd_env;
 
-static struct privsep_proc procs[] = {
-	{ "sockets",	PROC_SOCKS,	gotwebd_dispatch_sockets, sockets,
-	    sockets_shutdown },
-};
+void
+imsg_event_add(struct imsgev *iev)
+{
+	if (iev->handler == NULL) {
+		imsg_flush(&iev->ibuf);
+		return;
+	}
 
+	iev->events = EV_READ;
+	if (iev->ibuf.w.queued)
+		iev->events |= EV_WRITE;
+
+	event_del(&iev->ev);
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+	event_add(&iev->ev, NULL);
+}
+
 int
-gotwebd_dispatch_sockets(int fd, struct privsep_proc *p, struct imsg *imsg)
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+    pid_t pid, int fd, const void *data, uint16_t datalen)
 {
-	struct privsep		*ps = p->p_ps;
-	struct gotwebd		*env = ps->ps_env;
+	int ret;
 
-	switch (imsg->hdr.type) {
-	case IMSG_CFG_DONE:
-		gotwebd_configure_done(env);
-		break;
-	default:
-		return (-1);
+	ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen);
+	if (ret == -1)
+		return (ret);
+	imsg_event_add(iev);
+	return (ret);
+}
+
+int
+main_compose_sockets(struct gotwebd *env, uint32_t type, int fd,
+    const void *data, uint16_t len)
+{
+	size_t	 i;
+	int	 ret, d;
+
+	for (i = 0; i < env->nserver; ++i) {
+		d = -1;
+		if (fd != -1 && (d = dup(fd)) == -1)
+			return (-1);
+
+		ret = imsg_compose_event(&env->iev_server[i], type, 0, -1,
+		    d, data, len);
+		if (ret == -1)
+			return (-1);
+
+		/* prevent fd exhaustion */
+		if (d != -1) {
+			do {
+				ret = imsg_flush(&env->iev_server[i].ibuf);
+			} while (ret == -1 && errno == EAGAIN);
+			if (ret == -1)
+				return (-1);
+			imsg_event_add(&env->iev_server[i]);
+		}
 	}
 
-	return (0);
+	if (fd != -1)
+		close(fd);
+
+	return 0;
 }
 
+int
+sockets_compose_main(struct gotwebd *env, uint32_t type, const void *d,
+    uint16_t len)
+{
+	return (imsg_compose_event(env->iev_parent, type, 0, -1, -1, d, len));
+}
+
 void
+gotwebd_dispatch_sockets(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 = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
+
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case 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; */
 
-	if (privsep_process != PROC_GOTWEBD)
-		return;
-
 	switch (sig) {
 	case SIGHUP:
 		log_info("%s: ignoring SIGHUP", __func__);
@@ -105,6 +202,52 @@ gotwebd_sighdlr(int sig, short event, void *arg)
 	}
 }
 
+static int
+spawn_socket_process(struct gotwebd *env, const char *argv0, int n)
+{
+	const char	*argv[5];
+	int		 argc = 0;
+	int		 p[2];
+	pid_t		 pid;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
+		fatal("socketpair");
+
+	switch (pid = fork()) {
+	case -1:
+		fatal("fork");
+	case 0:		/* child */
+		break;
+	default:	/* parent */
+		close(p[0]);
+		imsg_init(&env->iev_server[n].ibuf, p[1]);
+		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;
+	}
+
+	close(p[1]);
+
+	argv[argc++] = argv0;
+	argv[argc++] = "-S";
+	if (env->gotwebd_verbose)
+		argv[argc++] = "-v";
+	argv[argc] = NULL;
+
+	if (p[0] != 3) {
+		if (dup2(p[0], 3) == -1)
+			fatal("dup2");
+	} else if (fcntl(p[0], F_SETFD, 0) == -1)
+		fatal("fcntl");
+
+	/* obnoxious cast */
+	execvp(argv0, (char * const *)argv);
+	fatal("execvp %s", argv0);
+}
+
 __dead void
 usage(void)
 {
@@ -116,25 +259,27 @@ usage(void)
 int
 main(int argc, char **argv)
 {
-	struct gotwebd *env;
-	struct privsep *ps;
-	unsigned int proc;
-	int ch;
-	const char *conffile = GOTWEBD_CONF;
-	enum privsep_procid proc_id = PROC_GOTWEBD;
-	int proc_instance = 0;
-	const char *errp, *title = NULL;
-	int argc0 = argc;
+	struct event		 sigint, sigterm, sighup, sigpipe, sigusr1;
+	struct gotwebd		*env;
+	struct passwd		*pw;
+	int			 ch, i;
+	int			 no_action = 0;
+	int			 server_proc = 0;
+	const char		*conffile = GOTWEBD_CONF;
+	const char		*argv0;
 
+	if ((argv0 = argv[0]) == NULL)
+		argv0 = "gotwebd";
+
 	env = calloc(1, sizeof(*env));
 	if (env == NULL)
 		fatal("%s: calloc", __func__);
+	config_init(env);
 
 	/* log to stderr until daemonized */
 	log_init(1, LOG_DAEMON);
 
-	/* XXX: add s and S for both sockets */
-	while ((ch = getopt(argc, argv, "D:df:I:nP:v")) != -1) {
+	while ((ch = getopt(argc, argv, "D:df:nSv")) != -1) {
 		switch (ch) {
 		case 'D':
 			if (cmdline_symset(optarg) < 0)
@@ -142,26 +287,16 @@ main(int argc, char **argv)
 				    optarg);
 			break;
 		case 'd':
-			env->gotwebd_debug = 2;
+			env->gotwebd_debug = 1;
 			break;
 		case 'f':
 			conffile = optarg;
 			break;
-		case 'I':
-			proc_instance = strtonum(optarg, 0,
-			    PROC_MAX_INSTANCES, &errp);
-			if (errp)
-				fatalx("invalid process instance");
-			break;
 		case 'n':
-			env->gotwebd_debug = 2;
-			env->gotwebd_noaction = 1;
+			no_action = 1;
 			break;
-		case 'P':
-			title = optarg;
-			proc_id = proc_getid(procs, nitems(procs), title);
-			if (proc_id == PROC_MAX)
-				fatalx("invalid process name");
+		case 'S':
+			server_proc = 1;
 			break;
 		case 'v':
 			env->gotwebd_verbose++;
@@ -175,72 +310,77 @@ main(int argc, char **argv)
 	if (argc > 0)
 		usage();
 
-	ps = calloc(1, sizeof(*ps));
-	if (ps == NULL)
-		fatal("%s: calloc:", __func__);
-
 	gotwebd_env = env;
-	env->gotwebd_ps = ps;
-	ps->ps_env = env;
 	env->gotwebd_conffile = conffile;
 
 	if (parse_config(env->gotwebd_conffile, env) == -1)
 		exit(1);
 
-	if (env->gotwebd_noaction && !env->gotwebd_debug)
-		env->gotwebd_debug = 1;
+	if (no_action) {
+		fprintf(stderr, "configuration OK\n");
+		exit(0);
+	}
 
 	/* check for root privileges */
-	if (env->gotwebd_noaction == 0) {
-		if (geteuid())
-			fatalx("need root privileges");
-	}
+	if (geteuid())
+		fatalx("need root privileges");
 
-	ps->ps_pw = getpwnam(GOTWEBD_USER);
-	if (ps->ps_pw == NULL)
+	pw = getpwnam(GOTWEBD_USER);
+	if (pw == NULL)
 		fatalx("unknown user %s", GOTWEBD_USER);
+	env->pw = pw;
 
+	if (server_proc) {
+		setproctitle("sockets");
+		log_procinit("sockets");
+
+		if (chroot(pw->pw_dir) == -1)
+			fatal("chroot %s", pw->pw_dir);
+		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)
+			fatal("failed to drop privileges");
+
+		sockets(env, 3);
+		return 1;
+	}
+
 	log_init(env->gotwebd_debug, LOG_DAEMON);
 	log_setverbose(env->gotwebd_verbose);
 
-	if (env->gotwebd_noaction)
-		ps->ps_noaction = 1;
+	if (!env->gotwebd_debug && daemon(1, 0) == -1)
+		fatal("daemon");
 
-	ps->ps_instances[PROC_SOCKS] = env->prefork_gotwebd;
-	ps->ps_instance = proc_instance;
-	if (title != NULL)
-		ps->ps_title[proc_id] = title;
-
-	for (proc = 0; proc < nitems(procs); proc++)
-		procs[proc].p_chroot = env->httpd_chroot;
-
-	/* only the gotwebd returns */
-	proc_init(ps, procs, nitems(procs), argc0, argv, proc_id);
-
-	log_procinit("gotwebd");
-	if (!env->gotwebd_debug && daemon(0, 0) == -1)
-		fatal("can't daemonize");
-
-	if (ps->ps_noaction == 0)
-		log_info("%s startup", getprogname());
-
 	event_init();
 
-	signal_set(&ps->ps_evsigint, SIGINT, gotwebd_sighdlr, ps);
-	signal_set(&ps->ps_evsigterm, SIGTERM, gotwebd_sighdlr, ps);
-	signal_set(&ps->ps_evsighup, SIGHUP, gotwebd_sighdlr, ps);
-	signal_set(&ps->ps_evsigpipe, SIGPIPE, gotwebd_sighdlr, ps);
-	signal_set(&ps->ps_evsigusr1, SIGUSR1, gotwebd_sighdlr, ps);
+	env->nserver = env->prefork_gotwebd;
+	env->iev_server = calloc(env->nserver, sizeof(*env->iev_server));
+	if (env->iev_server == NULL)
+		fatal("calloc");
 
-	signal_add(&ps->ps_evsigint, NULL);
-	signal_add(&ps->ps_evsigterm, NULL);
-	signal_add(&ps->ps_evsighup, NULL);
-	signal_add(&ps->ps_evsigpipe, NULL);
-	signal_add(&ps->ps_evsigusr1, NULL);
+	for (i = 0; i < env->nserver; ++i) {
+		if (spawn_socket_process(env, argv0, i) == -1)
+			fatal("spawn_socket_process");
+	}
 
-	if (!env->gotwebd_noaction)
-		proc_connect(ps);
+	log_procinit("gotwebd");
 
+	log_info("%s startup", getprogname());
+
+	signal_set(&sigint, SIGINT, gotwebd_sighdlr, env);
+	signal_set(&sigterm, SIGTERM, gotwebd_sighdlr, env);
+	signal_set(&sighup, SIGHUP, gotwebd_sighdlr, env);
+	signal_set(&sigpipe, SIGPIPE, gotwebd_sighdlr, env);
+	signal_set(&sigusr1, SIGUSR1, gotwebd_sighdlr, env);
+
+	signal_add(&sigint, NULL);
+	signal_add(&sigterm, NULL);
+	signal_add(&sighup, NULL);
+	signal_add(&sigpipe, NULL);
+	signal_add(&sigusr1, NULL);
+
 	if (gotwebd_configure(env) == -1)
 		fatalx("configuration failed");
 
@@ -275,14 +415,7 @@ gotwebd_configure(struct gotwebd *env)
 {
 	struct server *srv;
 	struct socket *sock;
-	int id;
 
-	if (env->gotwebd_noaction) {
-		fprintf(stderr, "configuration OK\n");
-		proc_kill(env->gotwebd_ps);
-		exit(0);
-	}
-
 	/* gotweb need to reload its config. */
 	env->gotwebd_reload = env->prefork_gotwebd;
 
@@ -300,11 +433,8 @@ gotwebd_configure(struct gotwebd *env)
 			fatalx("%s: send priv_fd error", __func__);
 	}
 
-	for (id = 0; id < PROC_MAX; id++) {
-		if (id == privsep_process)
-			continue;
-		proc_compose(env->gotwebd_ps, id, IMSG_CFG_DONE, NULL, 0);
-	}
+	if (main_compose_sockets(env, IMSG_CFG_DONE, -1, NULL, 0) == -1)
+		fatal("main_compose_sockets IMSG_CFG_DONE");
 
 	return (0);
 }
@@ -312,31 +442,44 @@ gotwebd_configure(struct gotwebd *env)
 void
 gotwebd_configure_done(struct gotwebd *env)
 {
-	int id;
-
 	if (env->gotwebd_reload == 0) {
 		log_warnx("%s: configuration already finished", __func__);
 		return;
 	}
 
 	env->gotwebd_reload--;
-	if (env->gotwebd_reload == 0) {
-		for (id = 0; id < PROC_MAX; id++) {
-			if (id == privsep_process)
-				continue;
-			proc_compose(env->gotwebd_ps, id, IMSG_CTL_START,
-			    NULL, 0);
-		}
-	}
+	if (env->gotwebd_reload == 0 &&
+	    main_compose_sockets(env, IMSG_CTL_START, -1, NULL, 0) == -1)
+		fatal("main_compose_sockets IMSG_CTL_START");
 }
 
 void
 gotwebd_shutdown(void)
 {
-	proc_kill(gotwebd_env->gotwebd_ps);
+	struct gotwebd	*env = gotwebd_env;
+	pid_t		 pid;
+	int		 i, status;
 
-	/* unlink(gotwebd_env->gotweb->gotweb_conf.gotweb_unix_socket_name); */
-	/* free(gotwebd_env->gotweb); */
+	for (i = 0; i < env->nserver; ++i) {
+		event_del(&env->iev_server[i].ev);
+		imsg_clear(&env->iev_server[i].ibuf);
+		close(env->iev_server[i].ibuf.fd);
+		env->iev_server[i].ibuf.fd = -1;
+	}
+
+	do {
+		pid = waitpid(WAIT_ANY, &status, 0);
+		if (pid <= 0)
+			continue;
+
+		if (WIFSIGNALED(status))
+			log_warnx("lost child: pid %u terminated; signal %d",
+			    pid, WTERMSIG(status));
+		else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
+			log_warnx("lost child: pid %u exited abnormally",
+			    pid);
+	} while (pid != -1 || (pid == -1 && errno == EINTR));
+
 	free(gotwebd_env);
 
 	log_warnx("gotwebd terminating");
blob - 44293b62e594c517072cd3268cb34949d7393bd0
file + gotwebd/gotwebd.h
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -49,6 +49,8 @@
 #define GOTWEBD_NUMPROC		 3
 #define GOTWEBD_REPO_CACHESIZE	 4
 
+#define PROC_MAX_INSTANCES	 32
+
 /* GOTWEB DEFAULTS */
 #define MAX_QUERYSTRING		 2048
 #define MAX_DOCUMENT_URI	 255
@@ -120,13 +122,28 @@ struct got_tree_entry;
 struct got_reflist_head;
 
 enum imsg_type {
-	IMSG_CFG_SRV = IMSG_PROC_MAX,
+	IMSG_CFG_SRV,
 	IMSG_CFG_SOCK,
 	IMSG_CFG_FD,
 	IMSG_CFG_DONE,
 	IMSG_CTL_START,
 };
 
+struct imsgev {
+	struct imsgbuf		 ibuf;
+	void			(*handler)(int, short, void *);
+	struct event		 ev;
+	void			*data;
+	short			 events;
+};
+
+#define IMSG_SIZE_CHECK(imsg, p) do {					\
+	if (IMSG_DATA_SIZE(imsg) < sizeof(*p))				\
+		fatalx("bad length imsg received (%s)",	#p);		\
+} while (0)
+
+#define IMSG_DATA_SIZE(imsg)	((imsg)->hdr.len - IMSG_HEADER_SIZE)
+
 struct env_val {
 	SLIST_ENTRY(env_val)	 entry;
 	char			*val;
@@ -345,17 +362,22 @@ struct socket {
 };
 TAILQ_HEAD(socketlist, socket);
 
+struct passwd;
 struct gotwebd {
 	struct serverlist	servers;
 	struct socketlist	sockets;
 
-	struct privsep	*gotwebd_ps;
 	const char	*gotwebd_conffile;
 
 	int		 gotwebd_debug;
 	int		 gotwebd_verbose;
-	int		 gotwebd_noaction;
 
+	struct imsgev	*iev_parent;
+	struct imsgev	*iev_server;
+	size_t		 nserver;
+
+	struct passwd	*pw;
+
 	uint16_t	 prefork_gotwebd;
 	int		 gotwebd_reload;
 
@@ -442,9 +464,17 @@ extern struct gotwebd	*gotwebd_env;
 typedef int (*got_render_blame_line_cb)(struct template *, const char *,
     struct blame_line *, int, int);
 
+/* 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);
+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);
+
 /* sockets.c */
-void sockets(struct privsep *, struct privsep_proc *);
-void sockets_shutdown(void);
+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 *);
@@ -519,3 +549,25 @@ int config_setfd(struct gotwebd *, struct socket *);
 int config_getfd(struct gotwebd *, struct imsg *);
 int config_getcfg(struct gotwebd *, struct imsg *);
 int config_init(struct gotwebd *);
+
+/* log.c */
+void	log_init(int, int);
+void	log_procinit(const char *);
+void	log_setverbose(int);
+int	log_getverbose(void);
+void	log_warn(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_warnx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_info(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_debug(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	logit(int, const char *, ...)
+	    __attribute__((__format__ (printf, 2, 3)));
+void	vlog(int, const char *, va_list)
+	    __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
blob - a51f74abb8dfc67479ed3558a8c7efd83400e068
file + gotwebd/pages.tmpl
--- gotwebd/pages.tmpl
+++ gotwebd/pages.tmpl
@@ -34,8 +34,6 @@
 #include "got_object.h"
 #include "got_reference.h"
 
-#include "proc.h"
-
 #include "gotwebd.h"
 #include "tmpl.h"
 
blob - b02dbb86b14c946386f468402580e3119a1f1e61
file + gotwebd/parse.y
--- gotwebd/parse.y
+++ gotwebd/parse.y
@@ -49,7 +49,6 @@
 
 #include "got_reference.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 
 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
blob - 16a74076b94ffabf45821936ff713dacb0e1dff0
file + gotwebd/sockets.c
--- gotwebd/sockets.c
+++ gotwebd/sockets.c
@@ -55,28 +55,25 @@
 #include "got_repository.h"
 #include "got_privsep.h"
 
-#include "proc.h"
 #include "gotwebd.h"
 #include "tmpl.h"
 
 #define SOCKS_BACKLOG 5
 #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
 
-
 volatile int client_cnt;
 
 static struct timeval	timeout = { TIMEOUT_DEFAULT, 0 };
 
 static void	 sockets_sighdlr(int, short, void *);
-static void	 sockets_run(struct privsep *, struct privsep_proc *, void *);
+static void	 sockets_shutdown(void);
 static void	 sockets_launch(void);
 static void	 sockets_purge(struct gotwebd *);
 static void	 sockets_accept_paused(int, short, void *);
 static void	 sockets_rlimit(int);
 
-static int	 sockets_dispatch_gotwebd(int, struct privsep_proc *,
-		    struct imsg *);
-static int	 sockets_unix_socket_listen(struct privsep *, struct socket *);
+static void	 sockets_dispatch_main(int, short, void *);
+static int	 sockets_unix_socket_listen(struct gotwebd *, struct socket *);
 static int	 sockets_create_socket(struct address *);
 static int	 sockets_accept_reserve(int, struct sockaddr *, socklen_t *,
 		    int, volatile int *);
@@ -88,35 +85,44 @@ static struct socket *sockets_conf_new_socket_fcgi(str
 
 int cgi_inflight = 0;
 
-static struct privsep_proc procs[] = {
-	{ "gotwebd",	PROC_GOTWEBD,	sockets_dispatch_gotwebd  },
-};
-
 void
-sockets(struct privsep *ps, struct privsep_proc *p)
+sockets(struct gotwebd *env, int fd)
 {
-	proc_run(ps, p, procs, nitems(procs), sockets_run, NULL);
-}
+	struct event	 sighup, sigpipe, sigusr1, sigchld;
 
-static void
-sockets_run(struct privsep *ps, struct privsep_proc *p, void *arg)
-{
-	if (config_init(ps->ps_env) == -1)
-		fatal("failed to initialize configuration");
+	event_init();
 
-	p->p_shutdown = sockets_shutdown;
-
 	sockets_rlimit(-1);
 
-	signal_del(&ps->ps_evsigchld);
-	signal_set(&ps->ps_evsigchld, SIGCHLD, sockets_sighdlr, ps);
-	signal_add(&ps->ps_evsigchld, NULL);
+	if (config_init(env) == -1)
+		fatal("failed to initialize configuration");
 
+	if ((env->iev_parent = malloc(sizeof(*env->iev_parent))) == NULL)
+		fatal("malloc");
+	imsg_init(&env->iev_parent->ibuf, fd);
+	env->iev_parent->handler = sockets_dispatch_main;
+	env->iev_parent->data = env->iev_parent;
+	event_set(&env->iev_parent->ev, fd, EV_READ, sockets_dispatch_main,
+	    env->iev_parent);
+	event_add(&env->iev_parent->ev, NULL);
+
+	signal_set(&sighup, SIGCHLD, sockets_sighdlr, env);
+	signal_add(&sighup, NULL);
+	signal_set(&sigpipe, SIGCHLD, sockets_sighdlr, env);
+	signal_add(&sigpipe, NULL);
+	signal_set(&sigusr1, SIGCHLD, sockets_sighdlr, env);
+	signal_add(&sigusr1, NULL);
+	signal_set(&sigchld, SIGCHLD, sockets_sighdlr, env);
+	signal_add(&sigchld, NULL);
+
 #ifndef PROFILE
 	if (pledge("stdio rpath inet recvfd proc exec sendfd unveil",
 	    NULL) == -1)
 		fatal("pledge");
 #endif
+
+	event_dispatch();
+	sockets_shutdown();
 }
 
 void
@@ -301,48 +307,68 @@ sockets_purge(struct gotwebd *env)
 	}
 }
 
-static int
-sockets_dispatch_gotwebd(int fd, struct privsep_proc *p, struct imsg *imsg)
+static void
+sockets_dispatch_main(int fd, short event, void *arg)
 {
-	struct privsep *ps = p->p_ps;
-	int res = 0, cmd = 0, verbose;
+	struct imsgev		*iev = arg;
+	struct imsgbuf		*ibuf;
+	struct imsg		 imsg;
+	struct gotwebd		*env = gotwebd_env;
+	ssize_t			 n;
+	int			 shut = 0;
 
-	switch (imsg->hdr.type) {
-	case IMSG_CFG_SRV:
-		config_getserver(gotwebd_env, imsg);
-		break;
-	case IMSG_CFG_SOCK:
-		config_getsock(gotwebd_env, imsg);
-		break;
-	case IMSG_CFG_FD:
-		config_getfd(gotwebd_env, imsg);
-		break;
-	case IMSG_CFG_DONE:
-		config_getcfg(gotwebd_env, imsg);
-		break;
-	case IMSG_CTL_START:
-		sockets_launch();
-		break;
-	case IMSG_CTL_VERBOSE:
-		IMSG_SIZE_CHECK(imsg, &verbose);
-		memcpy(&verbose, imsg->data, sizeof(verbose));
-		log_setverbose(verbose);
-		break;
-	default:
-		return -1;
+	ibuf = &iev->ibuf;
+
+	if (event & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+			fatal("imsg_read error");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
 	}
+	if (event & EV_WRITE) {
+		if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+			fatal("msgbuf_write");
+		if (n == 0)	/* Connection closed */
+			shut = 1;
+	}
 
-	switch (cmd) {
-	case 0:
-		break;
-	default:
-		if (proc_compose_imsg(ps, PROC_GOTWEBD, -1, cmd,
-		    imsg->hdr.peerid, -1, &res, sizeof(res)) == -1)
-			return -1;
-		break;
+	for (;;) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1)
+			fatal("imsg_get");
+		if (n == 0)	/* No more messages. */
+			break;
+
+		switch (imsg.hdr.type) {
+		case IMSG_CFG_SRV:
+			config_getserver(env, &imsg);
+			break;
+		case IMSG_CFG_SOCK:
+			config_getsock(env, &imsg);
+			break;
+		case IMSG_CFG_FD:
+			config_getfd(env, &imsg);
+			break;
+		case IMSG_CFG_DONE:
+			config_getcfg(env, &imsg);
+			break;
+		case IMSG_CTL_START:
+			sockets_launch();
+			break;
+		default:
+			fatalx("%s: unknown imsg type %d", __func__,
+			    imsg.hdr.type);
+		}
+
+		imsg_free(&imsg);
 	}
 
-	return 0;
+	if (!shut)
+		imsg_event_add(iev);
+	else {
+		/* This pipe is dead.  Remove its event handler */
+		event_del(&iev->ev);
+		event_loopexit(NULL);
+	}
 }
 
 static void
@@ -366,7 +392,7 @@ sockets_sighdlr(int sig, short event, void *arg)
 	}
 }
 
-void
+static void
 sockets_shutdown(void)
 {
 	struct server *srv, *tsrv;
@@ -395,12 +421,10 @@ sockets_shutdown(void)
 int
 sockets_privinit(struct gotwebd *env, struct socket *sock)
 {
-	struct privsep *ps = env->gotwebd_ps;
-
 	if (sock->conf.af_type == AF_UNIX) {
 		log_debug("%s: initializing unix socket %s", __func__,
 		    sock->conf.unix_socket_name);
-		sock->fd = sockets_unix_socket_listen(ps, sock);
+		sock->fd = sockets_unix_socket_listen(env, sock);
 		if (sock->fd == -1) {
 			log_warnx("%s: create unix socket failed", __func__);
 			return -1;
@@ -422,9 +446,8 @@ sockets_privinit(struct gotwebd *env, struct socket *s
 }
 
 static int
-sockets_unix_socket_listen(struct privsep *ps, struct socket *sock)
+sockets_unix_socket_listen(struct gotwebd *env, struct socket *sock)
 {
-	struct gotwebd *env = ps->ps_env;
 	struct sockaddr_un sun;
 	struct socket *tsock;
 	int u_fd = -1;
@@ -480,8 +503,8 @@ sockets_unix_socket_listen(struct privsep *ps, struct 
 		return -1;
 	}
 
-	if (chown(sock->conf.unix_socket_name, ps->ps_pw->pw_uid,
-	    ps->ps_pw->pw_gid) == -1) {
+	if (chown(sock->conf.unix_socket_name, env->pw->pw_uid,
+	    env->pw->pw_gid) == -1) {
 		log_warn("%s: chown", __func__);
 		close(u_fd);
 		(void)unlink(sock->conf.unix_socket_name);