Sun, 11 Jun 2023 15:53:55 +0200
fix non-blocking CGI handler and non-blocking SSL-IO
/* * 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"); (void)webserver_reconfig(); 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; } 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; } return EXIT_SUCCESS; }