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

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

Download raw body.

Thread
On 2023/11/15 18:42:14 +0100, Omar Polo <op@omarpolo.com> wrote:
> 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.

Found it.  The logging handling in the child was wrong, plus in
log_debug() we log only if verbose > 1.  Now, we don't do anything
special for the case verbose == 1 so I just 'lowered' log_debug() to one
-v.

> 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.

updated diff, this includes the fix to the daemon() call too.

diff 5abbba2d467df0d641100b74fbe24428fbb1c2c6 c85d8cd6af267cd4529f13315a1e00a416621d5f
commit - 5abbba2d467df0d641100b74fbe24428fbb1c2c6
commit + c85d8cd6af267cd4529f13315a1e00a416621d5f
blob - 8a18107cdc7ef357964ae5a8c38a6e1205874e12
blob + 258caeda8f63d90382ebfba64f426fcbc541a389
--- 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
blob + 775d88bd837ed1c57363aea14c95b1793c999afa
--- 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
blob + 202f1af662fe84b6759cc1a1c53efca43470da98
--- 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
blob + 45a8a36ea39f6f705bfc81c145d550389dd82872
--- 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
blob + 228d0e727516198db3aa33c2653f669ee179b8d6
--- 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
blob + 4017a8fd50fbe849af03accbf3ef5e16ba4eb232
--- 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,54 @@ 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_debug)
+		argv[argc++] = "-d";
+	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 +261,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 +289,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 +312,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;
 
 	log_init(env->gotwebd_debug, LOG_DAEMON);
 	log_setverbose(env->gotwebd_verbose);
 
-	if (env->gotwebd_noaction)
-		ps->ps_noaction = 1;
+	if (server_proc) {
+		setproctitle("sockets");
+		log_procinit("sockets");
 
-	ps->ps_instances[PROC_SOCKS] = env->prefork_gotwebd;
-	ps->ps_instance = proc_instance;
-	if (title != NULL)
-		ps->ps_title[proc_id] = title;
+		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");
 
-	for (proc = 0; proc < nitems(procs); proc++)
-		procs[proc].p_chroot = env->httpd_chroot;
+		sockets(env, 3);
+		return 1;
+	}
 
-	/* 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");
+		fatal("daemon");
 
-	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 +417,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 +435,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 +444,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
blob + fd1c0c99231219c08e4fc5bf185228b9925892c3
--- 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 - 79d3d334581eddd8ffcc0f02f067e5c64d0be72e
blob + 73cbf47a766ba764769018c3e5833cb0414ed1f8
--- gotwebd/log.c
+++ gotwebd/log.c
@@ -164,7 +164,7 @@ log_debug(const char *emsg, ...)
 {
 	va_list ap;
 
-	if (verbose > 1) {
+	if (verbose) {
 		va_start(ap, emsg);
 		vlog(LOG_DEBUG, emsg, ap);
 		va_end(ap);
blob - a51f74abb8dfc67479ed3558a8c7efd83400e068
blob + d04e49122cc951bb2815072249af493a694624f3
--- 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
blob + 207d9f20026688bdc8849f0125e73b94a285504e
--- 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
blob + dd100773cb3593bb4f04e31d024acf711bdc912e
--- 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);