root/libexec/httpd/daemon-bozo.c
/*      $NetBSD: daemon-bozo.c,v 1.23 2026/05/03 21:40:37 mrg Exp $     */

/*      $eterna: daemon-bozo.c,v 1.24 2011/11/18 09:21:15 mrg Exp $     */

/*
 * Copyright (c) 1997-2019 Matthew R. Green
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer and
 *    dedication in the documentation and/or other materials provided
 *    with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

/* this code implements daemon mode for bozohttpd */

#ifndef NO_DAEMON_MODE

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "bozohttpd.h"

static  void    sigchild(int);  /* SIGCHLD handler */

#ifndef POLLRDNORM
#define POLLRDNORM 0
#endif
#ifndef POLLRDBAND
#define POLLRDBAND 0
#endif
#ifndef INFTIM
#define INFTIM -1
#endif
#ifndef USE_ARG
#define USE_ARG(x)      /*LINTED*/(void)&(x)
#endif

static const char* pidfile_path = NULL;
static pid_t pidfile_pid = 0;

static void
sigchild(int signo)
{
        USE_ARG(signo);
        while (waitpid(-1, NULL, WNOHANG) > 0)
                /* nothing */;
}

/* Signal handler to exit in a controlled manner.  This ensures that
 * any atexit(3) handlers are properly executed. */
BOZO_DEAD static void
controlled_exit(int signo)
{
        USE_ARG(signo);
        exit(EXIT_SUCCESS);
}

static void
remove_pidfile(void)
{

        if (pidfile_path != NULL && pidfile_pid == getpid()) {
                (void)unlink(pidfile_path);
                pidfile_path = NULL;
        }
}

static void
create_pidfile(bozohttpd_t *httpd)
{
        FILE *file;

        assert(pidfile_path == NULL);

        if (httpd->pidfile == NULL)
                return;

        if (atexit(remove_pidfile) == -1)
                bozoerr(httpd, 1, "Failed to install pidfile handler");

        if ((file = fopen(httpd->pidfile, "w")) == NULL)
                bozoerr(httpd, 1, "Failed to create pidfile '%s'",
                    httpd->pidfile);
        (void)fprintf(file, "%d\n", getpid());
        (void)fclose(file);

        pidfile_path = httpd->pidfile;
        pidfile_pid = getpid();

        debug((httpd, DEBUG_FAT, "Created pid file '%s' for pid %d",
            pidfile_path, pidfile_pid));
}

void
bozo_daemon_init(bozohttpd_t *httpd)
{
        struct addrinfo h, *r, *r0;
        const char      *portnum;
        int e, i, on = 1;

        if (!httpd->background && !httpd->foreground)
                return;

        if (!httpd->background)
                httpd->background = 1;

        portnum = httpd->bindport ? httpd->bindport : httpd->defbindport;
        
        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)
                bozoerr(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)
                        bozowarn(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)
                bozoerr(httpd, 1, "could not find any addresses to bind");
        httpd->nsock = i;
        freeaddrinfo(r0);

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

        create_pidfile(httpd);

        bozowarn(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);
}

void
bozo_daemon_closefds(bozohttpd_t *httpd)
{
        int i;

        for (i = 0; i < httpd->nsock; i++)
                close(httpd->sock[i]);
}

static void
daemon_runchild(bozohttpd_t *httpd, int fd)
{
        httpd->request_times++;

        /* setup stdin/stdout/stderr */
        dup2(fd, 0);
        dup2(fd, 1);
        /*dup2(fd, 2);*/
        close(fd);
}

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

        bozowarn(httpd, "poll on fd %d pid %d revents %d: %s",
            httpd->fds[idx].fd, getpid(), httpd->fds[idx].revents,
            strerror(errno));
        bozowarn(httpd, "nsock = %d", httpd->nsock);
        close(httpd->sock[idx]);
        httpd->nsock--;
        bozowarn(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)
                                bozoerr(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, 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)
                                        bozoerr(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 */
                                bozowarn(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;
}

#endif /* NO_DAEMON_MODE */