src/server/daemon/main.c

Sun, 27 Nov 2022 13:33:30 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 27 Nov 2022 13:33:30 +0100
changeset 443
ef3c8a0e1fee
parent 434
ff576305ae6e
child 444
96d2ba2f28db
permissions
-rw-r--r--

improve daemon startup
parent will wait until daemon is started and returns error code if startup failed
daemon startup log messages will be printed by parent

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 Olaf Wintermann. 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 in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <poll.h>
#include <semaphore.h>

#include "../util/pool.h"
#include "../public/nsapi.h"
#include "../util/plist.h"
#include "../util/date.h"

#include <cx/string.h>

#include "webserver.h"
#include "log.h"
#include "httprequest.h"
#include "httplistener.h"
#include "srvctrl.h"

#include "configmanager.h"

#define LOG_THREAD_STACK_SIZE     32768
#define LOG_THREAD_MAX_POLL_FAILS 10
#define LOG_THREAD_READ_BUF       2048

static int std_out[2];
static int std_err[2];
static WSBool is_daemon;

void test() {
    time_t t = time(NULL);
    pool_handle_t *pool = pool_create();
    cxmutstr date = date_format_http(t, pool);
    printf("%s\n", date.ptr);
}


WSBool main_is_daemon(void) {
    return is_daemon;
}

/*
 * SIGUSR1: reload the configuration files
 */
void sig_usr1_reload(int sig) {
    log_ereport(LOG_INFORM, "sig reload");
    
    CfgManager mgr;
    if(cfgmgr_load_config(&mgr) != 0) {
        log_ereport(LOG_FAILURE, "cannot reload server.conf");
    } else {
        if(cfgmgr_apply_config(&mgr)) {
            log_ereport(LOG_FAILURE, "cannot reload config");
        }
    }
    
    // start newly created listeners
    start_all_listener();

    signal(SIGUSR1, sig_usr1_reload);
}

/*
 * SIGTERM: stop the server
 */
void sig_term(int sig) {
    webserver_shutdown();
    //exit(EXIT_SUCCESS);
}

static void set_pipe_nonblocking(int fd) {
    int flags = 0;
    flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
}

static char log_pipe_readbuf[LOG_THREAD_READ_BUF];
static char log_pipe_stdout_buf[LOG_THREAD_READ_BUF];
static char log_pipe_stderr_buf[LOG_THREAD_READ_BUF];
static size_t log_pipe_stdout_tmp_pos = 0;
static size_t log_pipe_stderr_tmp_pos = 0;

// pipe used for startup
// after fork, the parent process waits, until the daemon is fully started
// to get potential error and log messages
static int daemon_start_pipe[2];

// used for sending log messages from the daemon process to the parent process
// in the startup phase
static int parent_log_pipe[2];

static LogDup startup_log;

static void finish_daemon_startup(int status) {
    if(status != 0) {
        log_ereport(LOG_FAILURE, "daemon startup failed");
        if(status > 127) {
            status = 127;
        }
    }
    char s = (char)status;
    
    // notify parent process about startup status
    write(daemon_start_pipe[1], &s, 1);
    close(daemon_start_pipe[1]);
    
    // remove logdup
    log_remove_logdup(&startup_log);
    close(parent_log_pipe[0]);
    close(parent_log_pipe[1]);
}

static int log_pipe(const char *name, int fd, char *buf, size_t *pos) {
    ssize_t r = read(fd, log_pipe_readbuf, 2);
    if(r <= 0) {
        return r == 0 ? 0 : 1;
    }
    
    char *tmp = buf;
    int tmplen = *pos;
    
    int s = 0;
    for(int i=0;i<r;i++) {
        if(log_pipe_readbuf[i] == '\n') {
            if(tmplen + i-s > 0) {
                log_message(name, "%.*s%.*s", tmplen, tmp, i-s, log_pipe_readbuf + s);
            }
            tmplen = 0;
            *pos = 0;
            s = i+1;
        }
    }
    
    int remaining = r - s;
    if(tmplen + remaining >= LOG_THREAD_READ_BUF) {
        log_message(name, "%.*s%.*s", tmplen, tmp, remaining, log_pipe_readbuf + s);
        *pos = 0;
    } else if(remaining > 0) {
        memcpy(buf + *pos, log_pipe_readbuf + s, remaining);
        *pos += remaining;
    }
    
    return 0;
}

void* log_pipe_thread(void *data) {
    set_pipe_nonblocking(std_out[0]);
    set_pipe_nonblocking(std_err[0]);
    
    struct pollfd fds[2];
    fds[0].fd = std_out[0];
    fds[0].events = POLLIN;
    fds[1].fd = std_err[0];
    fds[1].events = POLLIN;
    
    int poll_fails = 0;
    for(;;) {
        if(poll(fds, 1, 1000000) < 0) {
            if(errno == EINTR) {
                continue;
            }
            log_ereport(LOG_FAILURE, "log thread poll failed: %s", strerror(errno));
            if(poll_fails++ > LOG_THREAD_MAX_POLL_FAILS) {
                break;
            }
        }
        
        // check stdout
        if(fds[0].revents & POLLIN) {
            if(log_pipe("stdout", fds[0].fd, log_pipe_stdout_buf, &log_pipe_stdout_tmp_pos)) {
                log_ereport(LOG_WARN, "log_pipe stdout failed");
                break;
            }
        }
        
        // check stderr
        if(fds[1].revents & POLLIN) {
            if(log_pipe("stderr", fds[0].fd, log_pipe_stderr_buf, &log_pipe_stderr_tmp_pos)) {
                log_ereport(LOG_WARN, "log_pipe stderr failed");
                break;
            }
        }
    }
    
    log_ereport(LOG_INFORM, "log thread end");

    return NULL;
}


// read (blocking) 1 byte from daemon_start_pipe and return the value
// this is used to wait in the parent process, until the daemon process
// is started
// the returning byte is used as the process return value
static int main_daemon_startup_wait(void) {
    // receive log messages from daemon
    char buf[2048];
    ssize_t r;
    while((r = read(parent_log_pipe[0], buf, 2048)) > 0) {
        ssize_t pos = 0;
        while(r > 0) {
            ssize_t w = write(STDOUT_FILENO, buf+pos, r-pos);
            pos += w;
            r -= w;
            break;
        }
    }
    // log pipe closed, daemon_start_pipe should contain the status code now
    
    char ret;
    if(read(daemon_start_pipe[0], &ret, 1) != 1) {
        return 255;
    }
    return ret;
}

static void startup_log_write(void *cookie, char *msg, size_t length) {
    write(parent_log_pipe[1], msg, length);
}

int main(int argc, char **argv) {
    //test();
    
    // if the -c parameter is specified, we don't create a daemon
    is_daemon = 1;
    for(int i=0;i<argc;i++) {
        char *p = argv[i];
        if(p[0] == '-' && p[1] == 'c') {
            is_daemon = 0;
            break;
        }
    }
    
    if(init_logging()) {
        fprintf(stderr, "OOM\n");
        return 1;
    }
    
    is_daemon = 1;
    if(is_daemon) {
        // initialize startup pipes
        if(pipe(daemon_start_pipe)) {
            perror("pipe");
            return EXIT_FAILURE;
        }
        if(pipe(parent_log_pipe)) {
            perror("pipe");
            return EXIT_FAILURE;
        }
        
        log_ereport(LOG_INFORM, "start daemon");
        
        // register parent process as LogDup to receive startup log messages
        // in the parent process, because stdout/stderr will be closed
        // after fork
        startup_log.write = startup_log_write;
        log_add_logdup(&startup_log);
        
        // create daemon
        pid_t pid = fork();
        if(pid < 0) {
            perror("fork");
            return EXIT_FAILURE;
        } else if(pid > 0) {
            close(daemon_start_pipe[1]);
            close(parent_log_pipe[1]);
            return main_daemon_startup_wait();
        }

        if(setsid() < 0) {
            fprintf(stderr, "setsid failed\n");
            return EXIT_FAILURE;
        }
        
        // close read-end of pipe in the daemon process
        close(daemon_start_pipe[0]);

        // stdio redirection
        // create pipes
        if(pipe(std_out) != 0) {
            perror("pipe");
            return EXIT_FAILURE;
        }
        if(pipe(std_err) != 0) {
            perror("pipe");
            return EXIT_FAILURE;
        }
        
        for(int i=0;i<3;i++) {
            close(i);
        }
        
        dup2(std_out[1], 1);
        //dup2(std_err[1], 2);
        close(std_out[1]);
        //close(std_err[1]);
        
        // set log thread stack size
        pthread_attr_t tattr;
        pthread_attr_init(&tattr);
        pthread_attr_setstacksize(&tattr, LOG_THREAD_STACK_SIZE);

        pthread_t tid;
        pthread_create(&tid, &tattr, log_pipe_thread, NULL);
    }

    pool_init(NULL, NULL, NULL);
    
    // add signal handler
    signal(SIGUSR1, sig_usr1_reload);
    signal(SIGTERM, sig_term);
    signal(SIGINT, sig_term);
    
    struct sigaction act;
    ZERO(&act, sizeof(struct sigaction));
    act.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &act, NULL);

    // start webserver
    log_ereport(LOG_INFORM, "startup");
    
    int status;
    status = webserver_init();
    if(status != 0) {
        log_ereport(LOG_FAILURE, "cannot initialize server.");
        return EXIT_FAILURE;
    }

    status = webserver_run();
    if(is_daemon) {
        finish_daemon_startup(status);
    }
    if(status != 0) {
        log_ereport(LOG_FAILURE, "cannot run server.");
        return EXIT_FAILURE;
    }
    
    if(srvctrl_wait()) {
        return EXIT_FAILURE;
    }
    
    /* TODO: join threads (or not?) */
/*
    while(1) {
        if(is_daemon) {
            fflush(stdout);
            fflush(stderr);
        }
        sleep(10000);
        if(0) {
            break;
        }
    }
*/

    return EXIT_SUCCESS;
}

mercurial