static int
daemon_poll_err(bozohttpd_t *httpd, int fd, int idx)
{
	if ((httpd->fds[idx].revents & (POLLNVAL|POLLERR|POLLHUP)) == 0)
		return 0;

	bozo_warn(httpd, "poll on fd %d pid %d revents %d: %s",
	    httpd->fds[idx].fd, getpid(), httpd->fds[idx].revents,
	    strerror(errno));
	bozo_warn(httpd, "nsock = %d", httpd->nsock);
	close(httpd->sock[idx]);
	httpd->nsock--;
	bozo_warn(httpd, "nsock now = %d", httpd->nsock);
	/* no sockets left */
	if (httpd->nsock == 0)
		exit(0);
	/* last socket closed is the easy case */
	if (httpd->nsock != idx) {
		memmove(&httpd->fds[idx], &httpd->fds[idx+1],
			(httpd->nsock - idx) * sizeof(*httpd->fds));
		memmove(&httpd->sock[idx], &httpd->sock[idx+1],
			(httpd->nsock - idx) * sizeof(*httpd->sock));
	}

	return 1;
}
/*
 * the parent never returns from this function, only children that
 * are ready to run... XXXMRG - still true in fork-lesser bozo?
 */
int
bozo_daemon_fork(bozohttpd_t *httpd)
{
	int i;

	debug((httpd, DEBUG_FAT, "%s: pid %u request_times %d",
		__func__, getpid(),
		httpd->request_times));
	/* if we've handled 5 files, exit and let someone else work */
	if (httpd->request_times > 5 ||
	    (httpd->background == 2 && httpd->request_times > 0))
		_exit(0);

#if 1
	if (httpd->request_times > 0)
		_exit(0);
#endif

	while (httpd->background) {
		struct	sockaddr_storage ss;
		socklen_t slen;
		int fd;

		if (httpd->nsock == 0)
			exit(0);

		/*
		 * wait for a connection, then fork() and return NULL in
		 * the parent, who will come back here waiting for another
		 * connection.  read the request in in the child, and return
		 * it, for processing.
		 */
again:
		if (poll(httpd->fds, (unsigned)httpd->nsock, INFTIM) == -1) {
			/* fail on programmer errors */
			if (errno == EFAULT ||
			    errno == EINVAL)
				bozo_err(httpd, 1, "poll: %s",
					strerror(errno));

			/* sleep on some temporary kernel failures */
			if (errno == ENOMEM ||
			    errno == EAGAIN)
				sleep(1);

			goto again;
		}

		for (i = 0; i < httpd->nsock; i++) {
			if (daemon_poll_err(httpd, fd, i))
				break;
			if (httpd->fds[i].revents == 0)
				continue;

			slen = sizeof(ss);
			fd = accept(httpd->fds[i].fd,
					(struct sockaddr *)(void *)&ss, &slen);
			if (fd == -1) {
				if (errno == EFAULT ||
				    errno == EINVAL)
					bozo_err(httpd, 1, "accept: %s",
						strerror(errno));

				if (errno == ENOMEM ||
				    errno == EAGAIN)
					sleep(1);

				continue;
			}

#if 0
			/*
			 * This code doesn't work.  It interacts very poorly
			 * with ~user translation and needs to be fixed.
			 */
			if (httpd->request_times > 0) {
				daemon_runchild(httpd, fd);
				return 0;
			}
#endif

			switch (fork()) {
			case -1: /* eep, failure */
				bozo_warn(httpd, "fork() failed, sleeping for "
					"10 seconds: %s", strerror(errno));
				close(fd);
				sleep(10);
				break;

			case 0: /* child */
				daemon_runchild(httpd, fd);
				return 0;

			default: /* parent */
				close(fd);
				break;
			}
		}
	}
	return 0;
}
void
bozo_daemon_init(bozohttpd_t *httpd)
{
	struct addrinfo h, *r, *r0;
	const char	*portnum;
	int e, i, on = 1;

	if (!httpd->background)
		return;

	portnum = (httpd->bindport) ? httpd->bindport : "http";
	
	memset(&h, 0, sizeof(h));
	h.ai_family = PF_UNSPEC;
	h.ai_socktype = SOCK_STREAM;
	h.ai_flags = AI_PASSIVE;
	e = getaddrinfo(httpd->bindaddress, portnum, &h, &r0);
	if (e)
		bozo_err(httpd, 1, "getaddrinfo([%s]:%s): %s",
		    httpd->bindaddress ? httpd->bindaddress : "*",
		    portnum, gai_strerror(e));
	for (r = r0; r != NULL; r = r->ai_next)
		httpd->nsock++;
	httpd->sock = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->sock));
	httpd->fds = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->fds));
	for (i = 0, r = r0; r != NULL; r = r->ai_next) {
		httpd->sock[i] = socket(r->ai_family, SOCK_STREAM, 0);
		if (httpd->sock[i] == -1)
			continue;
		if (setsockopt(httpd->sock[i], SOL_SOCKET, SO_REUSEADDR, &on,
		    sizeof(on)) == -1)
			bozo_warn(httpd, "setsockopt SO_REUSEADDR: %s",
			    strerror(errno));
		if (bind(httpd->sock[i], r->ai_addr, r->ai_addrlen) == -1)
			continue;
		if (listen(httpd->sock[i], SOMAXCONN) == -1)
			continue;
		httpd->fds[i].events = POLLIN | POLLPRI | POLLRDNORM |
				POLLRDBAND | POLLERR;
		httpd->fds[i].fd = httpd->sock[i];
		i++;
	}
	if (i == 0)
		bozo_err(httpd, 1, "could not find any addresses to bind");
	httpd->nsock = i;
	freeaddrinfo(r0);

	if (httpd->foreground == 0)
		daemon(1, 0);

	create_pidfile(httpd);

	bozo_warn(httpd, "started in daemon mode as `%s' port `%s' root `%s'",
	    httpd->virthostname, portnum, httpd->slashdir);

	signal(SIGHUP, controlled_exit);
	signal(SIGINT, controlled_exit);
	signal(SIGTERM, controlled_exit);

	signal(SIGCHLD, sigchild);
}
/*
 * bozo_user_transform does this:
 *	- chdir's /~user/public_html
 *	- returns the rest of the file, index.html appended if required
 *	- returned malloced file to serve in request->hr_file,
 *        ala transform_request().
 *
 * transform_request() is supposed to check that we have user support
 * enabled.
 */
int
bozo_user_transform(bozo_httpreq_t *request, int *isindex)
{
	bozohttpd_t *httpd = request->hr_httpd;
	char	c, *s, *file = NULL;
	struct	passwd *pw;

	*isindex = 0;

	if ((s = strchr(request->hr_file + 2, '/')) != NULL) {
		*s++ = '\0';
		c = s[strlen(s)-1];
		*isindex = (c == '/' || c == '\0');
	}

	debug((httpd, DEBUG_OBESE, "looking for user %s",
		request->hr_file + 2));
	pw = getpwnam(request->hr_file + 2);
	/* fix this up immediately */
	if (s)
		s[-1] = '/';
	if (pw == NULL) {
		(void)bozo_http_error(httpd, 404, request, "no such user");
		return 0;
	}

	debug((httpd, DEBUG_OBESE, "user %s dir %s/%s uid %d gid %d",
	      pw->pw_name, pw->pw_dir, httpd->public_html,
	      pw->pw_uid, pw->pw_gid));

	if (chdir(pw->pw_dir) < 0) {
		bozo_warn(httpd, "chdir1 error: %s: %s", pw->pw_dir,
			strerror(errno));
		(void)bozo_http_error(httpd, 404, request,
			"can't chdir to homedir");
		return 0;
	}
	if (chdir(httpd->public_html) < 0) {
		bozo_warn(httpd, "chdir2 error: %s: %s", httpd->public_html,
			strerror(errno));
		(void)bozo_http_error(httpd, 404, request,
			"can't chdir to public_html");
		return 0;
	}
	if (s == NULL || *s == '\0') {
		file = bozostrdup(httpd, httpd->index_html);
	} else {
		file = bozomalloc(httpd, strlen(s) +
		    (*isindex ? strlen(httpd->index_html) + 1 : 1));
		strcpy(file, s);
		if (*isindex)
			strcat(file, httpd->index_html);
	}

	/* see transform_request() */
	if (*file == '/' || strcmp(file, "..") == 0 ||
	    strstr(file, "/..") || strstr(file, "../")) {
		(void)bozo_http_error(httpd, 403, request, "illegal request");
		free(file);
		return 0;
	}

	if (bozo_auth_check(request, file)) {
		free(file);
		return 0;
	}

	free(request->hr_file);
	request->hr_file = file;

	debug((httpd, DEBUG_FAT, "transform_user returning %s under %s", file,
	    pw->pw_dir));
	return 1;
}