12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025 |
- /*
- * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc.
- * 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.
- * 3. Neither the name of Silicon Graphics, Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * 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
- * HOLDERS AND 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 <string.h>
- #include <unistd.h>
- #include <time.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/socket.h>
- #include <sys/wait.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <pwd.h>
- #include "st.h"
- /******************************************************************
- * Server configuration parameters
- */
- /* Log files */
- #define PID_FILE "pid"
- #define ERRORS_FILE "errors"
- #define ACCESS_FILE "access"
- /* Default server port */
- #define SERV_PORT_DEFAULT 8000
- /* Socket listen queue size */
- #define LISTENQ_SIZE_DEFAULT 256
- /* Max number of listening sockets ("hardware virtual servers") */
- #define MAX_BIND_ADDRS 16
- /* Max number of "spare" threads per process per socket */
- #define MAX_WAIT_THREADS_DEFAULT 8
- /* Number of file descriptors needed to handle one client session */
- #define FD_PER_THREAD 2
- /* Access log buffer flushing interval (in seconds) */
- #define ACCLOG_FLUSH_INTERVAL 30
- /* Request read timeout (in seconds) */
- #define REQUEST_TIMEOUT 30
- /******************************************************************
- * Global data
- */
- struct socket_info {
- st_netfd_t nfd; /* Listening socket */
- char *addr; /* Bind address */
- unsigned int port; /* Port */
- int wait_threads; /* Number of threads waiting to accept */
- int busy_threads; /* Number of threads processing request */
- int rqst_count; /* Total number of processed requests */
- } srv_socket[MAX_BIND_ADDRS]; /* Array of listening sockets */
- static int sk_count = 0; /* Number of listening sockets */
- static int vp_count = 0; /* Number of server processes (VPs) */
- static pid_t *vp_pids; /* Array of VP pids */
- static int my_index = -1; /* Current process index */
- static pid_t my_pid = -1; /* Current process pid */
- static st_netfd_t sig_pipe[2]; /* Signal pipe */
- /*
- * Configuration flags/parameters
- */
- static int interactive_mode = 0;
- static int serialize_accept = 0;
- static int log_access = 0;
- static char *logdir = NULL;
- static char *username = NULL;
- static int listenq_size = LISTENQ_SIZE_DEFAULT;
- static int errfd = STDERR_FILENO;
- /*
- * Thread throttling parameters (all numbers are per listening socket).
- * Zero values mean use default.
- */
- static int max_threads = 0; /* Max number of threads */
- static int max_wait_threads = 0; /* Max number of "spare" threads */
- static int min_wait_threads = 2; /* Min number of "spare" threads */
- /******************************************************************
- * Useful macros
- */
- #ifndef INADDR_NONE
- #define INADDR_NONE 0xffffffff
- #endif
- #define SEC2USEC(s) ((s)*1000000LL)
- #define WAIT_THREADS(i) (srv_socket[i].wait_threads)
- #define BUSY_THREADS(i) (srv_socket[i].busy_threads)
- #define TOTAL_THREADS(i) (WAIT_THREADS(i) + BUSY_THREADS(i))
- #define RQST_COUNT(i) (srv_socket[i].rqst_count)
- /******************************************************************
- * Forward declarations
- */
- static void usage(const char *progname);
- static void parse_arguments(int argc, char *argv[]);
- static void start_daemon(void);
- static void set_thread_throttling(void);
- static void create_listeners(void);
- static void change_user(void);
- static void open_log_files(void);
- static void start_processes(void);
- static void wdog_sighandler(int signo);
- static void child_sighandler(int signo);
- static void install_sighandlers(void);
- static void start_threads(void);
- static void *process_signals(void *arg);
- static void *flush_acclog_buffer(void *arg);
- static void *handle_connections(void *arg);
- static void dump_server_info(void);
- static void Signal(int sig, void (*handler)(int));
- static int cpu_count(void);
- extern void handle_session(long srv_socket_index, st_netfd_t cli_nfd);
- extern void load_configs(void);
- extern void logbuf_open(void);
- extern void logbuf_flush(void);
- extern void logbuf_close(void);
- /* Error reporting functions defined in the error.c file */
- extern void err_sys_report(int fd, const char *fmt, ...);
- extern void err_sys_quit(int fd, const char *fmt, ...);
- extern void err_sys_dump(int fd, const char *fmt, ...);
- extern void err_report(int fd, const char *fmt, ...);
- extern void err_quit(int fd, const char *fmt, ...);
- /*
- * General server example: accept a client connection and do something.
- * This program just outputs a short HTML page, but can be easily adapted
- * to do other things.
- *
- * This server creates a constant number of processes ("virtual processors"
- * or VPs) and replaces them when they die. Each virtual processor manages
- * its own independent set of state threads (STs), the number of which varies
- * with load against the server. Each state thread listens to exactly one
- * listening socket. The initial process becomes the watchdog, waiting for
- * children (VPs) to die or for a signal requesting termination or restart.
- * Upon receiving a restart signal (SIGHUP), all VPs close and then reopen
- * log files and reload configuration. All currently active connections remain
- * active. It is assumed that new configuration affects only request
- * processing and not the general server parameters such as number of VPs,
- * thread limits, bind addresses, etc. Those are specified as command line
- * arguments, so the server has to be stopped and then started again in order
- * to change them.
- *
- * Each state thread loops processing connections from a single listening
- * socket. Only one ST runs on a VP at a time, and VPs do not share memory,
- * so no mutual exclusion locking is necessary on any data, and the entire
- * server is free to use all the static variables and non-reentrant library
- * functions it wants, greatly simplifying programming and debugging and
- * increasing performance (for example, it is safe to ++ and -- all global
- * counters or call inet_ntoa(3) without any mutexes). The current thread on
- * each VP maintains equilibrium on that VP, starting a new thread or
- * terminating itself if the number of spare threads exceeds the lower or
- * upper limit.
- *
- * All I/O operations on sockets must use the State Thread library's I/O
- * functions because only those functions prevent blocking of the entire VP
- * process and perform state thread scheduling.
- */
- int main(int argc, char *argv[])
- {
- /* Parse command-line options */
- parse_arguments(argc, argv);
- /* Allocate array of server pids */
- if ((vp_pids = calloc(vp_count, sizeof(pid_t))) == NULL)
- err_sys_quit(errfd, "ERROR: calloc failed");
- /* Start the daemon */
- if (!interactive_mode)
- start_daemon();
- /* Initialize the ST library */
- if (st_init() < 0)
- err_sys_quit(errfd, "ERROR: initialization failed: st_init");
- /* Set thread throttling parameters */
- set_thread_throttling();
- /* Create listening sockets */
- create_listeners();
- /* Change the user */
- if (username)
- change_user();
- /* Open log files */
- open_log_files();
- /* Start server processes (VPs) */
- start_processes();
- /* Turn time caching on */
- st_timecache_set(1);
- /* Install signal handlers */
- install_sighandlers();
- /* Load configuration from config files */
- load_configs();
- /* Start all threads */
- start_threads();
- /* Become a signal processing thread */
- process_signals(NULL);
- /* NOTREACHED */
- return 1;
- }
- /******************************************************************/
- static void usage(const char *progname)
- {
- fprintf(stderr, "Usage: %s -l <log_directory> [<options>]\n\n"
- "Possible options:\n\n"
- "\t-b <host>:<port> Bind to specified address. Multiple"
- " addresses\n"
- "\t are permitted.\n"
- "\t-p <num_processes> Create specified number of processes.\n"
- "\t-t <min_thr>:<max_thr> Specify thread limits per listening"
- " socket\n"
- "\t across all processes.\n"
- "\t-u <user> Change server's user id to specified"
- " value.\n"
- "\t-q <backlog> Set max length of pending connections"
- " queue.\n"
- "\t-a Enable access logging.\n"
- "\t-i Run in interactive mode.\n"
- "\t-S Serialize all accept() calls.\n"
- "\t-h Print this message.\n",
- progname);
- exit(1);
- }
- /******************************************************************/
- static void parse_arguments(int argc, char *argv[])
- {
- extern char *optarg;
- int opt;
- char *c;
- while ((opt = getopt(argc, argv, "b:p:l:t:u:q:aiSh")) != EOF) {
- switch (opt) {
- case 'b':
- if (sk_count >= MAX_BIND_ADDRS)
- err_quit(errfd, "ERROR: max number of bind addresses (%d) exceeded",
- MAX_BIND_ADDRS);
- if ((c = strdup(optarg)) == NULL)
- err_sys_quit(errfd, "ERROR: strdup");
- srv_socket[sk_count++].addr = c;
- break;
- case 'p':
- vp_count = atoi(optarg);
- if (vp_count < 1)
- err_quit(errfd, "ERROR: invalid number of processes: %s", optarg);
- break;
- case 'l':
- logdir = optarg;
- break;
- case 't':
- max_wait_threads = (int) strtol(optarg, &c, 10);
- if (*c++ == ':')
- max_threads = atoi(c);
- if (max_wait_threads < 0 || max_threads < 0)
- err_quit(errfd, "ERROR: invalid number of threads: %s", optarg);
- break;
- case 'u':
- username = optarg;
- break;
- case 'q':
- listenq_size = atoi(optarg);
- if (listenq_size < 1)
- err_quit(errfd, "ERROR: invalid listen queue size: %s", optarg);
- break;
- case 'a':
- log_access = 1;
- break;
- case 'i':
- interactive_mode = 1;
- break;
- case 'S':
- /*
- * Serialization decision is tricky on some platforms. For example,
- * Solaris 2.6 and above has kernel sockets implementation, so supposedly
- * there is no need for serialization. The ST library may be compiled
- * on one OS version, but used on another, so the need for serialization
- * should be determined at run time by the application. Since it's just
- * an example, the serialization decision is left up to user.
- * Only on platforms where the serialization is never needed on any OS
- * version st_netfd_serialize_accept() is a no-op.
- */
- serialize_accept = 1;
- break;
- case 'h':
- case '?':
- usage(argv[0]);
- }
- }
- if (logdir == NULL && !interactive_mode) {
- err_report(errfd, "ERROR: logging directory is required\n");
- usage(argv[0]);
- }
- if (getuid() == 0 && username == NULL)
- err_report(errfd, "WARNING: running as super-user!");
- if (vp_count == 0 && (vp_count = cpu_count()) < 1)
- vp_count = 1;
- if (sk_count == 0) {
- sk_count = 1;
- srv_socket[0].addr = "0.0.0.0";
- }
- }
- /******************************************************************/
- static void start_daemon(void)
- {
- pid_t pid;
- /* Start forking */
- if ((pid = fork()) < 0)
- err_sys_quit(errfd, "ERROR: fork");
- if (pid > 0)
- exit(0); /* parent */
- /* First child process */
- setsid(); /* become session leader */
- if ((pid = fork()) < 0)
- err_sys_quit(errfd, "ERROR: fork");
- if (pid > 0) /* first child */
- exit(0);
- umask(022);
- if (chdir(logdir) < 0)
- err_sys_quit(errfd, "ERROR: can't change directory to %s: chdir", logdir);
- }
- /******************************************************************
- * For simplicity, the minimal size of thread pool is considered
- * as a maximum number of spare threads (max_wait_threads) that
- * will be created upon server startup. The pool size can grow up
- * to the max_threads value. Note that this is a per listening
- * socket limit. It is also possible to limit the total number of
- * threads for all sockets rather than impose a per socket limit.
- */
- static void set_thread_throttling(void)
- {
- /*
- * Calculate total values across all processes.
- * All numbers are per listening socket.
- */
- if (max_wait_threads == 0)
- max_wait_threads = MAX_WAIT_THREADS_DEFAULT * vp_count;
- /* Assuming that each client session needs FD_PER_THREAD file descriptors */
- if (max_threads == 0)
- max_threads = (st_getfdlimit() * vp_count) / FD_PER_THREAD / sk_count;
- if (max_wait_threads > max_threads)
- max_wait_threads = max_threads;
- /*
- * Now calculate per-process values.
- */
- if (max_wait_threads % vp_count)
- max_wait_threads = max_wait_threads / vp_count + 1;
- else
- max_wait_threads = max_wait_threads / vp_count;
- if (max_threads % vp_count)
- max_threads = max_threads / vp_count + 1;
- else
- max_threads = max_threads / vp_count;
- if (min_wait_threads > max_wait_threads)
- min_wait_threads = max_wait_threads;
- }
- /******************************************************************/
- static void create_listeners(void)
- {
- int i, n, sock;
- char *c;
- struct sockaddr_in serv_addr;
- struct hostent *hp;
- unsigned short port;
- for (i = 0; i < sk_count; i++) {
- port = 0;
- if ((c = strchr(srv_socket[i].addr, ':')) != NULL) {
- *c++ = '\0';
- port = (unsigned short) atoi(c);
- }
- if (srv_socket[i].addr[0] == '\0')
- srv_socket[i].addr = "0.0.0.0";
- if (port == 0)
- port = SERV_PORT_DEFAULT;
- /* Create server socket */
- if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
- err_sys_quit(errfd, "ERROR: can't create socket: socket");
- n = 1;
- if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&n, sizeof(n)) < 0)
- err_sys_quit(errfd, "ERROR: can't set SO_REUSEADDR: setsockopt");
- memset(&serv_addr, 0, sizeof(serv_addr));
- serv_addr.sin_family = AF_INET;
- serv_addr.sin_port = htons(port);
- serv_addr.sin_addr.s_addr = inet_addr(srv_socket[i].addr);
- if (serv_addr.sin_addr.s_addr == INADDR_NONE) {
- /* not dotted-decimal */
- if ((hp = gethostbyname(srv_socket[i].addr)) == NULL)
- err_quit(errfd, "ERROR: can't resolve address: %s",
- srv_socket[i].addr);
- memcpy(&serv_addr.sin_addr, hp->h_addr, hp->h_length);
- }
- srv_socket[i].port = port;
- /* Do bind and listen */
- if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
- err_sys_quit(errfd, "ERROR: can't bind to address %s, port %hu",
- srv_socket[i].addr, port);
- if (listen(sock, listenq_size) < 0)
- err_sys_quit(errfd, "ERROR: listen");
- /* Create file descriptor object from OS socket */
- if ((srv_socket[i].nfd = st_netfd_open_socket(sock)) == NULL)
- err_sys_quit(errfd, "ERROR: st_netfd_open_socket");
- /*
- * On some platforms (e.g. IRIX, Linux) accept() serialization is never
- * needed for any OS version. In that case st_netfd_serialize_accept()
- * is just a no-op. Also see the comment above.
- */
- if (serialize_accept && st_netfd_serialize_accept(srv_socket[i].nfd) < 0)
- err_sys_quit(errfd, "ERROR: st_netfd_serialize_accept");
- }
- }
- /******************************************************************/
- static void change_user(void)
- {
- struct passwd *pw;
- if ((pw = getpwnam(username)) == NULL)
- err_quit(errfd, "ERROR: can't find user '%s': getpwnam failed", username);
- if (setgid(pw->pw_gid) < 0)
- err_sys_quit(errfd, "ERROR: can't change group id: setgid");
- if (setuid(pw->pw_uid) < 0)
- err_sys_quit(errfd, "ERROR: can't change user id: setuid");
- err_report(errfd, "INFO: changed process user id to '%s'", username);
- }
- /******************************************************************/
- static void open_log_files(void)
- {
- int fd;
- char str[32];
- if (interactive_mode)
- return;
- /* Open access log */
- if (log_access)
- logbuf_open();
- /* Open and write pid to pid file */
- if ((fd = open(PID_FILE, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0)
- err_sys_quit(errfd, "ERROR: can't open pid file: open");
- sprintf(str, "%d\n", (int)getpid());
- if (write(fd, str, strlen(str)) != strlen(str))
- err_sys_quit(errfd, "ERROR: can't write to pid file: write");
- close(fd);
- /* Open error log file */
- if ((fd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
- err_sys_quit(errfd, "ERROR: can't open error log file: open");
- errfd = fd;
- err_report(errfd, "INFO: starting the server...");
- }
- /******************************************************************/
- static void start_processes(void)
- {
- int i, status;
- pid_t pid;
- sigset_t mask, omask;
- if (interactive_mode) {
- my_index = 0;
- my_pid = getpid();
- return;
- }
- for (i = 0; i < vp_count; i++) {
- if ((pid = fork()) < 0) {
- err_sys_report(errfd, "ERROR: can't create process: fork");
- if (i == 0)
- exit(1);
- err_report(errfd, "WARN: started only %d processes out of %d", i,
- vp_count);
- vp_count = i;
- break;
- }
- if (pid == 0) {
- my_index = i;
- my_pid = getpid();
- /* Child returns to continue in main() */
- return;
- }
- vp_pids[i] = pid;
- }
- /*
- * Parent process becomes a "watchdog" and never returns to main().
- */
- /* Install signal handlers */
- Signal(SIGTERM, wdog_sighandler); /* terminate */
- Signal(SIGHUP, wdog_sighandler); /* restart */
- Signal(SIGUSR1, wdog_sighandler); /* dump info */
- /* Now go to sleep waiting for a child termination or a signal */
- for ( ; ; ) {
- if ((pid = wait(&status)) < 0) {
- if (errno == EINTR)
- continue;
- err_sys_quit(errfd, "ERROR: watchdog: wait");
- }
- /* Find index of the exited child */
- for (i = 0; i < vp_count; i++) {
- if (vp_pids[i] == pid)
- break;
- }
- /* Block signals while printing and forking */
- sigemptyset(&mask);
- sigaddset(&mask, SIGTERM);
- sigaddset(&mask, SIGHUP);
- sigaddset(&mask, SIGUSR1);
- sigprocmask(SIG_BLOCK, &mask, &omask);
- if (WIFEXITED(status))
- err_report(errfd, "WARN: watchdog: process %d (pid %d) exited"
- " with status %d", i, pid, WEXITSTATUS(status));
- else if (WIFSIGNALED(status))
- err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated"
- " by signal %d", i, pid, WTERMSIG(status));
- else if (WIFSTOPPED(status))
- err_report(errfd, "WARN: watchdog: process %d (pid %d) stopped"
- " by signal %d", i, pid, WSTOPSIG(status));
- else
- err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated:"
- " unknown termination reason", i, pid);
- /* Fork another VP */
- if ((pid = fork()) < 0) {
- err_sys_report(errfd, "ERROR: watchdog: can't create process: fork");
- } else if (pid == 0) {
- my_index = i;
- my_pid = getpid();
- /* Child returns to continue in main() */
- return;
- }
- vp_pids[i] = pid;
- /* Restore the signal mask */
- sigprocmask(SIG_SETMASK, &omask, NULL);
- }
- }
- /******************************************************************/
- static void wdog_sighandler(int signo)
- {
- int i, err;
- /* Save errno */
- err = errno;
- /* Forward the signal to all children */
- for (i = 0; i < vp_count; i++) {
- if (vp_pids[i] > 0)
- kill(vp_pids[i], signo);
- }
- /*
- * It is safe to do pretty much everything here because process is
- * sleeping in wait() which is async-safe.
- */
- switch (signo) {
- case SIGHUP:
- err_report(errfd, "INFO: watchdog: caught SIGHUP");
- /* Reopen log files - needed for log rotation */
- if (log_access) {
- logbuf_close();
- logbuf_open();
- }
- close(errfd);
- if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
- err_sys_quit(STDERR_FILENO, "ERROR: watchdog: open");
- break;
- case SIGTERM:
- /* Non-graceful termination */
- err_report(errfd, "INFO: watchdog: caught SIGTERM, terminating");
- unlink(PID_FILE);
- exit(0);
- case SIGUSR1:
- err_report(errfd, "INFO: watchdog: caught SIGUSR1");
- break;
- default:
- err_report(errfd, "INFO: watchdog: caught signal %d", signo);
- }
- /* Restore errno */
- errno = err;
- }
- /******************************************************************/
- static void install_sighandlers(void)
- {
- sigset_t mask;
- int p[2];
- /* Create signal pipe */
- if (pipe(p) < 0)
- err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
- " signal pipe: pipe", my_index, my_pid);
- if ((sig_pipe[0] = st_netfd_open(p[0])) == NULL ||
- (sig_pipe[1] = st_netfd_open(p[1])) == NULL)
- err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
- " signal pipe: st_netfd_open", my_index, my_pid);
- /* Install signal handlers */
- Signal(SIGTERM, child_sighandler); /* terminate */
- Signal(SIGHUP, child_sighandler); /* restart */
- Signal(SIGUSR1, child_sighandler); /* dump info */
- /* Unblock signals */
- sigemptyset(&mask);
- sigaddset(&mask, SIGTERM);
- sigaddset(&mask, SIGHUP);
- sigaddset(&mask, SIGUSR1);
- sigprocmask(SIG_UNBLOCK, &mask, NULL);
- }
- /******************************************************************/
- static void child_sighandler(int signo)
- {
- int err, fd;
- err = errno;
- fd = st_netfd_fileno(sig_pipe[1]);
- /* write() is async-safe */
- if (write(fd, &signo, sizeof(int)) != sizeof(int))
- err_sys_quit(errfd, "ERROR: process %d (pid %d): child's signal"
- " handler: write", my_index, my_pid);
- errno = err;
- }
- /******************************************************************
- * The "main" function of the signal processing thread.
- */
- /* ARGSUSED */
- static void *process_signals(void *arg)
- {
- int signo;
- for ( ; ; ) {
- /* Read the next signal from the signal pipe */
- if (st_read(sig_pipe[0], &signo, sizeof(int),
- ST_UTIME_NO_TIMEOUT) != sizeof(int))
- err_sys_quit(errfd, "ERROR: process %d (pid %d): signal processor:"
- " st_read", my_index, my_pid);
- switch (signo) {
- case SIGHUP:
- err_report(errfd, "INFO: process %d (pid %d): caught SIGHUP,"
- " reloading configuration", my_index, my_pid);
- if (interactive_mode) {
- load_configs();
- break;
- }
- /* Reopen log files - needed for log rotation */
- if (log_access) {
- logbuf_flush();
- logbuf_close();
- logbuf_open();
- }
- close(errfd);
- if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
- err_sys_quit(STDERR_FILENO, "ERROR: process %d (pid %d): signal"
- " processor: open", my_index, my_pid);
- /* Reload configuration */
- load_configs();
- break;
- case SIGTERM:
- /*
- * Terminate ungracefully since it is generally not known how long
- * it will take to gracefully complete all client sessions.
- */
- err_report(errfd, "INFO: process %d (pid %d): caught SIGTERM,"
- " terminating", my_index, my_pid);
- if (log_access)
- logbuf_flush();
- exit(0);
- case SIGUSR1:
- err_report(errfd, "INFO: process %d (pid %d): caught SIGUSR1",
- my_index, my_pid);
- /* Print server info to stderr */
- dump_server_info();
- break;
- default:
- err_report(errfd, "INFO: process %d (pid %d): caught signal %d",
- my_index, my_pid, signo);
- }
- }
- /* NOTREACHED */
- return NULL;
- }
- /******************************************************************
- * The "main" function of the access log flushing thread.
- */
- /* ARGSUSED */
- static void *flush_acclog_buffer(void *arg)
- {
- for ( ; ; ) {
- st_sleep(ACCLOG_FLUSH_INTERVAL);
- logbuf_flush();
- }
- /* NOTREACHED */
- return NULL;
- }
- /******************************************************************/
- static void start_threads(void)
- {
- long i, n;
- /* Create access log flushing thread */
- if (log_access && st_thread_create(flush_acclog_buffer, NULL, 0, 0) == NULL)
- err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
- " log flushing thread", my_index, my_pid);
- /* Create connections handling threads */
- for (i = 0; i < sk_count; i++) {
- err_report(errfd, "INFO: process %d (pid %d): starting %d threads"
- " on %s:%u", my_index, my_pid, max_wait_threads,
- srv_socket[i].addr, srv_socket[i].port);
- WAIT_THREADS(i) = 0;
- BUSY_THREADS(i) = 0;
- RQST_COUNT(i) = 0;
- for (n = 0; n < max_wait_threads; n++) {
- if (st_thread_create(handle_connections, (void *)i, 0, 0) != NULL)
- WAIT_THREADS(i)++;
- else
- err_sys_report(errfd, "ERROR: process %d (pid %d): can't create"
- " thread", my_index, my_pid);
- }
- if (WAIT_THREADS(i) == 0)
- exit(1);
- }
- }
- /******************************************************************/
- static void *handle_connections(void *arg)
- {
- st_netfd_t srv_nfd, cli_nfd;
- struct sockaddr_in from;
- int fromlen;
- long i = (long) arg;
- srv_nfd = srv_socket[i].nfd;
- fromlen = sizeof(from);
- while (WAIT_THREADS(i) <= max_wait_threads) {
- cli_nfd = st_accept(srv_nfd, (struct sockaddr *)&from, &fromlen,
- ST_UTIME_NO_TIMEOUT);
- if (cli_nfd == NULL) {
- err_sys_report(errfd, "ERROR: can't accept connection: st_accept");
- continue;
- }
- /* Save peer address, so we can retrieve it later */
- st_netfd_setspecific(cli_nfd, &from.sin_addr, NULL);
- WAIT_THREADS(i)--;
- BUSY_THREADS(i)++;
- if (WAIT_THREADS(i) < min_wait_threads && TOTAL_THREADS(i) < max_threads) {
- /* Create another spare thread */
- if (st_thread_create(handle_connections, (void *)i, 0, 0) != NULL)
- WAIT_THREADS(i)++;
- else
- err_sys_report(errfd, "ERROR: process %d (pid %d): can't create"
- " thread", my_index, my_pid);
- }
- handle_session(i, cli_nfd);
- st_netfd_close(cli_nfd);
- WAIT_THREADS(i)++;
- BUSY_THREADS(i)--;
- }
- WAIT_THREADS(i)--;
- return NULL;
- }
- /******************************************************************/
- static void dump_server_info(void)
- {
- char *buf;
- int i, len;
- if ((buf = malloc(sk_count * 512)) == NULL) {
- err_sys_report(errfd, "ERROR: malloc failed");
- return;
- }
- len = sprintf(buf, "\n\nProcess #%d (pid %d):\n", my_index, (int)my_pid);
- for (i = 0; i < sk_count; i++) {
- len += sprintf(buf + len, "\nListening Socket #%d:\n"
- "-------------------------\n"
- "Address %s:%u\n"
- "Thread limits (min/max) %d/%d\n"
- "Waiting threads %d\n"
- "Busy threads %d\n"
- "Requests served %d\n",
- i, srv_socket[i].addr, srv_socket[i].port,
- max_wait_threads, max_threads,
- WAIT_THREADS(i), BUSY_THREADS(i), RQST_COUNT(i));
- }
- write(STDERR_FILENO, buf, len);
- free(buf);
- }
- /******************************************************************
- * Stubs
- */
- /*
- * Session handling function stub. Just dumps small HTML page.
- */
- void handle_session(long srv_socket_index, st_netfd_t cli_nfd)
- {
- static char resp[] = "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n"
- "Connection: close\r\n\r\n<H2>It worked!</H2>\n";
- char buf[512];
- int n = sizeof(resp) - 1;
- struct in_addr *from = st_netfd_getspecific(cli_nfd);
- if (st_read(cli_nfd, buf, sizeof(buf), SEC2USEC(REQUEST_TIMEOUT)) < 0) {
- err_sys_report(errfd, "WARN: can't read request from %s: st_read",
- inet_ntoa(*from));
- return;
- }
- if (st_write(cli_nfd, resp, n, ST_UTIME_NO_TIMEOUT) != n) {
- err_sys_report(errfd, "WARN: can't write response to %s: st_write",
- inet_ntoa(*from));
- return;
- }
- RQST_COUNT(srv_socket_index)++;
- }
- /*
- * Configuration loading function stub.
- */
- void load_configs(void)
- {
- err_report(errfd, "INFO: process %d (pid %d): configuration loaded",
- my_index, my_pid);
- }
- /*
- * Buffered access logging methods.
- * Note that stdio functions (fopen(3), fprintf(3), fflush(3), etc.) cannot
- * be used if multiple VPs are created since these functions can flush buffer
- * at any point and thus write only partial log record to disk.
- * Also, it is completely safe for all threads of the same VP to write to
- * the same log buffer without any mutex protection (one buffer per VP, of
- * course).
- */
- void logbuf_open(void)
- {
- }
- void logbuf_flush(void)
- {
- }
- void logbuf_close(void)
- {
- }
- /******************************************************************
- * Small utility functions
- */
- static void Signal(int sig, void (*handler)(int))
- {
- struct sigaction sa;
- sa.sa_handler = handler;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = 0;
- sigaction(sig, &sa, NULL);
- }
- static int cpu_count(void)
- {
- int n;
- #if defined (_SC_NPROCESSORS_ONLN)
- n = (int) sysconf(_SC_NPROCESSORS_ONLN);
- #elif defined (_SC_NPROC_ONLN)
- n = (int) sysconf(_SC_NPROC_ONLN);
- #elif defined (HPUX)
- #include <sys/mpctl.h>
- n = mpctl(MPC_GETNUMSPUS, 0, 0);
- #else
- n = -1;
- errno = ENOSYS;
- #endif
- return n;
- }
- /******************************************************************/
|