diff options
Diffstat (limited to 'APACHE_1_3_42/src/main/http_main.c')
-rw-r--r-- | APACHE_1_3_42/src/main/http_main.c | 8224 |
1 files changed, 8224 insertions, 0 deletions
diff --git a/APACHE_1_3_42/src/main/http_main.c b/APACHE_1_3_42/src/main/http_main.c new file mode 100644 index 0000000000..f68f3e2735 --- /dev/null +++ b/APACHE_1_3_42/src/main/http_main.c @@ -0,0 +1,8224 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * httpd.c: simple http daemon for answering WWW file requests + * + * + * 03-21-93 Rob McCool wrote original code (up to NCSA HTTPd 1.3) + * + * 03-06-95 blong + * changed server number for child-alone processes to 0 and changed name + * of processes + * + * 03-10-95 blong + * Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu) + * including set group before fork, and call gettime before to fork + * to set up libraries. + * + * 04-14-95 rst / rh + * Brandon's code snarfed from NCSA 1.4, but tinkered to work with the + * Apache server, and also to have child processes do accept() directly. + * + * April-July '95 rst + * Extensive rework for Apache. + */ + +#ifndef SHARED_CORE_BOOTSTRAP +#ifndef SHARED_CORE_TIESTATIC + +#ifdef SHARED_CORE +#define REALMAIN ap_main +int ap_main(int argc, char *argv[]); +#else +#define REALMAIN main +#endif + +#define CORE_PRIVATE + +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_protocol.h" /* for read_request */ +#include "http_request.h" /* for process_request */ +#include "http_conf_globals.h" +#include "http_core.h" /* for get_remote_host */ +#include "http_vhost.h" +#include "util_script.h" /* to force util_script.c linking */ +#include "util_uri.h" +#include "scoreboard.h" +#include "multithread.h" +#include <sys/stat.h> +#ifdef USE_SHMGET_SCOREBOARD +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#endif +#ifdef SecureWare +#include <sys/security.h> +#include <sys/audit.h> +#include <prot.h> +#endif +#ifdef WIN32 +#include "../os/win32/getopt.h" +#elif !defined(BEOS) && !defined(TPF) && !defined(NETWARE) && !defined(OS390) && !defined(CYGWIN) +#include <netinet/tcp.h> +#endif +#ifdef HAVE_BSTRING_H +#include <bstring.h> /* for IRIX, FD_SET calls bzero() */ +#endif +#ifdef HAVE_SET_DUMPABLE /* certain levels of Linux */ +#include <sys/prctl.h> +#endif + +#ifdef MULTITHREAD +/* special debug stuff -- PCS */ + +/* Set this non-zero if you are prepared to put up with more than one log entry per second */ +#define SEVERELY_VERBOSE 0 + + /* APD1() to APD5() are macros to help us debug. They can either + * log to the screen or the error_log file. In release builds, these + * macros do nothing. In debug builds, they send messages at priority + * "debug" to the error log file, or if DEBUG_TO_CONSOLE is defined, + * to the console. + */ + +# ifdef _DEBUG +# ifndef DEBUG_TO_CONSOLE +# define APD1(a) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a) +# define APD2(a,b) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b) +# define APD3(a,b,c) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b,c) +# define APD4(a,b,c,d) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b,c,d) +# define APD5(a,b,c,d,e) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b,c,d,e) +# else +# define APD1(a) printf("%s\n",a) +# define APD2(a,b) do { printf(a,b);putchar('\n'); } while(0); +# define APD3(a,b,c) do { printf(a,b,c);putchar('\n'); } while(0); +# define APD4(a,b,c,d) do { printf(a,b,c,d);putchar('\n'); } while(0); +# define APD5(a,b,c,d,e) do { printf(a,b,c,d,e);putchar('\n'); } while(0); +# endif +# else /* !_DEBUG */ +# define APD1(a) +# define APD2(a,b) +# define APD3(a,b,c) +# define APD4(a,b,c,d) +# define APD5(a,b,c,d,e) +# endif /* _DEBUG */ +#endif /* MULTITHREAD */ + +/* This next function is never used. It is here to ensure that if we + * make all the modules into shared libraries that core httpd still + * includes the full Apache API. Without this function the objects in + * main/util_script.c would not be linked into a minimal httpd. + * And the extra prototype is to make gcc -Wmissing-prototypes quiet. + */ +API_EXPORT(void) ap_force_library_loading(void); +API_EXPORT(void) ap_force_library_loading(void) { + ap_add_cgi_vars(NULL); +} + +#include "explain.h" + +#if !defined(max) +#define max(a,b) (a > b ? a : b) +#endif + +#ifdef WIN32 +#include "../os/win32/service.h" +#include "../os/win32/registry.h" +#define DEFAULTSERVICENAME "Apache" +#define PATHSEPARATOR '\\' +#else +#define PATHSEPARATOR '/' +#endif + + +#ifdef MINT +long _stksize = 32768; +#endif + +#ifdef USE_OS2_SCOREBOARD + /* Add MMAP style functionality to OS/2 */ +#define INCL_DOSMEMMGR +#define INCL_DOSEXCEPTIONS +#define INCL_DOSSEMAPHORES +#include <os2.h> +#include <umalloc.h> +#include <stdio.h> +caddr_t create_shared_heap(const char *, size_t); +caddr_t get_shared_heap(const char *); +#endif + +DEF_Explain + +/* Defining GPROF when compiling uses the moncontrol() function to + * disable gprof profiling in the parent, and enable it only for + * request processing in children (or in one_process mode). It's + * absolutely required to get useful gprof results under linux + * because the profile itimers and such are disabled across a + * fork(). It's probably useful elsewhere as well. + */ +#ifdef GPROF +extern void moncontrol(int); +#define MONCONTROL(x) moncontrol(x) +#else +#define MONCONTROL(x) +#endif + +#ifndef MULTITHREAD +/* this just need to be anything non-NULL */ +void *ap_dummy_mutex = &ap_dummy_mutex; +#endif + +/* + * Actual definitions of config globals... here because this is + * for the most part the only code that acts on 'em. (Hmmm... mod_main.c?) + */ +#ifdef NETWARE +BOOL ap_main_finished = FALSE; +unsigned int ap_thread_stack_size = 65536; +#endif +int ap_thread_count = 0; +API_VAR_EXPORT int ap_standalone=0; +API_VAR_EXPORT int ap_configtestonly=0; +int ap_docrootcheck=1; +API_VAR_EXPORT uid_t ap_user_id=0; +API_VAR_EXPORT char *ap_user_name=NULL; +API_VAR_EXPORT gid_t ap_group_id=0; +#ifdef MULTIPLE_GROUPS +gid_t group_id_list[NGROUPS_MAX]; +#endif +API_VAR_EXPORT int ap_max_requests_per_child=0; +API_VAR_EXPORT int ap_threads_per_child=0; +API_VAR_EXPORT int ap_excess_requests_per_child=0; +API_VAR_EXPORT char *ap_pid_fname=NULL; +API_VAR_EXPORT char *ap_scoreboard_fname=NULL; +API_VAR_EXPORT char *ap_lock_fname=NULL; +API_VAR_EXPORT char *ap_server_argv0=NULL; +API_VAR_EXPORT struct in_addr ap_bind_address={0}; +API_VAR_EXPORT int ap_daemons_to_start=0; +API_VAR_EXPORT int ap_daemons_min_free=0; +API_VAR_EXPORT int ap_daemons_max_free=0; +API_VAR_EXPORT int ap_daemons_limit=0; +API_VAR_EXPORT time_t ap_restart_time=0; +API_VAR_EXPORT int ap_suexec_enabled = 0; +API_VAR_EXPORT int ap_listenbacklog=0; +#ifdef AP_ENABLE_EXCEPTION_HOOK +int ap_exception_hook_enabled=0; +#endif + +struct accept_mutex_methods_s { + void (*child_init)(pool *p); + void (*init)(pool *p); + void (*on)(void); + void (*off)(void); + char *name; +}; +typedef struct accept_mutex_methods_s accept_mutex_methods_s; +accept_mutex_methods_s *amutex; + +#ifdef SO_ACCEPTFILTER +int ap_acceptfilter = +#ifdef AP_ACCEPTFILTER_OFF + 0; +#else + 1; +#endif +#endif + +int ap_dump_settings = 0; +API_VAR_EXPORT int ap_extended_status = 0; + +/* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxClients changes across SIGUSR1 restarts. We use this + * value to optimize routines that have to scan the entire scoreboard. + */ +static int max_daemons_limit = -1; + +/* + * During config time, listeners is treated as a NULL-terminated list. + * child_main previously would start at the beginning of the list each time + * through the loop, so a socket early on in the list could easily starve out + * sockets later on in the list. The solution is to start at the listener + * after the last one processed. But to do that fast/easily in child_main it's + * way more convenient for listeners to be a ring that loops back on itself. + * The routine setup_listeners() is called after config time to both open up + * the sockets and to turn the NULL-terminated list into a ring that loops back + * on itself. + * + * head_listener is used by each child to keep track of what they consider + * to be the "start" of the ring. It is also set by make_child to ensure + * that new children also don't starve any sockets. + * + * Note that listeners != NULL is ensured by read_config(). + */ +listen_rec *ap_listeners=NULL; +static listen_rec *head_listener; + +API_VAR_EXPORT char ap_server_root[MAX_STRING_LEN]=""; +API_VAR_EXPORT char ap_server_confname[MAX_STRING_LEN]=""; +API_VAR_EXPORT char ap_coredump_dir[MAX_STRING_LEN]=""; +int ap_coredump_dir_configured=0; + +API_VAR_EXPORT array_header *ap_server_pre_read_config=NULL; +API_VAR_EXPORT array_header *ap_server_post_read_config=NULL; +API_VAR_EXPORT array_header *ap_server_config_defines=NULL; + +/* *Non*-shared http_main globals... */ + +static server_rec *server_conf; +#ifndef NETWARE +static JMP_BUF APACHE_TLS jmpbuffer; +#endif +static int sd; +static fd_set listenfds; +static int listenmaxfd; +#ifndef NETWARE +static pid_t pgrp; +#endif + +/* one_process --- debugging mode variable; can be set from the command line + * with the -X flag. If set, this gets you the child_main loop running + * in the process which originally started up (no detach, no make_child), + * which is a pretty nice debugging environment. (You'll get a SIGHUP + * early in standalone_main; just continue through. This is the server + * trying to kill off any child processes which it might have lying + * around --- Apache doesn't keep track of their pids, it just sends + * SIGHUP to the process group, ignoring it in the root process. + * Continue through and you'll be fine.). + */ + +static int one_process = 0; + +static int do_detach = 1; + +/* set if timeouts are to be handled by the children and not by the parent. + * i.e. child_timeouts = !standalone || one_process. + */ +#ifndef NETWARE +static int child_timeouts; +#endif + +#ifdef DEBUG_SIGSTOP +int raise_sigstop_flags; +#endif + +#ifndef NO_OTHER_CHILD +/* used to maintain list of children which aren't part of the scoreboard */ +typedef struct other_child_rec other_child_rec; +struct other_child_rec { + other_child_rec *next; + int pid; + void (*maintenance) (int, void *, ap_wait_t); + void *data; + int write_fd; +}; +static other_child_rec *other_children; +#endif + +static pool *pglobal; /* Global pool */ +static pool *pconf; /* Pool for config stuff */ +static pool *plog; /* Pool for error-logging files */ +static pool *ptrans; /* Pool for per-transaction stuff */ +static pool *pchild; /* Pool for httpd child stuff */ +static pool *pmutex; /* Pool for accept mutex in child */ +static pool *pcommands; /* Pool for -C and -c switches */ + +#ifndef NETWARE +static int APACHE_TLS my_pid; /* it seems silly to call getpid all the time */ +#endif +#ifndef MULTITHREAD +static int my_child_num; +#endif + +#ifdef TPF +pid_t tpf_parent_pid; +int tpf_child = 0; +char tpf_server_name[INETD_SERVNAME_LENGTH+1]; +char tpf_mutex_key[TPF_MUTEX_KEY_SIZE]; +#endif /* TPF */ + +/* + * Shared memory scoreboard + */ +scoreboard *ap_scoreboard_image = NULL; + +/* + * Parent process local storage of child pids + */ +static int pid_table[HARD_SERVER_LIMIT]; + +/* + * Pieces for managing the contents of the Server response header + * field. + */ +static char *server_version = NULL; +static int version_locked = 0; + +/* Global, alas, so http_core can talk to us */ +enum server_token_type ap_server_tokens = SrvTk_FULL; + +/* Also global, for http_core and http_protocol */ +API_VAR_EXPORT int ap_protocol_req_check = 1; + +API_VAR_EXPORT int ap_change_shmem_uid = 0; + +/* + * Check the pid table to see if the actual pid exists + */ + +static int in_pid_table(int pid) { + int i; + for (i = 0; i < HARD_SERVER_LIMIT; i++) { + if (pid_table[i] == pid) { + return 1; + } + } + return 0; +} + +static void set_pid_table(int pid) { + int i; + for (i = 0; i < HARD_SERVER_LIMIT; i++) { + if (pid_table[i] == 0) { + pid_table[i] = pid; + break; + } + } + /* NOTE: Error detection?? */ +} + +static void unset_pid_table(int pid) { + int i; + for (i = 0; i < HARD_SERVER_LIMIT; i++) { + if (pid_table[i] == pid) { + pid_table[i] = 0; + break; + } + } +} + +/* + * This routine is called when the pconf pool is vacuumed. It resets the + * server version string to a known value and [re]enables modifications + * (which are disabled by configuration completion). + */ +static void reset_version(void *dummy) +{ + version_locked = 0; + ap_server_tokens = SrvTk_FULL; + server_version = NULL; +} + +API_EXPORT(const char *) ap_get_server_version(void) +{ + return (server_version ? server_version : SERVER_BASEVERSION); +} + +API_EXPORT(void) ap_add_version_component(const char *component) +{ + if (! version_locked) { + /* + * If the version string is null, register our cleanup to reset the + * pointer on pool destruction. We also know that, if NULL, + * we are adding the original SERVER_BASEVERSION string. + */ + if (server_version == NULL) { + ap_register_cleanup(pconf, NULL, (void (*)(void *))reset_version, + ap_null_cleanup); + server_version = ap_pstrdup(pconf, component); + } + else { + /* + * Tack the given component identifier to the end of + * the existing string. + */ + server_version = ap_pstrcat(pconf, server_version, " ", + component, NULL); + } + } +} + +/* + * This routine adds the real server base identity to the version string, + * and then locks out changes until the next reconfig. + */ +static void ap_set_version(void) +{ + if (ap_server_tokens == SrvTk_PRODUCT_ONLY) { + ap_add_version_component(SERVER_PRODUCT); + } + else if (ap_server_tokens == SrvTk_MIN) { + ap_add_version_component(SERVER_BASEVERSION); + } + else { + ap_add_version_component(SERVER_BASEVERSION " (" PLATFORM ")"); + } + /* + * Lock the server_version string if we're not displaying + * the full set of tokens + */ + if (ap_server_tokens != SrvTk_FULL) { + version_locked++; + } +} + +#ifndef NETWARE +static APACHE_TLS int volatile exit_after_unblock = 0; +#endif + +#ifdef GPROF +/* + * change directory for gprof to plop the gmon.out file + * configure in httpd.conf: + * GprofDir logs/ -> $ServerRoot/logs/gmon.out + * GprofDir logs/% -> $ServerRoot/logs/gprof.$pid/gmon.out + */ +static void chdir_for_gprof(void) +{ + core_server_config *sconf = + ap_get_module_config(server_conf->module_config, &core_module); + char *dir = sconf->gprof_dir; + + if(dir) { + char buf[512]; + int len = strlen(sconf->gprof_dir) - 1; + if(*(dir + len) == '%') { + dir[len] = '\0'; + ap_snprintf(buf, sizeof(buf), "%sgprof.%d", dir, (int)getpid()); + } + dir = ap_server_root_relative(pconf, buf[0] ? buf : dir); + if(mkdir(dir, 0755) < 0 && errno != EEXIST) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "gprof: error creating directory %s", dir); + } + } + else { + dir = ap_server_root_relative(pconf, "logs"); + } + + chdir(dir); +} +#else +#define chdir_for_gprof() +#endif + +/* a clean exit from a child with proper cleanup */ +static void clean_child_exit(int code) __attribute__ ((noreturn)); +static void clean_child_exit(int code) +{ +#ifdef TPF + /* run ptrans cleanups since TPF's sockets don't close upon exit */ + if (ptrans) { + ap_clear_pool(ptrans); + } +#endif /* TPF */ + + if (pchild) { + /* make sure the accept mutex is released before calling child + * exit hooks and cleanups... otherwise, modules can segfault + * in such code and, depending on the mutex mechanism, leave + * the server deadlocked... even if the module doesn't segfault, + * if it performs extensive processing it can temporarily prevent + * the server from accepting new connections + */ + ap_clear_pool(pmutex); + ap_child_exit_modules(pchild, server_conf); + ap_destroy_pool(pchild); + } + chdir_for_gprof(); + exit(code); +} + +/* + * Start of accept() mutex fluff: + * Concept: Each method has it's own distinct set of mutex functions, + * which it shoves in a nice struct for us. We then pick + * which struct to use. We tell Apache which methods we + * support via HAVE_FOO_SERIALIZED_ACCEPT. We can + * specify the default via USE_FOO_SERIALIZED_ACCEPT + * (this pre-1.3.21 builds which use that at the command- + * line during builds work as expected). Without a set + * method, we pick the 1st from the following order: + * uslock, pthread, sysvsem, fcntl, flock, os2sem, tpfcore and none. + */ + +#if defined(HAVE_FCNTL_SERIALIZED_ACCEPT) || defined(HAVE_FLOCK_SERIALIZED_ACCEPT) +static void expand_lock_fname(pool *p) +{ + /* XXXX possibly bogus cast */ + ap_lock_fname = ap_psprintf(p, "%s.%lu", + ap_server_root_relative(p, ap_lock_fname), (unsigned long)getpid()); +} +#endif + +#if defined (HAVE_USLOCK_SERIALIZED_ACCEPT) +#include <ulocks.h> +static ulock_t uslock = NULL; + +#define accept_mutex_child_init_uslock(x) + +static void accept_mutex_init_uslock(pool *p) +{ + ptrdiff_t old; + usptr_t *us; + + + /* default is 8, allocate enough for all the children plus the parent */ + if ((old = usconfig(CONF_INITUSERS, HARD_SERVER_LIMIT + 1)) == -1) { + perror("usconfig(CONF_INITUSERS)"); + exit(-1); + } + + if ((old = usconfig(CONF_LOCKTYPE, US_NODEBUG)) == -1) { + perror("usconfig(CONF_LOCKTYPE)"); + exit(-1); + } + if ((old = usconfig(CONF_ARENATYPE, US_SHAREDONLY)) == -1) { + perror("usconfig(CONF_ARENATYPE)"); + exit(-1); + } + if ((us = usinit("/dev/zero")) == NULL) { + perror("usinit"); + exit(-1); + } + + if ((uslock = usnewlock(us)) == NULL) { + perror("usnewlock"); + exit(-1); + } +} + +static void accept_mutex_on_uslock(void) +{ + switch (ussetlock(uslock)) { + case 1: + /* got lock */ + break; + case 0: + fprintf(stderr, "didn't get lock\n"); + clean_child_exit(APEXIT_CHILDFATAL); + case -1: + perror("ussetlock"); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +static void accept_mutex_off_uslock(void) +{ + if (usunsetlock(uslock) == -1) { + perror("usunsetlock"); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +accept_mutex_methods_s accept_mutex_uslock_s = { + NULL, + accept_mutex_init_uslock, + accept_mutex_on_uslock, + accept_mutex_off_uslock, + "uslock" +}; +#endif + +#if defined (HAVE_PTHREAD_SERIALIZED_ACCEPT) + +/* This code probably doesn't work on many platforms, but it is + * known to work really fast (at least on Solaris and AIX). + * Note that pthread mutexes are *NOT* released when a task + * dies ... the task has to free it itself. So we block signals and + * try to be nice about releasing the mutex. + */ + +#include <pthread.h> + +static pthread_mutex_t *accept_mutex = (void *)(caddr_t) -1; +static int have_accept_mutex; +static sigset_t accept_block_mask; +static sigset_t accept_previous_mask; + +static void accept_mutex_child_cleanup_pthread(void *foo) +{ + if (accept_mutex != (void *)(caddr_t)-1 + && have_accept_mutex) { + pthread_mutex_unlock(accept_mutex); + } +} + +static void accept_mutex_child_init_pthread(pool *p) +{ + ap_register_cleanup(p, NULL, accept_mutex_child_cleanup_pthread, ap_null_cleanup); +} + +static void accept_mutex_cleanup_pthread(void *foo) +{ + if (accept_mutex != (void *)(caddr_t)-1 + && munmap((caddr_t) accept_mutex, sizeof(*accept_mutex))) { + perror("munmap"); + } + accept_mutex = (void *)(caddr_t)-1; +} + +/* remove_sync_sigs() is from APR 0.9.4 + * + * It is invalid to block synchronous signals, as such signals must + * be delivered on the thread that generated the original error + * (e.g., invalid storage reference). Blocking them interferes + * with proper recovery. + */ +static void remove_sync_sigs(sigset_t *sig_mask) +{ +#ifdef SIGABRT + sigdelset(sig_mask, SIGABRT); +#endif +#ifdef SIGBUS + sigdelset(sig_mask, SIGBUS); +#endif +#ifdef SIGEMT + sigdelset(sig_mask, SIGEMT); +#endif +#ifdef SIGFPE + sigdelset(sig_mask, SIGFPE); +#endif +#ifdef SIGILL + sigdelset(sig_mask, SIGILL); +#endif +#ifdef SIGIOT + sigdelset(sig_mask, SIGIOT); +#endif +#ifdef SIGPIPE + sigdelset(sig_mask, SIGPIPE); +#endif +#ifdef SIGSEGV + sigdelset(sig_mask, SIGSEGV); +#endif +#ifdef SIGSYS + sigdelset(sig_mask, SIGSYS); +#endif +#ifdef SIGTRAP + sigdelset(sig_mask, SIGTRAP); +#endif + +/* APR logic to remove SIGUSR2 not copied */ +} + +static void accept_mutex_init_pthread(pool *p) +{ + pthread_mutexattr_t mattr; + int fd; + + fd = open("/dev/zero", O_RDWR); + if (fd == -1) { + perror("open(/dev/zero)"); + exit(APEXIT_INIT); + } + accept_mutex = (pthread_mutex_t *) mmap((caddr_t) 0, sizeof(*accept_mutex), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (accept_mutex == (void *) (caddr_t) - 1) { + perror("mmap /dev/zero"); + exit(APEXIT_INIT); + } + close(fd); + if ((errno = pthread_mutexattr_init(&mattr))) { + perror("pthread_mutexattr_init"); + exit(APEXIT_INIT); + } +#if !defined(CYGWIN) + /* Cygwin has problems with this pthread call claiming that these + * are "Invalid arguements", Stipe Tolj <tolj@wapme-systems.de> + */ + if ((errno = pthread_mutexattr_setpshared(&mattr, + PTHREAD_PROCESS_SHARED))) { + perror("pthread_mutexattr_setpshared"); + exit(APEXIT_INIT); + } +#endif + if ((errno = pthread_mutex_init(accept_mutex, &mattr))) { + perror("pthread_mutex_init"); + exit(APEXIT_INIT); + } + sigfillset(&accept_block_mask); + sigdelset(&accept_block_mask, SIGHUP); + sigdelset(&accept_block_mask, SIGTERM); + sigdelset(&accept_block_mask, SIGUSR1); + remove_sync_sigs(&accept_block_mask); + ap_register_cleanup(p, NULL, accept_mutex_cleanup_pthread, ap_null_cleanup); +} + +static void accept_mutex_on_pthread(void) +{ + int err; + + if (sigprocmask(SIG_BLOCK, &accept_block_mask, &accept_previous_mask)) { + perror("sigprocmask(SIG_BLOCK)"); + clean_child_exit(APEXIT_CHILDFATAL); + } + /* We need to block alarms here, since if we get killed *right* after + * locking the mutex, have_accept_mutex will not be set, and our + * child cleanup will not work. + */ + ap_block_alarms(); + if ((err = pthread_mutex_lock(accept_mutex))) { + errno = err; + perror("pthread_mutex_lock"); + clean_child_exit(APEXIT_CHILDFATAL); + } + have_accept_mutex = 1; + ap_unblock_alarms(); +} + +static void accept_mutex_off_pthread(void) +{ + int err; + + /* Have to block alarms here, or else we might have a double-unlock, which + * is possible with pthread mutexes, since they are designed to be fast, + * and hence not necessarily make checks for ownership or multiple unlocks. + */ + ap_block_alarms(); + if ((err = pthread_mutex_unlock(accept_mutex))) { + errno = err; + perror("pthread_mutex_unlock"); + clean_child_exit(APEXIT_CHILDFATAL); + } + have_accept_mutex = 0; + ap_unblock_alarms(); + if (sigprocmask(SIG_SETMASK, &accept_previous_mask, NULL)) { + perror("sigprocmask(SIG_SETMASK)"); + clean_child_exit(1); + } +} + +accept_mutex_methods_s accept_mutex_pthread_s = { + accept_mutex_child_init_pthread, + accept_mutex_init_pthread, + accept_mutex_on_pthread, + accept_mutex_off_pthread, + "pthread" +}; +#endif + +#if defined (HAVE_SYSVSEM_SERIALIZED_ACCEPT) + +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/sem.h> + +#ifdef NEED_UNION_SEMUN +union semun { + int val; + struct semid_ds *buf; + ushort *array; +}; + +#endif + +static int sem_id = -1; +static struct sembuf op_on; +static struct sembuf op_off; + +/* We get a random semaphore ... the lame sysv semaphore interface + * means we have to be sure to clean this up or else we'll leak + * semaphores. + */ +static void accept_mutex_cleanup_sysvsem(void *foo) +{ + union semun ick; + + if (sem_id < 0) + return; + /* this is ignored anyhow */ + ick.val = 0; + semctl(sem_id, 0, IPC_RMID, ick); +} + +#define accept_mutex_child_init_sysvsem(x) + +static void accept_mutex_init_sysvsem(pool *p) +{ + union semun ick; + struct semid_ds buf; + + /* acquire the semaphore */ + sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600); + if (sem_id < 0) { + perror("semget"); + exit(APEXIT_INIT); + } + ick.val = 1; + if (semctl(sem_id, 0, SETVAL, ick) < 0) { + perror("semctl(SETVAL)"); + exit(APEXIT_INIT); + } + if (!getuid()) { + /* restrict it to use only by the appropriate user_id ... not that this + * stops CGIs from acquiring it and dinking around with it. + */ + buf.sem_perm.uid = ap_user_id; + buf.sem_perm.gid = ap_group_id; + buf.sem_perm.mode = 0600; + ick.buf = &buf; + if (semctl(sem_id, 0, IPC_SET, ick) < 0) { + perror("semctl(IPC_SET)"); + exit(APEXIT_INIT); + } + } + ap_register_cleanup(p, NULL, accept_mutex_cleanup_sysvsem, ap_null_cleanup); + + /* pre-initialize these */ + op_on.sem_num = 0; + op_on.sem_op = -1; + op_on.sem_flg = SEM_UNDO; + op_off.sem_num = 0; + op_off.sem_op = 1; + op_off.sem_flg = SEM_UNDO; +} + +static void accept_mutex_on_sysvsem(void) +{ + while (semop(sem_id, &op_on, 1) < 0) { + if (errno != EINTR) { + perror("accept_mutex_on"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +} + +static void accept_mutex_off_sysvsem(void) +{ + while (semop(sem_id, &op_off, 1) < 0) { + if (errno != EINTR) { + perror("accept_mutex_off"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +} + +accept_mutex_methods_s accept_mutex_sysvsem_s = { + NULL, + accept_mutex_init_sysvsem, + accept_mutex_on_sysvsem, + accept_mutex_off_sysvsem, + "sysvsem" +}; +#endif + +#if defined(HAVE_FCNTL_SERIALIZED_ACCEPT) +static struct flock lock_it; +static struct flock unlock_it; + +static int lock_fd = -1; + +#define accept_mutex_child_init_fcntl(x) + +/* + * Initialize mutex lock. + * Must be safe to call this on a restart. + */ +static void accept_mutex_init_fcntl(pool *p) +{ + + lock_it.l_whence = SEEK_SET; /* from current point */ + lock_it.l_start = 0; /* -"- */ + lock_it.l_len = 0; /* until end of file */ + lock_it.l_type = F_WRLCK; /* set exclusive/write lock */ + lock_it.l_pid = 0; /* pid not actually interesting */ + unlock_it.l_whence = SEEK_SET; /* from current point */ + unlock_it.l_start = 0; /* -"- */ + unlock_it.l_len = 0; /* until end of file */ + unlock_it.l_type = F_UNLCK; /* set exclusive/write lock */ + unlock_it.l_pid = 0; /* pid not actually interesting */ + + expand_lock_fname(p); + lock_fd = ap_popenf_ex(p, ap_lock_fname, O_CREAT | O_WRONLY | O_EXCL, 0644, 1); + if (lock_fd == -1) { + perror("open"); + fprintf(stderr, "Cannot open lock file: %s\n", ap_lock_fname); + exit(APEXIT_INIT); + } + unlink(ap_lock_fname); +} + +static void accept_mutex_on_fcntl(void) +{ + int ret; + + while ((ret = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0 && errno == EINTR) { + /* nop */ + } + + if (ret < 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "fcntl: F_SETLKW: Error getting accept lock, exiting! " + "Perhaps you need to use the LockFile directive to place " + "your lock file on a local disk!"); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +static void accept_mutex_off_fcntl(void) +{ + int ret; + + while ((ret = fcntl(lock_fd, F_SETLKW, &unlock_it)) < 0 && errno == EINTR) { + /* nop */ + } + if (ret < 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "fcntl: F_SETLKW: Error freeing accept lock, exiting! " + "Perhaps you need to use the LockFile directive to place " + "your lock file on a local disk!"); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +accept_mutex_methods_s accept_mutex_fcntl_s = { + NULL, + accept_mutex_init_fcntl, + accept_mutex_on_fcntl, + accept_mutex_off_fcntl, + "fcntl" +}; +#endif + +#if defined(HAVE_FLOCK_SERIALIZED_ACCEPT) + +static int flock_fd = -1; + +static void accept_mutex_cleanup_flock(void *foo) +{ + unlink(ap_lock_fname); +} + +/* + * Initialize mutex lock. + * Done by each child at it's birth + */ +static void accept_mutex_child_init_flock(pool *p) +{ + + flock_fd = ap_popenf_ex(p, ap_lock_fname, O_WRONLY, 0600, 1); + if (flock_fd == -1) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "Child cannot open lock file: %s", ap_lock_fname); + clean_child_exit(APEXIT_CHILDINIT); + } +} + +/* + * Initialize mutex lock. + * Must be safe to call this on a restart. + */ +static void accept_mutex_init_flock(pool *p) +{ + expand_lock_fname(p); + unlink(ap_lock_fname); + flock_fd = ap_popenf_ex(p, ap_lock_fname, O_CREAT | O_WRONLY | O_EXCL, 0600, 1); + if (flock_fd == -1) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "Parent cannot open lock file: %s", ap_lock_fname); + exit(APEXIT_INIT); + } + ap_register_cleanup(p, NULL, accept_mutex_cleanup_flock, ap_null_cleanup); +} + +static void accept_mutex_on_flock(void) +{ + int ret; + + while ((ret = flock(flock_fd, LOCK_EX)) < 0 && errno == EINTR) + continue; + + if (ret < 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "flock: LOCK_EX: Error getting accept lock. Exiting!"); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +static void accept_mutex_off_flock(void) +{ + if (flock(flock_fd, LOCK_UN) < 0) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "flock: LOCK_UN: Error freeing accept lock. Exiting!"); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +accept_mutex_methods_s accept_mutex_flock_s = { + accept_mutex_child_init_flock, + accept_mutex_init_flock, + accept_mutex_on_flock, + accept_mutex_off_flock, + "flock" +}; +#endif + +#if defined(HAVE_OS2SEM_SERIALIZED_ACCEPT) + +static HMTX lock_sem = -1; + +static void accept_mutex_cleanup_os2sem(void *foo) +{ + DosReleaseMutexSem(lock_sem); + DosCloseMutexSem(lock_sem); +} + +/* + * Initialize mutex lock. + * Done by each child at it's birth + */ +static void accept_mutex_child_init_os2sem(pool *p) +{ + int rc = DosOpenMutexSem(NULL, &lock_sem); + + if (rc != 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf, + "Child cannot open lock semaphore, rc=%d", rc); + clean_child_exit(APEXIT_CHILDINIT); + } else { + ap_register_cleanup(p, NULL, accept_mutex_cleanup_os2sem, ap_null_cleanup); + } +} + +/* + * Initialize mutex lock. + * Must be safe to call this on a restart. + */ +static void accept_mutex_init_os2sem(pool *p) +{ + int rc = DosCreateMutexSem(NULL, &lock_sem, DC_SEM_SHARED, FALSE); + + if (rc != 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf, + "Parent cannot create lock semaphore, rc=%d", rc); + exit(APEXIT_INIT); + } + + ap_register_cleanup(p, NULL, accept_mutex_cleanup_os2sem, ap_null_cleanup); +} + +static void accept_mutex_on_os2sem(void) +{ + int rc = DosRequestMutexSem(lock_sem, SEM_INDEFINITE_WAIT); + + if (rc != 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf, + "OS2SEM: Error %d getting accept lock. Exiting!", rc); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +static void accept_mutex_off_os2sem(void) +{ + int rc = DosReleaseMutexSem(lock_sem); + + if (rc != 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf, + "OS2SEM: Error %d freeing accept lock. Exiting!", rc); + clean_child_exit(APEXIT_CHILDFATAL); + } +} + +accept_mutex_methods_s accept_mutex_os2sem_s = { + accept_mutex_child_init_os2sem, + accept_mutex_init_os2sem, + accept_mutex_on_os2sem, + accept_mutex_off_os2sem, + "os2sem" +}; +#endif + +#if defined(HAVE_TPF_CORE_SERIALIZED_ACCEPT) + +static int tpf_core_held; + +static void accept_mutex_cleanup_tpfcore(void *foo) +{ + if(tpf_core_held) + deqc(tpf_mutex_key, QUAL_S); +} + +#define accept_mutex_init_tpfcore(x) + +static void accept_mutex_child_init_tpfcore(pool *p) +{ + ap_register_cleanup(p, NULL, accept_mutex_cleanup_tpfcore, ap_null_cleanup); + tpf_core_held = 0; +} + +static void accept_mutex_on_tpfcore(void) +{ + enqc(tpf_mutex_key, ENQ_WAIT, 0, QUAL_S); + tpf_core_held = 1; + ap_check_signals(); +} + +static void accept_mutex_off_tpfcore(void) +{ + deqc(tpf_mutex_key, QUAL_S); + tpf_core_held = 0; + ap_check_signals(); +} + +accept_mutex_methods_s accept_mutex_tpfcore_s = { + accept_mutex_child_init_tpfcore, + NULL, + accept_mutex_on_tpfcore, + accept_mutex_off_tpfcore, + "tpfcore" +}; +#endif + +#ifdef HAVE_BEOS_SERIALIZED_ACCEPT +static sem_id _sem = -1; +static int locked = 0; + +static void accept_mutex_child_cleanup_beos(void *foo) +{ + if (_sem > 0 && locked) + release_sem(_sem); +} + +static void accept_mutex_child_init_beos(pool *p) +{ + ap_register_cleanup(p, NULL, accept_mutex_child_cleanup_beos, ap_null_cleanup); + locked = 0; +} + +static void accept_mutex_cleanup_beos(void *foo) +{ + if (_sem > 0) + delete_sem(_sem); +} + +static void accept_mutex_init_beos(pool *p) +{ + _sem = create_sem(1, "httpd_accept"); + if (_sem < 0) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf, + "Parent cannot create lock semaphore, sem=%ld", _sem); + exit(APEXIT_INIT); + } + + ap_register_cleanup(p, NULL, accept_mutex_cleanup_beos, ap_null_cleanup); +} +void accept_mutex_on_beos(void) +{ + if (locked == 0) { + if (acquire_sem(_sem) == B_OK) + locked = 1; + } +} + +static void accept_mutex_off_beos(void) +{ + if (locked == 1) { + if (release_sem(_sem) == B_OK) + locked = 0; + } +} + +accept_mutex_methods_s accept_mutex_beos_s = { + accept_mutex_child_init_beos, + accept_mutex_init_beos, + accept_mutex_on_beos, + accept_mutex_off_beos, + "beos_sem" +}; +#endif /* HAVE_BEOS_SERIALIZED_ACCEPT */ + + +/* Generally, HAVE_NONE_SERIALIZED_ACCEPT simply won't work but + * for testing purposes, here it is... */ +#if defined HAVE_NONE_SERIALIZED_ACCEPT +#if !defined(MULTITHREAD) +/* Multithreaded systems don't complete between processes for + * the sockets. */ +#define NO_SERIALIZED_ACCEPT +#endif + +accept_mutex_methods_s accept_mutex_none_s = { + NULL, + NULL, + NULL, + NULL, + "none" +}; +#endif + +#define AP_FPTR1(x,y) { if (x) ((* x)(y)); } +#define AP_FPTR0(x) { if (x) ((* x)()); } + +#define accept_mutex_child_init(x) AP_FPTR1(amutex->child_init,x) +#define accept_mutex_init(x) AP_FPTR1(amutex->init,x) +#define accept_mutex_off() AP_FPTR0(amutex->off) +#define accept_mutex_on() AP_FPTR0(amutex->on) + +char *ap_default_mutex_method(void) +{ + char *t; +#if defined USE_USLOCK_SERIALIZED_ACCEPT + t = "uslock"; +#elif defined USE_PTHREAD_SERIALIZED_ACCEPT + t = "pthread"; +#elif defined USE_SYSVSEM_SERIALIZED_ACCEPT + t = "sysvsem"; +#elif defined USE_FCNTL_SERIALIZED_ACCEPT + t = "fcntl"; +#elif defined USE_FLOCK_SERIALIZED_ACCEPT + t = "flock"; +#elif defined USE_OS2SEM_SERIALIZED_ACCEPT + t = "os2sem"; +#elif defined USE_TPF_CORE_SERIALIZED_ACCEPT + t = "tpfcore"; +#elif defined USE_BEOS_SERIALIZED_ACCEPT + t = "beos_sem"; +#elif defined USE_NONE_SERIALIZED_ACCEPT + t = "none"; +#else + t = "default"; +#endif +#if defined HAVE_USLOCK_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"uslock")))) + return "uslock"; +#endif +#if defined HAVE_PTHREAD_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"pthread")))) + return "pthread"; +#endif +#if defined HAVE_SYSVSEM_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"sysvsem")))) + return "sysvsem"; +#endif +#if defined HAVE_FCNTL_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"fcntl")))) + return "fcntl"; +#endif +#if defined HAVE_FLOCK_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"flock")))) + return "flock"; +#endif +#if defined HAVE_OS2SEM_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"os2sem")))) + return "os2sem"; +#endif +#if defined HAVE_TPF_CORE_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"tpfcore")))) + return "tpfcore"; +#endif +#if defined HAVE_BEOS_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"beos_sem")))) + return "beos_sem"; +#endif +#if defined HAVE_NONE_SERIALIZED_ACCEPT + if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"none")))) + return "none"; +#endif + + fprintf(stderr, "No default accept serialization known!!\n"); + exit(APEXIT_INIT); + /*NOTREACHED */ + return "unknown"; +} + +char *ap_init_mutex_method(char *t) +{ + if (!(strcasecmp(t,"default"))) + t = ap_default_mutex_method(); + +#if defined HAVE_USLOCK_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"uslock"))) { + amutex = &accept_mutex_uslock_s; + } else +#endif +#if defined HAVE_PTHREAD_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"pthread"))) { + amutex = &accept_mutex_pthread_s; + } else +#endif +#if defined HAVE_SYSVSEM_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"sysvsem"))) { + amutex = &accept_mutex_sysvsem_s; + } else +#endif +#if defined HAVE_FCNTL_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"fcntl"))) { + amutex = &accept_mutex_fcntl_s; + } else +#endif +#if defined HAVE_FLOCK_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"flock"))) { + amutex = &accept_mutex_flock_s; + } else +#endif +#if defined HAVE_OS2SEM_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"os2sem"))) { + amutex = &accept_mutex_os2sem_s; + } else +#endif +#if defined HAVE_TPF_CORE_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"tpfcore"))) { + amutex = &accept_mutex_tpfcore_s; + } else +#endif +#if defined HAVE_BEOS_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"beos_sem"))) { + amutex = &accept_mutex_beos_s; + } else +#endif +#if defined HAVE_NONE_SERIALIZED_ACCEPT + if (!(strcasecmp(t,"none"))) { + amutex = &accept_mutex_none_s; + } else +#endif + { +/* Ignore this directive on Windows */ +#ifndef WIN32 + if (server_conf) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "Requested serialization method '%s' not available",t); + exit(APEXIT_INIT); + } else { + fprintf(stderr, "Requested serialization method '%s' not available\n", t); + exit(APEXIT_INIT); + } +#endif + } + return NULL; +} + +/* On some architectures it's safe to do unserialized accept()s in the single + * Listen case. But it's never safe to do it in the case where there's + * multiple Listen statements. Define SINGLE_LISTEN_UNSERIALIZED_ACCEPT + * when it's safe in the single Listen case. + */ +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT +#define SAFE_ACCEPT(stmt) do {if(ap_listeners->next != ap_listeners) {stmt;}} while(0) +#else +#define SAFE_ACCEPT(stmt) do {stmt;} while(0) +#endif + +static void usage(char *bin) +{ + char pad[MAX_STRING_LEN]; + unsigned i; + + for (i = 0; i < strlen(bin); i++) + pad[i] = ' '; + pad[i] = '\0'; +#ifdef WIN32 + fprintf(stderr, "Usage: %s [-D name] [-d directory] [-f file] [-n service]\n", bin); + fprintf(stderr, " %s [-C \"directive\"] [-c \"directive\"] [-k signal]\n", pad); + fprintf(stderr, " %s [-v] [-V] [-h] [-l] [-L] [-S] [-t] [-T]\n", pad); +#else /* !WIN32 */ +#ifdef SHARED_CORE + fprintf(stderr, "Usage: %s [-R directory] [-D name] [-d directory] [-f file]\n", bin); +#else + fprintf(stderr, "Usage: %s [-D name] [-d directory] [-f file]\n", bin); +#endif + fprintf(stderr, " %s [-C \"directive\"] [-c \"directive\"]\n", pad); + fprintf(stderr, " %s [-v] [-V] [-h] [-l] [-L] [-S] [-t] [-T] [-F]\n", pad); + fprintf(stderr, "Options:\n"); +#ifdef SHARED_CORE + fprintf(stderr, " -R directory : specify an alternate location for shared object files\n"); +#endif +#endif /* !WIN32 */ + fprintf(stderr, " -D name : define a name for use in <IfDefine name> directives\n"); + fprintf(stderr, " -d directory : specify an alternate initial ServerRoot\n"); + fprintf(stderr, " -f file : specify an alternate ServerConfigFile\n"); + fprintf(stderr, " -C \"directive\" : process directive before reading config files\n"); + fprintf(stderr, " -c \"directive\" : process directive after reading config files\n"); + fprintf(stderr, " -v : show version number\n"); + fprintf(stderr, " -V : show compile settings\n"); + fprintf(stderr, " -h : list available command line options (this page)\n"); + fprintf(stderr, " -l : list compiled-in modules\n"); + fprintf(stderr, " -L : list available configuration directives\n"); + fprintf(stderr, " -S : show parsed settings (currently only vhost settings)\n"); +#ifdef NETWARE + fprintf(stderr, " -e : force the display of configuration file errors to the logger screen\n"); + fprintf(stderr, " -s : load Apache without a screen\n"); +#endif + fprintf(stderr, " -t : run syntax check for config files (with docroot check)\n"); + fprintf(stderr, " -T : run syntax check for config files (without docroot check)\n"); +#ifndef WIN32 + fprintf(stderr, " -F : run main process in foreground, for process supervisors\n"); +#endif +#ifdef WIN32 + fprintf(stderr, " -n name : name the Apache service for -k options below;\n"); + fprintf(stderr, " -k stop|shutdown : tell running Apache to shutdown\n"); + fprintf(stderr, " -k restart : tell running Apache to do a graceful restart\n"); + fprintf(stderr, " -k start : tell Apache to start\n"); + fprintf(stderr, " -k install | -i: install an Apache service\n"); + fprintf(stderr, " -k config : reconfigure an installed Apache service\n"); + fprintf(stderr, " -k uninstall | -u: uninstall an Apache service\n"); + fprintf(stderr, " -W service : after -k config|install; Apache starts after 'service'\n"); + fprintf(stderr, " -w : holds the window open for 30 seconds for fatal errors.\n"); +#endif + +#if defined(NETWARE) + clean_parent_exit(0); +#else + exit(1); +#endif +} + + + +#ifdef NETWARE +/* Thread Storage Data */ +typedef struct _TSD { + conn_rec* current_conn; + int alarms_blocked; + int alarm_pending; + request_rec* timeout_req; + char* timeout_name; + JMP_BUF jmpbuffer; + int exit_after_unblock; + void (*alarm_fn) (int); + unsigned int alarm_expiry_time; +} TSD; + +static TSD Tsd; + +void init_tsd() +{ + int *thread_ptr; + + memset(&Tsd, 0, sizeof(TSD)); + thread_ptr = __get_thread_data_area_ptr(); + *thread_ptr = (int) &Tsd; +} + +#define get_tsd TSD* tsd = (TSD*) Thread_Data_Area; +#define current_conn tsd->current_conn +#define alarms_blocked tsd->alarms_blocked +#define alarm_pending tsd->alarm_pending +#define timeout_req tsd->timeout_req +#define timeout_name tsd->timeout_name +#define jmpbuffer tsd->jmpbuffer +#define exit_after_unblock tsd->exit_after_unblock +#define alarm_fn tsd->alarm_fn +#define alarm_expiry_time tsd->alarm_expiry_time + +#else +/***************************************************************** + * + * Timeout handling. DISTINCTLY not thread-safe, but all this stuff + * has to change for threads anyway. Note that this code allows only + * one timeout in progress at a time... + */ + +static APACHE_TLS conn_rec *volatile current_conn; +static APACHE_TLS request_rec *volatile timeout_req; +static APACHE_TLS const char *volatile timeout_name = NULL; +static APACHE_TLS int volatile alarms_blocked = 0; +static APACHE_TLS int volatile alarm_pending = 0; +#endif + + +static void timeout(int sig) +{ + void *dirconf; +#ifdef NETWARE + get_tsd +#endif + if (alarms_blocked) { + alarm_pending = 1; + return; + } + if (exit_after_unblock) { + clean_child_exit(0); + } + + if (!current_conn) { + ap_longjmp(jmpbuffer, 1); + } + + if (timeout_req != NULL) + dirconf = timeout_req->per_dir_config; + else + dirconf = current_conn->server->lookup_defaults; + if (!current_conn->keptalive) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, + current_conn->server, "[client %s] %s timed out", + current_conn->remote_ip, + timeout_name ? timeout_name : "request"); + } + + if (timeout_req) { + /* Someone has asked for this transaction to just be aborted + * if it times out... + */ + request_rec *log_req = timeout_req; + request_rec *save_req = timeout_req; + + /* avoid looping... if ap_log_transaction started another + * timer (say via rfc1413.c) we could loop... + */ + timeout_req = NULL; + + while (log_req->main || log_req->prev) { +#ifdef NETWARE + ThreadSwitch(); +#endif + /* Get back to original request... */ + if (log_req->main) + log_req = log_req->main; + else + log_req = log_req->prev; + } + + if (!current_conn->keptalive) { + /* in some cases we come here before setting the time */ + if (log_req->request_time == 0) { + log_req->request_time = time(NULL); + } + ap_log_transaction(log_req); + } + + ap_bsetflag(save_req->connection->client, B_EOUT, 1); + ap_bclose(save_req->connection->client); + + if (!ap_standalone) + exit(0); + ap_longjmp(jmpbuffer, 1); + } + else { /* abort the connection */ + ap_bsetflag(current_conn->client, B_EOUT, 1); + ap_bclose(current_conn->client); + current_conn->aborted = 1; + } +} + + +/* + * These two called from alloc.c to protect its critical sections... + * Note that they can nest (as when destroying the sub_pools of a pool + * which is itself being cleared); we have to support that here. + */ + +API_EXPORT(void) ap_block_alarms(void) +{ +#ifdef NETWARE + get_tsd +#endif + ++alarms_blocked; +} + +API_EXPORT(void) ap_unblock_alarms(void) +{ +#ifdef NETWARE + get_tsd +#endif + --alarms_blocked; + if (alarms_blocked == 0) { + if (exit_after_unblock) { + /* We have a couple race conditions to deal with here, we can't + * allow a timeout that comes in this small interval to allow + * the child to jump back to the main loop. Instead we block + * alarms again, and then note that exit_after_unblock is + * being dealt with. We choose this way to solve this so that + * the common path through unblock_alarms() is really short. + */ + ++alarms_blocked; + exit_after_unblock = 0; + clean_child_exit(0); + } + if (alarm_pending) { + alarm_pending = 0; + timeout(0); + } + } +} + +#ifndef NETWARE +static APACHE_TLS void (*volatile alarm_fn) (int) = NULL; +#endif +#if defined(WIN32) || defined(CYGWIN_WINSOCK) +static APACHE_TLS unsigned int alarm_expiry_time = 0; +#endif /* WIN32 */ + +#if !defined(WIN32) && !defined(NETWARE) +static void alrm_handler(int sig) +{ +#ifdef TPF41 + signal(sig, exit); +#endif + if (alarm_fn) { + (*alarm_fn) (sig); + } +} +#endif + +API_EXPORT(unsigned int) ap_set_callback_and_alarm(void (*fn) (int), int x) +{ + unsigned int old; + +#if defined(WIN32) || defined(NETWARE) + time_t now = time(NULL); +#ifdef NETWARE + get_tsd +#endif + old = alarm_expiry_time; + + if (old) + old -= now; + if (x == 0) { + alarm_fn = NULL; + alarm_expiry_time = 0; + } + else { + alarm_fn = fn; + alarm_expiry_time = now + x; + } +#else + if (alarm_fn && x && fn != alarm_fn) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, NULL, + "ap_set_callback_and_alarm: possible nested timer!"); + } + alarm_fn = fn; +#ifndef OPTIMIZE_TIMEOUTS + old = alarm(x); +#else + if (child_timeouts) { + old = alarm(x); + } + else { + /* Just note the timeout in our scoreboard, no need to call the system. + * We also note that the virtual time has gone forward. + */ + ap_check_signals(); + old = ap_scoreboard_image->servers[my_child_num].timeout_len; + ap_scoreboard_image->servers[my_child_num].timeout_len = x; + ++ap_scoreboard_image->servers[my_child_num].cur_vtime; + } +#endif +#endif + return (old); +} + + +#if defined(WIN32) || defined(NETWARE) || defined(CYGWIN_WINSOCK) +API_EXPORT(int) ap_check_alarm(void) +{ +#ifdef NETWARE + get_tsd +#endif + if (alarm_expiry_time) { + unsigned int t; + + t = time(NULL); + if (t >= alarm_expiry_time) { + alarm_expiry_time = 0; + (*alarm_fn) (0); + return (-1); + } + else { + return (alarm_expiry_time - t); + } + } + else + return (0); +} +#endif /* WIN32 */ + +#ifdef TPF +API_EXPORT(int) ap_check_alarm(void) +{ + int i; + +#ifdef OPTIMIZE_TIMEOUTS + /* just pull the timeout from the scoreboard */ + ap_sync_scoreboard_image(); + i = ap_scoreboard_image->servers[my_child_num].timeout_len; +#else + i = ap_set_callback_and_alarm(alarm_fn, 3); /* determine time left */ + /* the 3 seconds is just an arbitrary amount of time to keep the alarm + from expiring before it is reset on this next line: */ + ap_set_callback_and_alarm(alarm_fn, i); /* restore time left */ +#endif + + return i; /* return the time left */ +} + +#endif /* TPF */ + +/* reset_timeout (request_rec *) resets the timeout in effect, + * as long as it hasn't expired already. + */ + +API_EXPORT(void) ap_reset_timeout(request_rec *r) +{ + int i; +#ifdef NETWARE + get_tsd +#endif + if (timeout_name) { /* timeout has been set */ + i = ap_set_callback_and_alarm(alarm_fn, r->server->timeout); + if (i == 0) /* timeout already expired, so set it back to 0 */ + ap_set_callback_and_alarm(alarm_fn, 0); + } +} + + + + +API_EXPORT(void) ap_keepalive_timeout(char *name, request_rec *r) +{ + unsigned int to; +#ifdef NETWARE + get_tsd +#endif + timeout_req = r; + timeout_name = name; + if (r->connection->keptalive) + to = r->server->keep_alive_timeout; + else + to = r->server->timeout; + ap_set_callback_and_alarm(timeout, to); +} + +API_EXPORT(void) ap_hard_timeout(char *name, request_rec *r) +{ +#ifdef NETWARE + get_tsd +#endif + timeout_req = r; + timeout_name = name; + ap_set_callback_and_alarm(timeout, r->server->timeout); +} + +API_EXPORT(void) ap_soft_timeout(char *name, request_rec *r) +{ +#ifdef NETWARE + get_tsd +#endif + timeout_name = name; + ap_set_callback_and_alarm(timeout, r->server->timeout); +} + +API_EXPORT(void) ap_kill_timeout(request_rec *dummy) +{ +#ifdef NETWARE + get_tsd +#endif + ap_check_signals(); + ap_set_callback_and_alarm(NULL, 0); + timeout_req = NULL; + timeout_name = NULL; +} + + +/* + * More machine-dependent networking gooo... on some systems, + * you've got to be *really* sure that all the packets are acknowledged + * before closing the connection, since the client will not be able + * to see the last response if their TCP buffer is flushed by a RST + * packet from us, which is what the server's TCP stack will send + * if it receives any request data after closing the connection. + * + * In an ideal world, this function would be accomplished by simply + * setting the socket option SO_LINGER and handling it within the + * server's TCP stack while the process continues on to the next request. + * Unfortunately, it seems that most (if not all) operating systems + * block the server process on close() when SO_LINGER is used. + * For those that don't, see USE_SO_LINGER below. For the rest, + * we have created a home-brew lingering_close. + * + * Many operating systems tend to block, puke, or otherwise mishandle + * calls to shutdown only half of the connection. You should define + * NO_LINGCLOSE in ap_config.h if such is the case for your system. + */ +#ifndef MAX_SECS_TO_LINGER +#define MAX_SECS_TO_LINGER 30 +#endif + +#ifdef USE_SO_LINGER +#define NO_LINGCLOSE /* The two lingering options are exclusive */ + +static void sock_enable_linger(int s) +{ + struct linger li; + + li.l_onoff = 1; + li.l_linger = MAX_SECS_TO_LINGER; + + if (setsockopt(s, SOL_SOCKET, SO_LINGER, + (char *) &li, sizeof(struct linger)) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, + "setsockopt: (SO_LINGER)"); + /* not a fatal error */ + } +} + +#else +#define sock_enable_linger(s) /* NOOP */ +#endif /* USE_SO_LINGER */ + +#ifndef NO_LINGCLOSE + +/* Special version of timeout for lingering_close */ + +static void lingerout(int sig) +{ +#ifdef NETWARE + get_tsd +#endif + if (alarms_blocked) { + alarm_pending = 1; + return; + } + + if (!current_conn) { + ap_longjmp(jmpbuffer, 1); + } + ap_bsetflag(current_conn->client, B_EOUT, 1); + current_conn->aborted = 1; +} + +static void linger_timeout(void) +{ +#ifdef NETWARE + get_tsd +#endif + timeout_name = "lingering close"; + ap_set_callback_and_alarm(lingerout, MAX_SECS_TO_LINGER); +} + +/* Since many clients will abort a connection instead of closing it, + * attempting to log an error message from this routine will only + * confuse the webmaster. There doesn't seem to be any portable way to + * distinguish between a dropped connection and something that might be + * worth logging. + */ +static void lingering_close(request_rec *r) +{ + char dummybuf[512]; + struct timeval tv; + fd_set lfds; + int select_rv; + int lsd; + + /* Prevent a slow-drip client from holding us here indefinitely */ + + linger_timeout(); + + /* Send any leftover data to the client, but never try to again */ + + if (ap_bflush(r->connection->client) == -1) { + ap_kill_timeout(r); + ap_bclose(r->connection->client); + return; + } + ap_bsetflag(r->connection->client, B_EOUT, 1); + + /* Close our half of the connection --- send the client a FIN */ + + lsd = r->connection->client->fd; + + if ((shutdown(lsd, 1) != 0) || r->connection->aborted) { + ap_kill_timeout(r); + ap_bclose(r->connection->client); + return; + } + + /* Set up to wait for readable data on socket... */ + + FD_ZERO(&lfds); + + /* Wait for readable data or error condition on socket; + * slurp up any data that arrives... We exit when we go for an + * interval of tv length without getting any more data, get an error + * from select(), get an error or EOF on a read, or the timer expires. + */ + + do { + /* We use a 2 second timeout because current (Feb 97) browsers + * fail to close a connection after the server closes it. Thus, + * to avoid keeping the child busy, we are only lingering long enough + * for a client that is actively sending data on a connection. + * This should be sufficient unless the connection is massively + * losing packets, in which case we might have missed the RST anyway. + * These parameters are reset on each pass, since they might be + * changed by select. + */ +#ifdef NETWARE + ThreadSwitch(); +#endif + + FD_SET(lsd, &lfds); + tv.tv_sec = 2; + tv.tv_usec = 0; + + select_rv = ap_select(lsd + 1, &lfds, NULL, NULL, &tv); + + } while ((select_rv > 0) && +#if defined(WIN32) || defined(NETWARE) + (recv(lsd, dummybuf, sizeof(dummybuf), 0) > 0)); +#else + (read(lsd, dummybuf, sizeof(dummybuf)) > 0)); +#endif + + /* Should now have seen final ack. Safe to finally kill socket */ + + ap_bclose(r->connection->client); + + ap_kill_timeout(r); +} +#endif /* ndef NO_LINGCLOSE */ + +/***************************************************************** + * dealing with other children + */ + +#ifndef NO_OTHER_CHILD +API_EXPORT(void) ap_register_other_child(int pid, + void (*maintenance) (int reason, void *, ap_wait_t status), + void *data, int write_fd) +{ + other_child_rec *ocr; + + ocr = ap_palloc(pconf, sizeof(*ocr)); + ocr->pid = pid; + ocr->maintenance = maintenance; + ocr->data = data; + ocr->write_fd = write_fd; + ocr->next = other_children; + other_children = ocr; +} + +/* note that since this can be called by a maintenance function while we're + * scanning the other_children list, all scanners should protect themself + * by loading ocr->next before calling any maintenance function. + */ +API_EXPORT(void) ap_unregister_other_child(void *data) +{ + other_child_rec **pocr, *nocr; + + for (pocr = &other_children; *pocr; pocr = &(*pocr)->next) { + if ((*pocr)->data == data) { + nocr = (*pocr)->next; + (*(*pocr)->maintenance) (OC_REASON_UNREGISTER, (*pocr)->data, (ap_wait_t)-1); + *pocr = nocr; + /* XXX: um, well we've just wasted some space in pconf ? */ + return; + } + } +} + +/* test to ensure that the write_fds are all still writable, otherwise + * invoke the maintenance functions as appropriate */ +static void probe_writable_fds(void) +{ + fd_set writable_fds; + int fd_max; + other_child_rec *ocr, *nocr; + struct timeval tv; + int rc; + + if (other_children == NULL) + return; + + fd_max = 0; + FD_ZERO(&writable_fds); + do { + for (ocr = other_children; ocr; ocr = ocr->next) { + if (ocr->write_fd == -1) + continue; + FD_SET(ocr->write_fd, &writable_fds); + if (ocr->write_fd > fd_max) { + fd_max = ocr->write_fd; + } + } + if (fd_max == 0) + return; + + tv.tv_sec = 0; + tv.tv_usec = 0; + rc = ap_select(fd_max + 1, NULL, &writable_fds, NULL, &tv); + } while (rc == -1 && errno == EINTR); + + if (rc == -1) { + /* XXX: uhh this could be really bad, we could have a bad file + * descriptor due to a bug in one of the maintenance routines */ + ap_log_unixerr("probe_writable_fds", "select", + "could not probe writable fds", server_conf); + return; + } + if (rc == 0) + return; + + for (ocr = other_children; ocr; ocr = nocr) { + nocr = ocr->next; + if (ocr->write_fd == -1) + continue; + if (FD_ISSET(ocr->write_fd, &writable_fds)) + continue; + (*ocr->maintenance) (OC_REASON_UNWRITABLE, ocr->data, (ap_wait_t)-1); + } +} + +/* possibly reap an other_child, return 0 if yes, -1 if not */ +static int reap_other_child(int pid, ap_wait_t status) +{ + other_child_rec *ocr, *nocr; + + for (ocr = other_children; ocr; ocr = nocr) { + nocr = ocr->next; + if (ocr->pid != pid) + continue; + ocr->pid = -1; + (*ocr->maintenance) (OC_REASON_DEATH, ocr->data, status); + return 0; + } + return -1; +} +#endif + +/***************************************************************** + * + * Dealing with the scoreboard... a lot of these variables are global + * only to avoid getting clobbered by the longjmp() that happens when + * a hard timeout expires... + * + * We begin with routines which deal with the file itself... + */ + +#ifdef MULTITHREAD +/* + * In the multithreaded mode, have multiple threads - not multiple + * processes that need to talk to each other. Just use a simple + * malloc. But let the routines that follow, think that you have + * shared memory (so they use memcpy etc.) + */ + +static void reinit_scoreboard(pool *p) +{ + ap_assert(!ap_scoreboard_image); + ap_scoreboard_image = (scoreboard *) malloc(SCOREBOARD_SIZE); + if (ap_scoreboard_image == NULL) { + fprintf(stderr, "Ouch! Out of memory reiniting scoreboard!\n"); + } + memset(ap_scoreboard_image, 0, SCOREBOARD_SIZE); +} + +void cleanup_scoreboard(void) +{ + ap_assert(ap_scoreboard_image); + free(ap_scoreboard_image); + ap_scoreboard_image = NULL; +} + +API_EXPORT(void) ap_sync_scoreboard_image(void) +{ +} + + +#else /* MULTITHREAD */ +#if defined(USE_OS2_SCOREBOARD) + +/* The next two routines are used to access shared memory under OS/2. */ +/* This requires EMX v09c to be installed. */ + +caddr_t create_shared_heap(const char *name, size_t size) +{ + ULONG rc; + void *mem; + Heap_t h; + + rc = DosAllocSharedMem(&mem, name, size, + PAG_COMMIT | PAG_READ | PAG_WRITE); + if (rc != 0) + return NULL; + h = _ucreate(mem, size, !_BLOCK_CLEAN, _HEAP_REGULAR | _HEAP_SHARED, + NULL, NULL); + if (h == NULL) + DosFreeMem(mem); + return (caddr_t) h; +} + +caddr_t get_shared_heap(const char *Name) +{ + + PVOID BaseAddress; /* Pointer to the base address of + the shared memory object */ + ULONG AttributeFlags; /* Flags describing characteristics + of the shared memory object */ + APIRET rc; /* Return code */ + + /* Request read and write access to */ + /* the shared memory object */ + AttributeFlags = PAG_WRITE | PAG_READ; + + rc = DosGetNamedSharedMem(&BaseAddress, Name, AttributeFlags); + + if (rc != 0) { + printf("DosGetNamedSharedMem error: return code = %ld", rc); + return 0; + } + + return BaseAddress; +} + +static void setup_shared_mem(pool *p) +{ + caddr_t m; + + int rc; + + m = (caddr_t) create_shared_heap("\\SHAREMEM\\SCOREBOARD", SCOREBOARD_SIZE); + if (m == 0) { + fprintf(stderr, "%s: Could not create OS/2 Shared memory pool.\n", + ap_server_argv0); + exit(APEXIT_INIT); + } + + rc = _uopen((Heap_t) m); + if (rc != 0) { + fprintf(stderr, + "%s: Could not uopen() newly created OS/2 Shared memory pool.\n", + ap_server_argv0); + } + ap_scoreboard_image = (scoreboard *) m; + ap_scoreboard_image->global.running_generation = 0; +} + +static void reopen_scoreboard(pool *p) +{ + caddr_t m; + int rc; + + m = (caddr_t) get_shared_heap("\\SHAREMEM\\SCOREBOARD"); + if (m == 0) { + fprintf(stderr, "%s: Could not find existing OS/2 Shared memory pool.\n", + ap_server_argv0); + exit(APEXIT_INIT); + } + + rc = _uopen((Heap_t) m); + ap_scoreboard_image = (scoreboard *) m; +} + +#elif defined(USE_POSIX_SCOREBOARD) +#include <sys/mman.h> +/* + * POSIX 1003.4 style + * + * Note 1: + * As of version 4.23A, shared memory in QNX must reside under /dev/shmem, + * where no subdirectories allowed. + * + * POSIX shm_open() and shm_unlink() will take care about this issue, + * but to avoid confusion, I suggest to redefine scoreboard file name + * in httpd.conf to cut "logs/" from it. With default setup actual name + * will be "/dev/shmem/logs.apache_status". + * + * If something went wrong and Apache did not unlinked this object upon + * exit, you can remove it manually, using "rm -f" command. + * + * Note 2: + * <sys/mman.h> in QNX defines MAP_ANON, but current implementation + * does NOT support BSD style anonymous mapping. So, the order of + * conditional compilation is important: + * this #ifdef section must be ABOVE the next one (BSD style). + * + * I tested this stuff and it works fine for me, but if it provides + * trouble for you, just comment out USE_MMAP_SCOREBOARD in QNX section + * of ap_config.h + * + * June 5, 1997, + * Igor N. Kovalenko -- infoh@mail.wplus.net + */ + +static void cleanup_shared_mem(void *d) +{ + shm_unlink(ap_scoreboard_fname); +} + +static void setup_shared_mem(pool *p) +{ + char buf[512]; + caddr_t m; + int fd; + + fd = shm_open(ap_scoreboard_fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fd == -1) { + ap_snprintf(buf, sizeof(buf), "%s: could not open(create) scoreboard", + ap_server_argv0); + perror(buf); + exit(APEXIT_INIT); + } + if (ltrunc(fd, (off_t) SCOREBOARD_SIZE, SEEK_SET) == -1) { + ap_snprintf(buf, sizeof(buf), "%s: could not ltrunc scoreboard", + ap_server_argv0); + perror(buf); + shm_unlink(ap_scoreboard_fname); + exit(APEXIT_INIT); + } + if ((m = (caddr_t) mmap((caddr_t) 0, + (size_t) SCOREBOARD_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, (off_t) 0)) == (caddr_t) - 1) { + ap_snprintf(buf, sizeof(buf), "%s: cannot mmap scoreboard", + ap_server_argv0); + perror(buf); + shm_unlink(ap_scoreboard_fname); + exit(APEXIT_INIT); + } + close(fd); + ap_register_cleanup(p, NULL, cleanup_shared_mem, ap_null_cleanup); + ap_scoreboard_image = (scoreboard *) m; + ap_scoreboard_image->global.running_generation = 0; +} + +static void reopen_scoreboard(pool *p) +{ +} + +#elif defined(USE_MMAP_SCOREBOARD) + +static void setup_shared_mem(pool *p) +{ + caddr_t m; + +#if defined(MAP_ANON) +/* BSD style */ +#ifdef CONVEXOS11 + /* + * 9-Aug-97 - Jeff Venters (venters@convex.hp.com) + * ConvexOS maps address space as follows: + * 0x00000000 - 0x7fffffff : Kernel + * 0x80000000 - 0xffffffff : User + * Start mmapped area 1GB above start of text. + * + * Also, the length requires a pointer as the actual length is + * returned (rounded up to a page boundary). + */ + { + unsigned len = SCOREBOARD_SIZE; + + m = mmap((caddr_t) 0xC0000000, &len, + PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, NOFD, 0); + } +#elif defined(MAP_TMPFILE) + { + char mfile[] = "/tmp/apache_shmem_XXXX"; + int fd = mkstemp(mfile); + if (fd == -1) { + perror("open"); + fprintf(stderr, "%s: Could not open %s\n", ap_server_argv0, mfile); + exit(APEXIT_INIT); + } + m = mmap((caddr_t) 0, SCOREBOARD_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (m == (caddr_t) - 1) { + perror("mmap"); + fprintf(stderr, "%s: Could not mmap %s\n", ap_server_argv0, mfile); + exit(APEXIT_INIT); + } + close(fd); + unlink(mfile); + } +#else + m = mmap((caddr_t) 0, SCOREBOARD_SIZE, + PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); +#endif + if (m == (caddr_t) - 1) { + perror("mmap"); + fprintf(stderr, "%s: Could not mmap memory\n", ap_server_argv0); + exit(APEXIT_INIT); + } +#else +/* Sun style */ + int fd; + + fd = open("/dev/zero", O_RDWR); + if (fd == -1) { + perror("open"); + fprintf(stderr, "%s: Could not open /dev/zero\n", ap_server_argv0); + exit(APEXIT_INIT); + } + m = mmap((caddr_t) 0, SCOREBOARD_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (m == (caddr_t) - 1) { + perror("mmap"); + fprintf(stderr, "%s: Could not mmap /dev/zero\n", ap_server_argv0); + exit(APEXIT_INIT); + } + close(fd); +#endif + ap_scoreboard_image = (scoreboard *) m; + ap_scoreboard_image->global.running_generation = 0; +} + +static void reopen_scoreboard(pool *p) +{ +} + +#elif defined(USE_SHMGET_SCOREBOARD) +static key_t shmkey = IPC_PRIVATE; +static int shmid = -1; + +static void setup_shared_mem(pool *p) +{ + struct shmid_ds shmbuf; +#ifdef MOVEBREAK + char *obrk; +#endif + + if ((shmid = shmget(shmkey, SCOREBOARD_SIZE, IPC_CREAT | SHM_R | SHM_W)) == -1) { +#ifdef LINUX + if (errno == ENOSYS) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf, + "Your kernel was built without CONFIG_SYSVIPC\n" + "%s: Please consult the Apache FAQ for details", + ap_server_argv0); + } +#endif + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "could not call shmget"); + exit(APEXIT_INIT); + } + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "created shared memory segment #%d", shmid); + +#ifdef MOVEBREAK + /* + * Some SysV systems place the shared segment WAY too close + * to the dynamic memory break point (sbrk(0)). This severely + * limits the use of malloc/sbrk in the program since sbrk will + * refuse to move past that point. + * + * To get around this, we move the break point "way up there", + * attach the segment and then move break back down. Ugly + */ + if ((obrk = sbrk(MOVEBREAK)) == (char *) -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "sbrk() could not move break"); + } +#endif + +#define BADSHMAT ((scoreboard *)(-1)) + if ((ap_scoreboard_image = (scoreboard *) shmat(shmid, 0, 0)) == BADSHMAT) { + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, "shmat error"); + /* + * We exit below, after we try to remove the segment + */ + } + /* only worry about permissions if we attached the segment + and we want/need to change the uid/gid */ + else if (ap_change_shmem_uid) { + if (shmctl(shmid, IPC_STAT, &shmbuf) != 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "shmctl() could not stat segment #%d", shmid); + } + else { + shmbuf.shm_perm.uid = ap_user_id; + shmbuf.shm_perm.gid = ap_group_id; + if (shmctl(shmid, IPC_SET, &shmbuf) != 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "shmctl() could not set segment #%d", shmid); + } + } + } + /* + * We must avoid leaving segments in the kernel's + * (small) tables. + */ + if (shmctl(shmid, IPC_RMID, NULL) != 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, + "shmctl: IPC_RMID: could not remove shared memory segment #%d", + shmid); + } + if (ap_scoreboard_image == BADSHMAT) /* now bailout */ + exit(APEXIT_INIT); + +#ifdef MOVEBREAK + if (obrk == (char *) -1) + return; /* nothing else to do */ + if (sbrk(-(MOVEBREAK)) == (char *) -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "sbrk() could not move break back"); + } +#endif + ap_scoreboard_image->global.running_generation = 0; +} + +static void reopen_scoreboard(pool *p) +{ +} + +#else +#define SCOREBOARD_FILE +static scoreboard _scoreboard_image; +static int scoreboard_fd = -1; + +/* XXX: things are seriously screwed if we ever have to do a partial + * read or write ... we could get a corrupted scoreboard + */ +static int force_write(int fd, void *buffer, int bufsz) +{ + int rv, orig_sz = bufsz; + + do { + rv = write(fd, buffer, bufsz); + if (rv > 0) { + buffer = (char *) buffer + rv; + bufsz -= rv; + } + } while ((rv > 0 && bufsz > 0) || (rv == -1 && errno == EINTR)); + + return rv < 0 ? rv : orig_sz - bufsz; +} + +static int force_read(int fd, void *buffer, int bufsz) +{ + int rv, orig_sz = bufsz; + + do { + rv = read(fd, buffer, bufsz); + if (rv > 0) { + buffer = (char *) buffer + rv; + bufsz -= rv; + } + } while ((rv > 0 && bufsz > 0) || (rv == -1 && errno == EINTR)); + + return rv < 0 ? rv : orig_sz - bufsz; +} + +static void cleanup_scoreboard_file(void *foo) +{ + unlink(ap_scoreboard_fname); +} + +void reopen_scoreboard(pool *p) +{ + if (scoreboard_fd != -1) + ap_pclosef(p, scoreboard_fd); + +#ifdef TPF + ap_scoreboard_fname = ap_server_root_relative(p, ap_scoreboard_fname); +#endif /* TPF */ + scoreboard_fd = ap_popenf_ex(p, ap_scoreboard_fname, O_CREAT | O_BINARY | O_RDWR, 0666, 1); + if (scoreboard_fd == -1) { + perror(ap_scoreboard_fname); + fprintf(stderr, "Cannot open scoreboard file:\n"); + clean_child_exit(1); + } +} +#endif + +/* Called by parent process */ +static void reinit_scoreboard(pool *p) +{ + int running_gen = 0; + if (ap_scoreboard_image) + running_gen = ap_scoreboard_image->global.running_generation; + +#ifndef SCOREBOARD_FILE + if (ap_scoreboard_image == NULL) { + setup_shared_mem(p); + } + memset(ap_scoreboard_image, 0, SCOREBOARD_SIZE); + ap_scoreboard_image->global.running_generation = running_gen; +#else + ap_scoreboard_image = &_scoreboard_image; + ap_scoreboard_fname = ap_server_root_relative(p, ap_scoreboard_fname); + + scoreboard_fd = ap_popenf_ex(p, ap_scoreboard_fname, O_CREAT | O_BINARY | O_RDWR, 0644, 1); + if (scoreboard_fd == -1) { + perror(ap_scoreboard_fname); + fprintf(stderr, "Cannot open scoreboard file:\n"); + exit(APEXIT_INIT); + } + ap_register_cleanup(p, NULL, cleanup_scoreboard_file, ap_null_cleanup); + + memset((char *) ap_scoreboard_image, 0, sizeof(*ap_scoreboard_image)); + ap_scoreboard_image->global.running_generation = running_gen; + force_write(scoreboard_fd, ap_scoreboard_image, sizeof(*ap_scoreboard_image)); +#endif +} + +/* Routines called to deal with the scoreboard image + * --- note that we do *not* need write locks, since update_child_status + * only updates a *single* record in place, and only one process writes to + * a given scoreboard slot at a time (either the child process owning that + * slot, or the parent, noting that the child has died). + * + * As a final note --- setting the score entry to getpid() is always safe, + * since when the parent is writing an entry, it's only noting SERVER_DEAD + * anyway. + */ + +ap_inline void ap_sync_scoreboard_image(void) +{ +#ifdef SCOREBOARD_FILE + lseek(scoreboard_fd, 0L, 0); + force_read(scoreboard_fd, ap_scoreboard_image, sizeof(*ap_scoreboard_image)); +#endif +} + +#endif /* MULTITHREAD */ + +API_EXPORT(int) ap_exists_scoreboard_image(void) +{ + return (ap_scoreboard_image ? 1 : 0); +} + +static ap_inline void put_scoreboard_info(int child_num, + short_score *new_score_rec) +{ +#ifdef SCOREBOARD_FILE + lseek(scoreboard_fd, (long) child_num * sizeof(short_score), 0); + force_write(scoreboard_fd, new_score_rec, sizeof(short_score)); +#endif +} + +/* a clean exit from the parent with proper cleanup */ +#ifdef NETWARE +void clean_shutdown_on_exit(void) +{ + if (!ap_main_finished) { + AMCSocketCleanup(); + ap_destroy_pool(pcommands); + free(ap_loaded_modules); + ap_cleanup_method_ptrs(); + ap_destroy_pool(pglobal); + ap_cleanup_alloc(); + ap_main_finished = TRUE; + } +} + +void clean_parent_exit(int code) __attribute__((noreturn)); +void clean_parent_exit(int code) +#else +static void clean_parent_exit(int code) __attribute__((noreturn)); +static void clean_parent_exit(int code) +#endif +{ +#ifdef NETWARE + AMCSocketCleanup(); + ap_destroy_pool(pcommands); + free(ap_loaded_modules); + ap_cleanup_method_ptrs(); + ap_destroy_pool(pglobal); + ap_cleanup_alloc(); + ap_main_finished = TRUE; +#else + /* Clear the pool - including any registered cleanups */ + ap_destroy_pool(pglobal); +#endif + exit(code); +} + +API_EXPORT(int) ap_update_child_status(int child_num, int status, request_rec *r) +{ + int old_status; + short_score *ss; + + if (child_num < 0) + return -1; + + ap_check_signals(); + + ap_sync_scoreboard_image(); + ss = &ap_scoreboard_image->servers[child_num]; + old_status = ss->status; + ss->status = status; +#ifdef NETWARE + ap_scoreboard_image->parent[child_num].pid = GetThreadID(); +#endif + +#ifdef OPTIMIZE_TIMEOUTS + ++ss->cur_vtime; +#endif + + if (ap_extended_status) { +#ifndef OPTIMIZE_TIMEOUTS + ss->last_used = time(NULL); +#endif + if (status == SERVER_READY || status == SERVER_DEAD) { + /* + * Reset individual counters + */ + if (status == SERVER_DEAD) { + ss->my_access_count = 0L; + ss->my_bytes_served = 0L; + } + ss->conn_count = (unsigned short) 0; + ss->conn_bytes = (unsigned long) 0; + } + else if (status == SERVER_STARTING) { + /* clean out the start_time so that mod_status will print Req=0 */ + /* Use memset to be independent from the type (struct timeval vs. clock_t) */ + memset (&ss->start_time, '\0', sizeof ss->start_time); + } + if (r) { + conn_rec *c = r->connection; + ap_cpystrn(ss->client, ap_get_remote_host(c, r->per_dir_config, + REMOTE_NOLOOKUP), sizeof(ss->client)); + if (r->the_request == NULL) { + ap_cpystrn(ss->request, "NULL", sizeof(ss->request)); + } else if (r->parsed_uri.password == NULL) { + ap_cpystrn(ss->request, r->the_request, sizeof(ss->request)); + } else { + /* Don't reveal the password in the server-status view */ + ap_cpystrn(ss->request, ap_pstrcat(r->pool, r->method, " ", + ap_unparse_uri_components(r->pool, &r->parsed_uri, UNP_OMITPASSWORD), + r->assbackwards ? NULL : " ", r->protocol, NULL), + sizeof(ss->request)); + } + ss->vhostrec = r->server; + } + } + if (status == SERVER_DEAD) { + ap_scoreboard_image->parent[child_num].pid = 0; + } + else if (status == SERVER_STARTING && r == NULL) { + /* clean up the slot's vhostrec pointer (maybe re-used) + * and mark the slot as belonging to a new generation. + */ + ss->vhostrec = NULL; + ap_scoreboard_image->parent[child_num].generation = ap_my_generation; +#ifdef SCOREBOARD_FILE + lseek(scoreboard_fd, XtOffsetOf(scoreboard, parent[child_num]), 0); + force_write(scoreboard_fd, &ap_scoreboard_image->parent[child_num], + sizeof(parent_score)); +#endif + } + put_scoreboard_info(child_num, ss); + + return old_status; +} + +static void update_scoreboard_global(void) +{ +#ifdef SCOREBOARD_FILE + lseek(scoreboard_fd, + (char *) &ap_scoreboard_image->global -(char *) ap_scoreboard_image, 0); + force_write(scoreboard_fd, &ap_scoreboard_image->global, + sizeof ap_scoreboard_image->global); +#endif +} + +void ap_time_process_request(int child_num, int status) +{ + short_score *ss; +#if defined(NO_GETTIMEOFDAY) && !defined(NO_TIMES) + struct tms tms_blk; +#endif + + if (child_num < 0) + return; + + ap_sync_scoreboard_image(); + ss = &ap_scoreboard_image->servers[child_num]; + + if (status == START_PREQUEST) { +#if defined(NO_GETTIMEOFDAY) +#ifndef NO_TIMES + if ((ss->start_time = times(&tms_blk)) == -1) +#endif /* NO_TIMES */ + ss->start_time = (clock_t) 0; +#else + if (gettimeofday(&ss->start_time, (struct timezone *) 0) < 0) + ss->start_time.tv_sec = + ss->start_time.tv_usec = 0L; +#endif + } + else if (status == STOP_PREQUEST) { +#if defined(NO_GETTIMEOFDAY) +#ifndef NO_TIMES + if ((ss->stop_time = times(&tms_blk)) == -1) +#endif + ss->stop_time = ss->start_time = (clock_t) 0; +#else + if (gettimeofday(&ss->stop_time, (struct timezone *) 0) < 0) + ss->stop_time.tv_sec = + ss->stop_time.tv_usec = + ss->start_time.tv_sec = + ss->start_time.tv_usec = 0L; +#endif + + } + + put_scoreboard_info(child_num, ss); +} + +static void increment_counts(int child_num, request_rec *r) +{ + long int bs = 0; + short_score *ss; + + ap_sync_scoreboard_image(); + ss = &ap_scoreboard_image->servers[child_num]; + + if (r->sent_bodyct) + ap_bgetopt(r->connection->client, BO_BYTECT, &bs); + +#ifndef NO_TIMES + times(&ss->times); +#endif + ss->access_count++; + ss->my_access_count++; + ss->conn_count++; + ss->bytes_served += (unsigned long) bs; + ss->my_bytes_served += (unsigned long) bs; + ss->conn_bytes += (unsigned long) bs; + + put_scoreboard_info(child_num, ss); +} + +static int find_child_by_pid(int pid) +{ + int i; + + for (i = 0; i < max_daemons_limit; ++i) + if (ap_scoreboard_image->parent[i].pid == pid) + return i; + + return -1; +} + +static void reclaim_child_processes(int terminate) +{ +#ifndef MULTITHREAD + int i, status; + long int waittime = 1024 * 16; /* in usecs */ + struct timeval tv; + int waitret, tries; + int not_dead_yet; + int ret; +#ifndef NO_OTHER_CHILD + other_child_rec *ocr, *nocr; +#endif + + ap_sync_scoreboard_image(); + + for (tries = terminate ? 4 : 1; tries <= 12; ++tries) { + /* don't want to hold up progress any more than + * necessary, but we need to allow children a few moments to exit. + * Set delay with an exponential backoff. NOTE: if we get + * interupted, we'll wait longer than expected... + */ + tv.tv_sec = waittime / 1000000; + tv.tv_usec = waittime % 1000000; + waittime = waittime * 4; + do { + ret = ap_select(0, NULL, NULL, NULL, &tv); + } while (ret == -1 && errno == EINTR); + + /* now see who is done */ + not_dead_yet = 0; + for (i = 0; i < max_daemons_limit; ++i) { + int pid = ap_scoreboard_image->parent[i].pid; + + if (pid == my_pid || pid == 0) + continue; + + if (!in_pid_table(pid)) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "Bad pid (%d) in scoreboard slot %d", pid, i); + continue; + } + waitret = waitpid(pid, &status, WNOHANG); + if (waitret == pid || waitret == -1) { + ap_scoreboard_image->parent[i].pid = 0; + unset_pid_table(pid); + continue; + } + ++not_dead_yet; + switch (tries) { + case 1: /* 16ms */ + case 2: /* 82ms */ + break; + case 3: /* 344ms */ + /* perhaps it missed the SIGHUP, lets try again */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, + server_conf, + "child process %d did not exit, sending another SIGHUP", + pid); + kill(pid, SIGHUP); + waittime = 1024 * 16; + break; + case 4: /* 16ms */ + case 5: /* 82ms */ + case 6: /* 344ms */ + break; + case 7: /* 1.4sec */ + /* ok, now it's being annoying */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, + server_conf, + "child process %d still did not exit, sending a SIGTERM", + pid); + kill(pid, SIGTERM); + break; + case 8: /* 6 sec */ + /* die child scum */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "child process %d still did not exit, sending a SIGKILL", + pid); + kill(pid, SIGKILL); + waittime = 1024 * 16; /* give them some time to die */ + break; + case 9: /* 6 sec */ + case 10: /* 6.1 sec */ + case 11: /* 6.4 sec */ + break; + case 12: /* 7.4 sec */ + /* gave it our best shot, but alas... If this really + * is a child we are trying to kill and it really hasn't + * exited, we will likely fail to bind to the port + * after the restart. + */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "could not make child process %d exit, " + "attempting to continue anyway", pid); + break; + } + } +#ifdef TPF + AP_OS_RECLAIM_LOOP_ADJUSTMENTS +#endif +#ifndef NO_OTHER_CHILD + for (ocr = other_children; ocr; ocr = nocr) { + nocr = ocr->next; + if (ocr->pid == -1) + continue; + + waitret = waitpid(ocr->pid, &status, WNOHANG); + if (waitret == ocr->pid) { + ocr->pid = -1; + (*ocr->maintenance) (OC_REASON_RESTART, ocr->data, (ap_wait_t)status); + } + else if (waitret == 0) { + (*ocr->maintenance) (OC_REASON_RESTART, ocr->data, (ap_wait_t)-1); + ++not_dead_yet; + } + else if (waitret == -1) { + /* uh what the heck? they didn't call unregister? */ + ocr->pid = -1; + (*ocr->maintenance) (OC_REASON_LOST, ocr->data, (ap_wait_t)-1); + } + } +#endif + if (!not_dead_yet) { + /* nothing left to wait for */ + break; + } + } +#endif /* ndef MULTITHREAD */ +} + + +#if defined(NEED_WAITPID) +/* + Systems without a real waitpid sometimes lose a child's exit while waiting + for another. Search through the scoreboard for missing children. + */ +int reap_children(ap_wait_t *status) +{ + int n, pid; + + for (n = 0; n < max_daemons_limit; ++n) { + ap_sync_scoreboard_image(); + pid = ap_scoreboard_image->parent[n].pid; + if (ap_scoreboard_image->servers[n].status != SERVER_DEAD) { + if (in_pid_table(pid)) { + if (kill(pid, 0) == -1) { + ap_update_child_status(n, SERVER_DEAD, NULL); + /* just mark it as having a successful exit status */ + bzero((char *) status, sizeof(ap_wait_t)); + unset_pid_table(pid); /* to be safe */ + return(pid); + } + } + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "Bad pid (%d) in scoreboard slot %d", pid, n); + } + } + } + return 0; +} +#endif + +/* Finally, this routine is used by the caretaker process to wait for + * a while... + */ + +#ifndef NETWARE +/* number of calls to wait_or_timeout between writable probes */ +#ifndef INTERVAL_OF_WRITABLE_PROBES +#define INTERVAL_OF_WRITABLE_PROBES 10 +#endif +static int wait_or_timeout_counter; + +static int wait_or_timeout(ap_wait_t *status) +{ +#ifdef WIN32 +#define MAXWAITOBJ MAXIMUM_WAIT_OBJECTS + HANDLE h[MAXWAITOBJ]; + int e[MAXWAITOBJ]; + int round, pi, hi, rv, err, pid; + for (round = 0; round <= (HARD_SERVER_LIMIT - 1) / MAXWAITOBJ + 1; round++) { + hi = 0; + for (pi = round * MAXWAITOBJ; + (pi < (round + 1) * MAXWAITOBJ) && (pi < HARD_SERVER_LIMIT); + pi++) { + if (ap_scoreboard_image->servers[pi].status != SERVER_DEAD) { + e[hi] = pi; + pid = ap_scoreboard_image->parent[pi].pid; + if (in_pid_table(pid)) + h[hi++] = (HANDLE) pid; + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "Bad pid (%d) in scoreboard slot %d", pid, pi); + } + } + + } + if (hi > 0) { + rv = WaitForMultipleObjects(hi, h, FALSE, 10000); + if (rv == -1) + err = GetLastError(); + if ((WAIT_OBJECT_0 <= (unsigned int) rv) && ((unsigned int) rv < (WAIT_OBJECT_0 + hi))) + return (ap_scoreboard_image->parent[e[rv - WAIT_OBJECT_0]].pid); + else if ((WAIT_ABANDONED_0 <= (unsigned int) rv) && ((unsigned int) rv < (WAIT_ABANDONED_0 + hi))) + return (ap_scoreboard_image->parent[e[rv - WAIT_ABANDONED_0]].pid); + + } + } + return (-1); +#else /* WIN32 */ + struct timeval tv; + int ret; + + ++wait_or_timeout_counter; + if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) { + wait_or_timeout_counter = 0; +#ifndef NO_OTHER_CHILD + probe_writable_fds(); +#endif + } + ret = waitpid(-1, status, WNOHANG); + if (ret == -1 && errno == EINTR) { + return -1; + } + if (ret > 0) { + return ret; + } +#ifdef NEED_WAITPID + if ((ret = reap_children(status)) > 0) { + return ret; + } +#endif + tv.tv_sec = SCOREBOARD_MAINTENANCE_INTERVAL / 1000000; + tv.tv_usec = SCOREBOARD_MAINTENANCE_INTERVAL % 1000000; + ap_select(0, NULL, NULL, NULL, &tv); + return -1; +#endif /* WIN32 */ +} +#endif + +#if defined(NSIG) +#define NumSIG NSIG +#elif defined(_NSIG) +#define NumSIG _NSIG +#elif defined(__NSIG) +#define NumSIG __NSIG +#else +#define NumSIG 32 /* for 1998's unixes, this is still a good assumption */ +#endif + +#ifdef SYS_SIGLIST /* platform has sys_siglist[] */ +#define INIT_SIGLIST() /*nothing*/ +#else /* platform has no sys_siglist[], define our own */ +#define SYS_SIGLIST ap_sys_siglist +#define INIT_SIGLIST() siglist_init(); + +const char *ap_sys_siglist[NumSIG]; + +static void siglist_init(void) +{ + int sig; + + ap_sys_siglist[0] = "Signal 0"; +#ifdef SIGHUP + ap_sys_siglist[SIGHUP] = "Hangup"; +#endif +#ifdef SIGINT + ap_sys_siglist[SIGINT] = "Interrupt"; +#endif +#ifdef SIGQUIT + ap_sys_siglist[SIGQUIT] = "Quit"; +#endif +#ifdef SIGILL + ap_sys_siglist[SIGILL] = "Illegal instruction"; +#endif +#ifdef SIGTRAP + ap_sys_siglist[SIGTRAP] = "Trace/BPT trap"; +#endif +#ifdef SIGIOT + ap_sys_siglist[SIGIOT] = "IOT instruction"; +#endif +#ifdef SIGABRT + ap_sys_siglist[SIGABRT] = "Abort"; +#endif +#ifdef SIGEMT + ap_sys_siglist[SIGEMT] = "Emulator trap"; +#endif +#ifdef SIGFPE + ap_sys_siglist[SIGFPE] = "Arithmetic exception"; +#endif +#ifdef SIGKILL + ap_sys_siglist[SIGKILL] = "Killed"; +#endif +#ifdef SIGBUS + ap_sys_siglist[SIGBUS] = "Bus error"; +#endif +#ifdef SIGSEGV + ap_sys_siglist[SIGSEGV] = "Segmentation fault"; +#endif +#ifdef SIGSYS + ap_sys_siglist[SIGSYS] = "Bad system call"; +#endif +#ifdef SIGPIPE + ap_sys_siglist[SIGPIPE] = "Broken pipe"; +#endif +#ifdef SIGALRM + ap_sys_siglist[SIGALRM] = "Alarm clock"; +#endif +#ifdef SIGTERM + ap_sys_siglist[SIGTERM] = "Terminated"; +#endif +#ifdef SIGUSR1 + ap_sys_siglist[SIGUSR1] = "User defined signal 1"; +#endif +#ifdef SIGUSR2 + ap_sys_siglist[SIGUSR2] = "User defined signal 2"; +#endif +#ifdef SIGCLD + ap_sys_siglist[SIGCLD] = "Child status change"; +#endif +#ifdef SIGCHLD + ap_sys_siglist[SIGCHLD] = "Child status change"; +#endif +#ifdef SIGPWR + ap_sys_siglist[SIGPWR] = "Power-fail restart"; +#endif +#ifdef SIGWINCH + ap_sys_siglist[SIGWINCH] = "Window changed"; +#endif +#ifdef SIGURG + ap_sys_siglist[SIGURG] = "urgent socket condition"; +#endif +#ifdef SIGPOLL + ap_sys_siglist[SIGPOLL] = "Pollable event occurred"; +#endif +#ifdef SIGIO + ap_sys_siglist[SIGIO] = "socket I/O possible"; +#endif +#ifdef SIGSTOP + ap_sys_siglist[SIGSTOP] = "Stopped (signal)"; +#endif +#ifdef SIGTSTP + ap_sys_siglist[SIGTSTP] = "Stopped"; +#endif +#ifdef SIGCONT + ap_sys_siglist[SIGCONT] = "Continued"; +#endif +#ifdef SIGTTIN + ap_sys_siglist[SIGTTIN] = "Stopped (tty input)"; +#endif +#ifdef SIGTTOU + ap_sys_siglist[SIGTTOU] = "Stopped (tty output)"; +#endif +#ifdef SIGVTALRM + ap_sys_siglist[SIGVTALRM] = "virtual timer expired"; +#endif +#ifdef SIGPROF + ap_sys_siglist[SIGPROF] = "profiling timer expired"; +#endif +#ifdef SIGXCPU + ap_sys_siglist[SIGXCPU] = "exceeded cpu limit"; +#endif +#ifdef SIGXFSZ + ap_sys_siglist[SIGXFSZ] = "exceeded file size limit"; +#endif + for (sig=0; sig < sizeof(ap_sys_siglist)/sizeof(ap_sys_siglist[0]); ++sig) + if (ap_sys_siglist[sig] == NULL) + ap_sys_siglist[sig] = ""; +} +#endif /* platform has sys_siglist[] */ + +#ifdef AP_ENABLE_EXCEPTION_HOOK +typedef struct except_hook_t { + struct except_hook_t *next; + void (*fn)(ap_exception_info_t *); +} except_hook_t; + +static except_hook_t *except_hooks; + +static void except_hook_cleanup(void *ignored) +{ + except_hooks = NULL; +} + +API_EXPORT(int) ap_add_fatal_exception_hook(void (*fn)(ap_exception_info_t *)) +{ + except_hook_t *new; + + ap_assert(pconf); + + if (!ap_exception_hook_enabled) { + return 1; + } + + new = ap_palloc(pconf, sizeof(except_hook_t)); + new->next = except_hooks; + new->fn = fn; + except_hooks = new; + + return 0; +} + +static void run_fatal_exception_hook(int sig) +{ + except_hook_t *cur_hook = except_hooks; + ap_exception_info_t ei = {0}; + + if (ap_exception_hook_enabled && + geteuid() != 0) { + ei.sig = sig; + ei.pid = getpid(); + + while (cur_hook) { + cur_hook->fn(&ei); + cur_hook = cur_hook->next; + } + } +} +#endif /* AP_ENABLE_EXCEPTION_HOOK */ + +/* handle all varieties of core dumping signals */ +static void sig_coredump(int sig) +{ + chdir(ap_coredump_dir); + signal(sig, SIG_DFL); +#ifdef AP_ENABLE_EXCEPTION_HOOK + run_fatal_exception_hook(sig); +#endif +#if !defined(WIN32) && !defined(NETWARE) + kill(getpid(), sig); +#else + raise(sig); +#endif + /* At this point we've got sig blocked, because we're still inside + * the signal handler. When we leave the signal handler it will + * be unblocked, and we'll take the signal... and coredump or whatever + * is appropriate for this particular Unix. In addition the parent + * will see the real signal we received -- whereas if we called + * abort() here, the parent would only see SIGABRT. + */ +} + +/***************************************************************** + * Connection structures and accounting... + */ + +static void just_die(int sig) +{ /* SIGHUP to child process??? */ + /* if alarms are blocked we have to wait to die otherwise we might + * end up with corruption in alloc.c's internal structures */ +#ifdef NETWARE + get_tsd +#endif + if (alarms_blocked) { + exit_after_unblock = 1; + } + else { + clean_child_exit(0); + } +} + +static int volatile usr1_just_die = 1; +static int volatile deferred_die; + +static void usr1_handler(int sig) +{ + if (usr1_just_die) { + just_die(sig); + } + deferred_die = 1; +} + +/* volatile just in case */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile is_graceful; +API_VAR_EXPORT ap_generation_t volatile ap_my_generation=0; + +#ifdef WIN32 +/* + * Signalling Apache on NT. + * + * Under Unix, Apache can be told to shutdown or restart by sending various + * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so + * we use "events" instead. The parent apache process goes into a loop + * where it waits forever for a set of events. Two of those events are + * called + * + * apPID_shutdown + * apPID_restart + * + * (where PID is the PID of the apache parent process). When one of these + * is signalled, the Apache parent performs the appropriate action. The events + * can become signalled through internal Apache methods (e.g. if the child + * finds a fatal error and needs to kill its parent), via the service + * control manager (the control thread will signal the shutdown event when + * requested to stop the Apache service), from the -k Apache command line, + * or from any external program which finds the Apache PID from the + * httpd.pid file. + * + * The signal_parent() function, below, is used to signal one of these events. + * It can be called by any child or parent process, since it does not + * rely on global variables. + * + * On entry, type gives the event to signal. 0 means shutdown, 1 means + * graceful restart. + */ + +static void signal_parent(int type) +{ + HANDLE e; + char *signal_name; + extern char signal_shutdown_name[]; + extern char signal_restart_name[]; + + /* after updating the shutdown_pending or restart flags, we need + * to wake up the parent process so it can see the changes. The + * parent will normally be waiting for either a child process + * to die, or for a signal on the "spache-signal" event. So set the + * "apache-signal" event here. + */ + + /* XXX: This is no good, can't we please die in -X mode :-? */ + if (one_process) { + return; + } + + switch(type) { + case 0: signal_name = signal_shutdown_name; break; + case 1: signal_name = signal_restart_name; break; + default: return; + } + + APD2("signal_parent signalling event \"%s\"", signal_name); + + e = OpenEvent(EVENT_ALL_ACCESS, FALSE, signal_name); + if (!e) { + /* Um, problem, can't signal the parent, which means we can't + * signal ourselves to die. Ignore for now... + */ + ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, + "OpenEvent on %s event", signal_name); + return; + } + if (SetEvent(e) == 0) { + /* Same problem as above */ + ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, + "SetEvent on %s event", signal_name); + CloseHandle(e); + return; + } + CloseHandle(e); +} +#endif + +/* + * ap_start_shutdown() and ap_start_restart(), below, are a first stab at + * functions to initiate shutdown or restart without relying on signals. + * Previously this was initiated in sig_term() and restart() signal handlers, + * but we want to be able to start a shutdown/restart from other sources -- + * e.g. on Win32, from the service manager. Now the service manager can + * call ap_start_shutdown() or ap_start_restart() as appropiate. Note that + * these functions can also be called by the child processes, since global + * variables are no longer used to pass on the required action to the parent. + */ + +API_EXPORT(void) ap_start_shutdown(void) +{ +#ifndef WIN32 + if (shutdown_pending == 1) { + /* Um, is this _probably_ not an error, if the user has + * tried to do a shutdown twice quickly, so we won't + * worry about reporting it. + */ + return; + } + shutdown_pending = 1; +#else + signal_parent(0); /* get the parent process to wake up */ +#endif +} + +/* do a graceful restart if graceful == 1 */ +API_EXPORT(void) ap_start_restart(int graceful) +{ +#ifndef WIN32 + if (restart_pending == 1) { + /* Probably not an error - don't bother reporting it */ + return; + } + restart_pending = 1; + is_graceful = graceful; +#else + signal_parent(1); /* get the parent process to wake up */ +#endif /* WIN32 */ +} + +static void sig_term(int sig) +{ + ap_start_shutdown(); +} + +static void restart(int sig) +{ +#ifdef TPF + signal(sig, restart); +#endif +#if !defined (WIN32) && !defined(NETWARE) + ap_start_restart(sig == SIGUSR1); +#else + ap_start_restart(1); +#endif +} + +static void set_signals(void) +{ +#ifndef NO_USE_SIGACTION + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + if (!one_process) { + sa.sa_handler = sig_coredump; +#if defined(SA_ONESHOT) + sa.sa_flags = SA_ONESHOT; +#elif defined(SA_RESETHAND) + sa.sa_flags = SA_RESETHAND; +#endif + if (sigaction(SIGSEGV, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGSEGV)"); +#ifdef SIGBUS + if (sigaction(SIGBUS, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGBUS)"); +#endif +#ifdef SIGABORT + if (sigaction(SIGABORT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGABORT)"); +#endif +#ifdef SIGABRT + if (sigaction(SIGABRT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGABRT)"); +#endif +#ifdef SIGILL + if (sigaction(SIGILL, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGILL)"); +#endif + sa.sa_flags = 0; + } + sa.sa_handler = sig_term; + if (sigaction(SIGTERM, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGTERM)"); +#ifdef SIGINT + if (sigaction(SIGINT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGINT)"); +#endif +#ifdef SIGXCPU + sa.sa_handler = SIG_DFL; + if (sigaction(SIGXCPU, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGXCPU)"); +#endif +#ifdef SIGXFSZ + sa.sa_handler = SIG_DFL; + if (sigaction(SIGXFSZ, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGXFSZ)"); +#endif +#ifdef SIGPIPE + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGPIPE)"); +#endif + + /* we want to ignore HUPs and USR1 while we're busy processing one */ + sigaddset(&sa.sa_mask, SIGHUP); + sigaddset(&sa.sa_mask, SIGUSR1); + sa.sa_handler = restart; + if (sigaction(SIGHUP, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGHUP)"); + if (sigaction(SIGUSR1, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGUSR1)"); +#else + if (!one_process) { + signal(SIGSEGV, sig_coredump); +#ifdef SIGBUS + signal(SIGBUS, sig_coredump); +#endif /* SIGBUS */ +#ifdef SIGABORT + signal(SIGABORT, sig_coredump); +#endif /* SIGABORT */ +#ifdef SIGABRT + signal(SIGABRT, sig_coredump); +#endif /* SIGABRT */ +#ifdef SIGILL + signal(SIGILL, sig_coredump); +#endif /* SIGILL */ +#ifdef SIGXCPU + signal(SIGXCPU, SIG_DFL); +#endif /* SIGXCPU */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_DFL); +#endif /* SIGXFSZ */ + } +#ifndef NETWARE + signal(SIGTERM, sig_term); +#endif +#ifdef SIGHUP + signal(SIGHUP, restart); +#endif /* SIGHUP */ +#ifdef SIGUSR1 + signal(SIGUSR1, restart); +#endif /* SIGUSR1 */ +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif /* SIGPIPE */ + +#endif +} + + +/***************************************************************** + * Here follows a long bunch of generic server bookkeeping stuff... + */ + +static void detach(void) +{ +#if !defined(WIN32) && !defined(NETWARE) + int x; + + chdir("/"); +#if !defined(MPE) && !defined(OS2) && !defined(TPF) && !defined(BEOS) && \ + !defined(BONE) +/* Don't detach for MPE because child processes can't survive the death of + the parent. */ + if (do_detach) { + if ((x = fork()) > 0) + exit(0); + else if (x == -1) { + perror("fork"); + fprintf(stderr, "%s: unable to fork new process\n", ap_server_argv0); + exit(1); + } + RAISE_SIGSTOP(DETACH); + } +#endif +#ifndef NO_SETSID + if ((pgrp = setsid()) == -1) { + perror("setsid"); + fprintf(stderr, "%s: setsid failed\n", ap_server_argv0); + if (!do_detach) + fprintf(stderr, "setsid() failed probably because you aren't " + "running under a process management tool like daemontools\n"); + exit(1); + } +#elif defined(NEXT) || defined(NEWSOS) + if (setpgrp(0, getpid()) == -1 || (pgrp = getpgrp(0)) == -1) { + perror("setpgrp"); + fprintf(stderr, "%s: setpgrp or getpgrp failed\n", ap_server_argv0); + exit(1); + } +#elif defined(OS2) || defined(TPF) + /* OS/2 and TPF don't support process group IDs */ + pgrp = getpid(); +#elif defined(MPE) + /* MPE uses negative pid for process group */ + pgrp = -getpid(); +#elif defined(CYGWIN) + /* Cygwin does not take any argument for setpgrp() */ + if ((pgrp = setpgrp()) == -1) { + perror("setpgrp"); + fprintf(stderr, "%s: setpgrp failed\n", ap_server_argv0); + exit(1); + } +#else + if ((pgrp = setpgrp(getpid(), 0)) == -1) { + perror("setpgrp"); + fprintf(stderr, "%s: setpgrp failed\n", ap_server_argv0); + exit(1); + } +#endif + + /* close out the standard file descriptors */ + if (freopen("/dev/null", "r", stdin) == NULL) { + fprintf(stderr, "%s: unable to replace stdin with /dev/null: %s\n", + ap_server_argv0, strerror(errno)); + /* continue anyhow -- note we can't close out descriptor 0 because we + * have nothing to replace it with, and if we didn't have a descriptor + * 0 the next file would be created with that value ... leading to + * havoc. + */ + } + if (freopen("/dev/null", "w", stdout) == NULL) { + fprintf(stderr, "%s: unable to replace stdout with /dev/null: %s\n", + ap_server_argv0, strerror(errno)); + } + /* stderr is a tricky one, we really want it to be the error_log, + * but we haven't opened that yet. So leave it alone for now and it'll + * be reopened moments later. + */ +#endif /* ndef WIN32 */ +} + +/* Set group privileges. + * + * Note that we use the username as set in the config files, rather than + * the lookup of to uid --- the same uid may have multiple passwd entries, + * with different sets of groups for each. + */ + +static void set_group_privs(void) +{ +#if !defined(WIN32) && !defined(NETWARE) && !defined(BEOS) && !defined(BONE) + if (!geteuid()) { + char *name; + + /* Get username if passed as a uid */ + + if (ap_user_name[0] == '#') { + struct passwd *ent; + uid_t uid = atoi(&ap_user_name[1]); + + if ((ent = getpwuid(uid)) == NULL) { + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "getpwuid: couldn't determine user name from uid %u, " + "you probably need to modify the User directive", + (unsigned)uid); + clean_child_exit(APEXIT_CHILDFATAL); + } + + name = ent->pw_name; + } + else + name = ap_user_name; + +#if !defined(OS2) && !defined(TPF) + /* OS/2 and TPF don't support groups. */ + + /* + * Set the GID before initgroups(), since on some platforms + * setgid() is known to zap the group list. + */ +#ifdef MPE + GETPRIVMODE(); +#endif + if (setgid(ap_group_id) == -1) { +#ifdef MPE + GETUSERMODE(); +#endif + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "setgid: unable to set group id to Group %u", + (unsigned)ap_group_id); + clean_child_exit(APEXIT_CHILDFATAL); + } +#ifdef MPE + GETUSERMODE(); +#endif + + /* Reset `groups' attributes. */ + + if (initgroups(name, ap_group_id) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "initgroups: unable to set groups for User %s " + "and Group %u", name, (unsigned)ap_group_id); + clean_child_exit(APEXIT_CHILDFATAL); + } +#ifdef MULTIPLE_GROUPS + if (getgroups(NGROUPS_MAX, group_id_list) == -1) { + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "getgroups: unable to get group list"); + clean_child_exit(APEXIT_CHILDFATAL); + } +#endif /* MULTIPLE_GROUPS */ +#endif /* !defined(OS2) && !defined(TPF) */ + } +#endif /* !defined(WIN32) && !defined(NETWARE) && !defined(BEOS) */ +} + +/* check to see if we have the 'suexec' setuid wrapper installed */ +static int init_suexec(void) +{ + int result = 0; + +#if !defined(WIN32) && !defined(NETWARE) && !defined(TPF) + struct stat wrapper; + + if ((stat(SUEXEC_BIN, &wrapper)) != 0) { + result = 0; + } + else if ((wrapper.st_mode & S_ISUID) && (wrapper.st_uid == 0)) { + result = 1; + } +#endif /* ndef WIN32 */ + return result; +} + +/***************************************************************** + * Connection structures and accounting... + */ + + +static conn_rec *new_connection(pool *p, server_rec *server, BUFF *inout, + const struct sockaddr_in *remaddr, + const struct sockaddr_in *saddr, + int child_num) +{ + conn_rec *conn = (conn_rec *) ap_pcalloc(p, sizeof(conn_rec)); + + /* Got a connection structure, so initialize what fields we can + * (the rest are zeroed out by pcalloc). + */ + + conn->child_num = child_num; + + conn->pool = p; + conn->local_addr = *saddr; + conn->local_ip = ap_pstrdup(conn->pool, + inet_ntoa(conn->local_addr.sin_addr)); + conn->server = server; /* just a guess for now */ + ap_update_vhost_given_ip(conn); + conn->base_server = conn->server; + conn->client = inout; + + conn->remote_addr = *remaddr; + conn->remote_ip = ap_pstrdup(conn->pool, + inet_ntoa(conn->remote_addr.sin_addr)); + + return conn; +} + +#if defined(TCP_NODELAY) && !defined(MPE) && !defined(TPF) +static void sock_disable_nagle(int s, struct sockaddr_in *sin_client) +{ + /* The Nagle algorithm says that we should delay sending partial + * packets in hopes of getting more data. We don't want to do + * this; we are not telnet. There are bad interactions between + * persistent connections and Nagle's algorithm that have very severe + * performance penalties. (Failing to disable Nagle is not much of a + * problem with simple HTTP.) + * + * In spite of these problems, failure here is not a shooting offense. + */ + int just_say_no = 1; + + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *) &just_say_no, + sizeof(int)) < 0) { +#ifdef NETWARE + errno = WSAGetLastError(); +#endif + if (sin_client) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf, + "setsockopt: (TCP_NODELAY), client %pA probably " + "dropped the connection", &sin_client->sin_addr); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf, + "setsockopt: (TCP_NODELAY)"); + } + } +} + +#else +#define sock_disable_nagle(s, c) /* NOOP */ +#endif + +static int make_sock(pool *p, const struct sockaddr_in *server) +{ + int s; + int one = 1; + char addr[512]; + + if (server->sin_addr.s_addr != htonl(INADDR_ANY)) + ap_snprintf(addr, sizeof(addr), "address %s port %d", + inet_ntoa(server->sin_addr), ntohs(server->sin_port)); + else + ap_snprintf(addr, sizeof(addr), "port %d", ntohs(server->sin_port)); + + /* note that because we're about to slack we don't use psocket */ + ap_block_alarms(); + if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "make_sock: failed to get a socket for %s", addr); + + ap_unblock_alarms(); + exit(1); + } + + /* Solaris (probably versions 2.4, 2.5, and 2.5.1 with various levels + * of tcp patches) has some really weird bugs where if you dup the + * socket now it breaks things across SIGHUP restarts. It'll either + * be unable to bind, or it won't respond. + */ +#if defined (SOLARIS2) && SOLARIS2 < 260 +#define WORKAROUND_SOLARIS_BUG +#endif + + /* PR#1282 Unixware 1.x appears to have the same problem as solaris */ +#if defined (UW) && UW < 200 +#define WORKAROUND_SOLARIS_BUG +#endif + + /* PR#1973 NCR SVR4 systems appear to have the same problem */ +#if defined (MPRAS) +#define WORKAROUND_SOLARIS_BUG +#endif + +#ifndef WORKAROUND_SOLARIS_BUG +#ifndef BEOS /* this won't work for BeOS sockets!! */ + s = ap_slack(s, AP_SLACK_HIGH); +#endif + + ap_note_cleanups_for_socket_ex(p, s, 1); /* arrange to close on exec or restart */ +#ifdef TPF + os_note_additional_cleanups(p, s); +#endif /* TPF */ +#endif + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) { +#ifndef _OSD_POSIX + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "make_sock: for %s, setsockopt: (SO_REUSEADDR)", addr); + closesocket(s); + ap_unblock_alarms(); + exit(1); +#endif /*_OSD_POSIX*/ + } + one = 1; +#if defined(SO_KEEPALIVE) && !defined(MPE) + if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(int)) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "make_sock: for %s, setsockopt: (SO_KEEPALIVE)", addr); + closesocket(s); + + ap_unblock_alarms(); + exit(1); + } +#endif + + sock_disable_nagle(s, NULL); + sock_enable_linger(s); + + /* + * To send data over high bandwidth-delay connections at full + * speed we must force the TCP window to open wide enough to keep the + * pipe full. The default window size on many systems + * is only 4kB. Cross-country WAN connections of 100ms + * at 1Mb/s are not impossible for well connected sites. + * If we assume 100ms cross-country latency, + * a 4kB buffer limits throughput to 40kB/s. + * + * To avoid this problem I've added the SendBufferSize directive + * to allow the web master to configure send buffer size. + * + * The trade-off of larger buffers is that more kernel memory + * is consumed. YMMV, know your customers and your network! + * + * -John Heidemann <johnh@isi.edu> 25-Oct-96 + * + * If no size is specified, use the kernel default. + */ +#ifndef BEOS /* BeOS does not support SO_SNDBUF */ + if (server_conf->send_buffer_size) { + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + (char *) &server_conf->send_buffer_size, sizeof(int)) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, + "make_sock: failed to set SendBufferSize for %s, " + "using default", addr); + /* not a fatal error */ + } + } +#endif + +#ifdef MPE +/* MPE requires CAP=PM and GETPRIVMODE to bind to ports less than 1024 */ + if (ntohs(server->sin_port) < 1024) + GETPRIVMODE(); +#endif + + if (bind(s, (struct sockaddr *) server, sizeof(struct sockaddr_in)) == -1) { + ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf, + "make_sock: could not bind to %s", addr); +#ifdef MPE + if (ntohs(server->sin_port) < 1024) + GETUSERMODE(); +#endif + + closesocket(s); + ap_unblock_alarms(); + exit(1); + } +#ifdef MPE + if (ntohs(server->sin_port) < 1024) + GETUSERMODE(); +#endif + + if (listen(s, ap_listenbacklog) == -1) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "make_sock: unable to listen for connections on %s", addr); + closesocket(s); + ap_unblock_alarms(); + exit(1); + } + +#ifdef SO_ACCEPTFILTER + if (ap_acceptfilter) { +#ifndef ACCEPT_FILTER_NAME +#define ACCEPT_FILTER_NAME "httpready" +#ifdef __FreeBSD_version +#if __FreeBSD_version < 411000 /* httpready broken before 4.1.1 */ +#undef ACCEPT_FILTER_NAME +#define ACCEPT_FILTER_NAME "dataready" +#endif +#endif +#endif /* ! ACCEPT_FILTER_NAME */ + /* + * See htdocs/manual/misc/perf-bsd44.html for a discussion of + * how to enable this feature and various issues with it. + */ + struct accept_filter_arg af = { + ACCEPT_FILTER_NAME, "" + }; + if (setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)) < 0) { + if (errno == ENOPROTOOPT) { + ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, server_conf, + "socket option SO_ACCEPTFILTER unkown on this machine. Continuing."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_INFO, server_conf, + "make_sock: for %s, setsockopt: (SO_ACCEPTFILTER)", addr); + } + } + } +#endif + +#ifdef WORKAROUND_SOLARIS_BUG + s = ap_slack(s, AP_SLACK_HIGH); + + ap_note_cleanups_for_socket_ex(p, s, 1); /* arrange to close on exec or restart */ +#endif + ap_unblock_alarms(); + +#ifdef CHECK_FD_SETSIZE + /* protect various fd_sets */ + if (s >= FD_SETSIZE) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, + "make_sock: problem listening on %s, filedescriptor (%u) " + "larger than FD_SETSIZE (%u) " + "found, you probably need to rebuild Apache with a " + "larger FD_SETSIZE", addr, s, FD_SETSIZE); + closesocket(s); + exit(1); + } +#endif + + return s; +} + + +/* + * During a restart we keep track of the old listeners here, so that we + * can re-use the sockets. We have to do this because we won't be able + * to re-open the sockets ("Address already in use"). + * + * Unlike the listeners ring, old_listeners is a NULL terminated list. + * + * copy_listeners() makes the copy, find_listener() finds an old listener + * and close_unused_listener() cleans up whatever wasn't used. + */ +static listen_rec *old_listeners; + +/* unfortunately copy_listeners may be called before listeners is a ring */ +static void copy_listeners(pool *p) +{ + listen_rec *lr; + + ap_assert(old_listeners == NULL); + if (ap_listeners == NULL) { + return; + } + lr = ap_listeners; + do { + listen_rec *nr = malloc(sizeof *nr); + + if (nr == NULL) { + fprintf(stderr, "Ouch! malloc failed in copy_listeners()\n"); + exit(1); + } + *nr = *lr; + ap_kill_cleanups_for_socket(p, nr->fd); + nr->next = old_listeners; + ap_assert(!nr->used); + old_listeners = nr; + lr = lr->next; + } while (lr && lr != ap_listeners); +} + + +static int find_listener(listen_rec *lr) +{ + listen_rec *or; + + for (or = old_listeners; or; or = or->next) { + if (!memcmp(&or->local_addr, &lr->local_addr, sizeof(or->local_addr))) { + or->used = 1; + return or->fd; + } + } + return -1; +} + + +static void close_unused_listeners(void) +{ + listen_rec *or, *next; + + for (or = old_listeners; or; or = next) { + next = or->next; + if (!or->used) + closesocket(or->fd); + free(or); + } + old_listeners = NULL; +} + +#ifdef NONBLOCK_WHEN_MULTI_LISTEN +/* retrieved from APR */ +static int soblock(int sd) +{ +#ifdef NETWARE + u_long one = 0; + + if (ioctlsocket(sd, FIONBIO, &one) == SOCKET_ERROR) { + return -1; + } +#else +#ifndef BEOS + int fd_flags; + + fd_flags = fcntl(sd, F_GETFL, 0); +#if defined(O_NONBLOCK) + fd_flags &= ~O_NONBLOCK; +#elif defined(O_NDELAY) + fd_flags &= ~O_NDELAY; +#elif defined(FNDELAY) + fd_flags &= ~FNDELAY; +#else +#error Teach soblock() how to make a socket blocking on your platform. +#endif + if (fcntl(sd, F_SETFL, fd_flags) == -1) { + return errno; + } +#else + int on = 0; + if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0) + return errno; +#endif /* BEOS */ +#endif /* NETWARE */ + return 0; +} + +static int sononblock(int sd) +{ +#ifdef NETWARE + u_long one = 1; + + if (ioctlsocket(sd, FIONBIO, &one) == SOCKET_ERROR) { + return -1; + } +#else +#ifndef BEOS + int fd_flags; + + fd_flags = fcntl(sd, F_GETFL, 0); +#if defined(O_NONBLOCK) + fd_flags |= O_NONBLOCK; +#elif defined(O_NDELAY) + fd_flags |= O_NDELAY; +#elif defined(FNDELAY) + fd_flags |= FNDELAY; +#else +#error Teach sononblock() how to make a socket non-blocking on your platform. +#endif + if (fcntl(sd, F_SETFL, fd_flags) == -1) { + return errno; + } +#else + int on = 1; + if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0) + return errno; +#endif /* BEOS */ +#endif /* NETWARE */ + return 0; +} +#endif /* NONBLOCK_WHEN_MULTI_LISTEN */ + +/* open sockets, and turn the listeners list into a singly linked ring */ +static void setup_listeners(pool *p) +{ + listen_rec *lr; + int fd; + + listenmaxfd = -1; + FD_ZERO(&listenfds); + lr = ap_listeners; + for (;;) { + fd = find_listener(lr); + if (fd < 0) { + fd = make_sock(p, &lr->local_addr); + } + else { + ap_note_cleanups_for_socket_ex(p, fd, 1); + } + /* if we get here, (fd >= 0) && (fd < FD_SETSIZE) */ + FD_SET(fd, &listenfds); + if (fd > listenmaxfd) + listenmaxfd = fd; + lr->fd = fd; + if (lr->next == NULL) + break; + lr = lr->next; + } + /* turn the list into a ring */ + lr->next = ap_listeners; + head_listener = ap_listeners; + close_unused_listeners(); + +#ifdef NONBLOCK_WHEN_MULTI_LISTEN + if (ap_listeners->next != ap_listeners) { + lr = ap_listeners; + do { + if (sononblock(lr->fd) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, NULL, + "A listening socket could not be made non-blocking."); + exit(APEXIT_INIT); + } + lr = lr->next; + } while (lr != ap_listeners); + } + else { + /* we could be restarting with a single remaining listening + * socket, still in non-blocking state from a previous + * generation which had more listening sockets + */ + if (soblock(ap_listeners->fd) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, NULL, + "A listening socket could not be made blocking."); + exit(APEXIT_INIT); + } + } +#endif /* NONBLOCK_WHEN_MULTI_LISTEN */ + +#ifdef NO_SERIALIZED_ACCEPT + /* warn them about the starvation problem if they're using multiple + * sockets + */ + if (ap_listeners->next != ap_listeners) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_CRIT, NULL, + "You cannot use multiple Listens safely on your system, " + "proceeding anyway. See src/PORTING, search for " + "SERIALIZED_ACCEPT."); + } +#endif +} + + +/* + * Find a listener which is ready for accept(). This advances the + * head_listener global. + */ +static ap_inline listen_rec *find_ready_listener(fd_set * main_fds) +{ + listen_rec *lr; + + lr = head_listener; + do { + if (FD_ISSET(lr->fd, main_fds)) { + head_listener = lr->next; + return (lr); + } + lr = lr->next; + } while (lr != head_listener); + return NULL; +} + + +#if defined(WIN32) || defined(NETWARE) +static int s_iInitCount = 0; + +static int AMCSocketInitialize(void) +{ + int iVersionRequested; + WSADATA wsaData; + int err; + + if (s_iInitCount > 0) { + s_iInitCount++; + return (0); + } + else if (s_iInitCount < 0) + return (s_iInitCount); + + /* s_iInitCount == 0. Do the initailization */ + iVersionRequested = MAKEWORD(2, 0); + err = WSAStartup((WORD) iVersionRequested, &wsaData); + if (err) { + printf("WSAStartup failed with error %d\n", err); + s_iInitCount = -1; + return (s_iInitCount); + } + + if (LOBYTE(wsaData.wVersion) != 2 || + HIBYTE(wsaData.wVersion) != 0) { + printf("Apache requires Winsock 2. Please see the Apache FAQ for more information.\n"); + s_iInitCount = -2; + WSACleanup(); + return (s_iInitCount); + } + s_iInitCount++; + return (s_iInitCount); +} + + +static void AMCSocketCleanup(void) +{ + if (--s_iInitCount == 0) + WSACleanup(); + return; +} +#endif + +static void show_compile_settings(void) +{ + printf("Server version: %s\n", ap_get_server_version()); + printf("Server built: %s\n", ap_get_server_built()); + printf("Server's Module Magic Number: %u:%u\n", + MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); + printf("Server compiled with....\n"); +#ifdef TPF + show_os_specific_compile_settings(); +#endif +#ifdef BIG_SECURITY_HOLE + printf(" -D BIG_SECURITY_HOLE\n"); +#endif +#ifdef SECURITY_HOLE_PASS_AUTHORIZATION + printf(" -D SECURITY_HOLE_PASS_AUTHORIZATION\n"); +#endif +#ifdef HAVE_MMAP + printf(" -D HAVE_MMAP\n"); +#endif +#ifdef HAVE_SHMGET + printf(" -D HAVE_SHMGET\n"); +#endif +#ifdef USE_MMAP_SCOREBOARD + printf(" -D USE_MMAP_SCOREBOARD\n"); +#endif +#ifdef USE_SHMGET_SCOREBOARD + printf(" -D USE_SHMGET_SCOREBOARD\n"); +#endif +#ifdef USE_OS2_SCOREBOARD + printf(" -D USE_OS2_SCOREBOARD\n"); +#endif +#ifdef USE_POSIX_SCOREBOARD + printf(" -D USE_POSIX_SCOREBOARD\n"); +#endif +#ifdef USE_MMAP_FILES + printf(" -D USE_MMAP_FILES\n"); +#ifdef MMAP_SEGMENT_SIZE + printf(" -D MMAP_SEGMENT_SIZE=%ld\n",(long)MMAP_SEGMENT_SIZE); +#endif +#endif /*USE_MMAP_FILES*/ +#ifdef NO_WRITEV + printf(" -D NO_WRITEV\n"); +#endif +#ifdef NO_LINGCLOSE + printf(" -D NO_LINGCLOSE\n"); +#endif +#ifdef HAVE_FCNTL_SERIALIZED_ACCEPT + printf(" -D HAVE_FCNTL_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_FLOCK_SERIALIZED_ACCEPT + printf(" -D HAVE_FLOCK_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_USLOCK_SERIALIZED_ACCEPT + printf(" -D HAVE_USLOCK_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_SYSVSEM_SERIALIZED_ACCEPT + printf(" -D HAVE_SYSVSEM_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_PTHREAD_SERIALIZED_ACCEPT + printf(" -D HAVE_PTHREAD_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_OS2SEM_SERIALIZED_ACCEPT + printf(" -D HAVE_OS2SEM_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_TPF_CORE_SERIALIZED_ACCEPT + printf(" -D HAVE_TPF_CORE_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_BEOS_SERIALIZED_ACCEPT + printf(" -D HAVE_BEOS_SERIALIZED_ACCEPT\n"); +#endif +#ifdef HAVE_NONE_SERIALIZED_ACCEPT + printf(" -D HAVE_NONE_SERIALIZED_ACCEPT\n"); +#endif +#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT + printf(" -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n"); +#endif +#ifdef NO_OTHER_CHILD + printf(" -D NO_OTHER_CHILD\n"); +#endif +#ifdef NO_RELIABLE_PIPED_LOGS + printf(" -D NO_RELIABLE_PIPED_LOGS\n"); +#endif +#ifdef BUFFERED_LOGS + printf(" -D BUFFERED_LOGS\n"); +#ifdef PIPE_BUF + printf(" -D PIPE_BUF=%ld\n",(long)PIPE_BUF); +#endif +#endif + printf(" -D DYNAMIC_MODULE_LIMIT=%ld\n",(long)DYNAMIC_MODULE_LIMIT); + printf(" -D HARD_SERVER_LIMIT=%ld\n",(long)HARD_SERVER_LIMIT); +#ifdef MULTITHREAD + printf(" -D MULTITHREAD\n"); +#endif +#ifdef CHARSET_EBCDIC + printf(" -D CHARSET_EBCDIC\n"); +#endif +#ifdef NEED_HASHBANG_EMUL + printf(" -D NEED_HASHBANG_EMUL\n"); +#endif +#ifdef SHARED_CORE + printf(" -D SHARED_CORE\n"); +#endif +#ifdef SO_ACCEPTFILTER + printf(" -D SO_ACCEPTFILTER\n"); + printf(" -D ACCEPT_FILTER_NAME=\"" ACCEPT_FILTER_NAME "\"\n"); +#endif +#ifdef AP_ACCEPTFILTER_OFF + printf(" -D AP_ACCEPTFILTER_OFF\n"); +#endif +#ifdef CYGWIN_WINSOCK + printf(" -D CYGWIN_WINSOCK\n"); +#endif + +/* This list displays the compiled-in default paths: */ +#ifdef HTTPD_ROOT + printf(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n"); +#endif +#if defined(SUEXEC_BIN) && !defined(TPF) + printf(" -D SUEXEC_BIN=\"" SUEXEC_BIN "\"\n"); +#endif +#if defined(SHARED_CORE) && defined(SHARED_CORE_DIR) + printf(" -D SHARED_CORE_DIR=\"" SHARED_CORE_DIR "\"\n"); +#endif +#ifdef DEFAULT_PIDLOG + printf(" -D DEFAULT_PIDLOG=\"" DEFAULT_PIDLOG "\"\n"); +#endif +#ifdef DEFAULT_SCOREBOARD + printf(" -D DEFAULT_SCOREBOARD=\"" DEFAULT_SCOREBOARD "\"\n"); +#endif +#ifdef DEFAULT_LOCKFILE + printf(" -D DEFAULT_LOCKFILE=\"" DEFAULT_LOCKFILE "\"\n"); +#endif +#ifdef DEFAULT_ERRORLOG + printf(" -D DEFAULT_ERRORLOG=\"" DEFAULT_ERRORLOG "\"\n"); +#endif +#ifdef TYPES_CONFIG_FILE + printf(" -D TYPES_CONFIG_FILE=\"" TYPES_CONFIG_FILE "\"\n"); +#endif +#ifdef SERVER_CONFIG_FILE + printf(" -D SERVER_CONFIG_FILE=\"" SERVER_CONFIG_FILE "\"\n"); +#endif +#ifdef ACCESS_CONFIG_FILE + printf(" -D ACCESS_CONFIG_FILE=\"" ACCESS_CONFIG_FILE "\"\n"); +#endif +#ifdef RESOURCE_CONFIG_FILE + printf(" -D RESOURCE_CONFIG_FILE=\"" RESOURCE_CONFIG_FILE "\"\n"); +#endif +} + + +/* Some init code that's common between win32 and unix... well actually + * some of it is #ifdef'd but was duplicated before anyhow. This stuff + * is still a mess. + */ +static void common_init(void) +{ + int i; + INIT_SIGLIST() +#ifdef AUX3 + (void) set42sig(); +#endif + +#if defined(WIN32) || defined(NETWARE) + /* Initialize the stupid sockets */ + AMCSocketInitialize(); +#endif /* WIN32 */ + + pglobal = ap_init_alloc(); + pconf = ap_make_sub_pool(pglobal); +#ifdef AP_ENABLE_EXCEPTION_HOOK + ap_register_cleanup(pconf, NULL, except_hook_cleanup, ap_null_cleanup); +#endif + plog = ap_make_sub_pool(pglobal); + ptrans = ap_make_sub_pool(pconf); + + ap_util_init(); + ap_util_uri_init(); + + pcommands = ap_make_sub_pool(NULL); + ap_server_pre_read_config = ap_make_array(pcommands, 1, sizeof(char *)); + ap_server_post_read_config = ap_make_array(pcommands, 1, sizeof(char *)); + ap_server_config_defines = ap_make_array(pcommands, 1, sizeof(char *)); + /* overkill since static */ + for (i = 0; i < HARD_SERVER_LIMIT; i++) { + pid_table[i] = 0; + } +} + +#ifndef MULTITHREAD +/***************************************************************** + * Child process main loop. + * The following vars are static to avoid getting clobbered by longjmp(); + * they are really private to child_main. + */ + +static int srv; +static int csd; +static int dupped_csd; +static int requests_this_child; +static fd_set main_fds; + +API_EXPORT(void) ap_child_terminate(request_rec *r) +{ + r->connection->keepalive = 0; + requests_this_child = ap_max_requests_per_child = 1; +} + +static void child_main(int child_num_arg) +{ + NET_SIZE_T clen; + struct sockaddr sa_server; + struct sockaddr sa_client; + listen_rec *lr; + + /* All of initialization is a critical section, we don't care if we're + * told to HUP or USR1 before we're done initializing. For example, + * we could be half way through child_init_modules() when a restart + * signal arrives, and we'd have no real way to recover gracefully + * and exit properly. + * + * I suppose a module could take forever to initialize, but that would + * be either a broken module, or a broken configuration (i.e. network + * problems, file locking problems, whatever). -djg + */ + ap_block_alarms(); + + my_pid = getpid(); + csd = -1; + dupped_csd = -1; + my_child_num = child_num_arg; + requests_this_child = 0; + + /* Get a sub pool for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + pchild = ap_make_sub_pool(pconf); + /* associate accept mutex cleanup with a subpool of pchild so we can + * make sure the mutex is released before calling module code at + * termination + */ + pmutex = ap_make_sub_pool(pchild); + + /* needs to be done before we switch UIDs so we have permissions */ + reopen_scoreboard(pchild); + SAFE_ACCEPT(accept_mutex_child_init(pmutex)); + + set_group_privs(); +#ifdef MPE + /* No such thing as root on MPE, so try to switch unconditionally */ + GETPRIVMODE(); + if (setuid(ap_user_id) == -1) { + GETUSERMODE(); + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "setuid: unable to change to uid: %d", ap_user_id); + exit(1); + } + GETUSERMODE(); +#else + /* + * Only try to switch if we're running as root + * In case of Cygwin we have the special super-user named SYSTEM + */ +#ifdef CYGWIN + if (getuid() == SYSTEM_UID && ( +#else + if (!geteuid() && ( +#endif +#ifdef _OSD_POSIX + os_init_job_environment(server_conf, ap_user_name, one_process) != 0 || +#endif + setuid(ap_user_id) == -1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "setuid: unable to change to uid: %ld", (long) ap_user_id); + clean_child_exit(APEXIT_CHILDFATAL); + } +#endif + +#ifdef HAVE_SET_DUMPABLE + if (ap_coredump_dir_configured) { + /* user set CoredumpDirectory, so they want to get core dumps + */ + if (prctl(PR_SET_DUMPABLE, 1)) { + ap_log_error(APLOG_MARK, APLOG_ALERT, NULL, + "set dumpable failed - this child will not coredump" + " after software errors"); + } + } +#endif + + ap_child_init_modules(pchild, server_conf); + + /* done with the initialization critical section */ + ap_unblock_alarms(); + + (void) ap_update_child_status(my_child_num, SERVER_READY, (request_rec *) NULL); + + /* + * Setup the jump buffers so that we can return here after a timeout + */ + ap_setjmp(jmpbuffer); +#ifndef OS2 +#ifdef SIGURG + signal(SIGURG, timeout); +#endif +#endif + signal(SIGALRM, alrm_handler); +#ifdef TPF + signal(SIGHUP, just_die); + signal(SIGTERM, just_die); + signal(SIGUSR1, just_die); +#endif /* TPF */ + +#ifdef OS2 +/* Stop Ctrl-C/Ctrl-Break signals going to child processes */ + { + unsigned long ulTimes; + DosSetSignalExceptionFocus(0, &ulTimes); + } +#endif + + while (1) { + BUFF *conn_io; + request_rec *r; + + /* Prepare to receive a SIGUSR1 due to graceful restart so that + * we can exit cleanly. Since we're between connections right + * now it's the right time to exit, but we might be blocked in a + * system call when the graceful restart request is made. */ + usr1_just_die = 1; + signal(SIGUSR1, usr1_handler); + + /* + * (Re)initialize this child to a pre-connection state. + */ + + ap_kill_timeout(0); /* Cancel any outstanding alarms. */ + current_conn = NULL; + + ap_clear_pool(ptrans); + + ap_sync_scoreboard_image(); + if (ap_scoreboard_image->global.running_generation != ap_my_generation) { + clean_child_exit(0); + } + +#ifndef WIN32 + if ((ap_max_requests_per_child > 0 + && requests_this_child++ >= ap_max_requests_per_child)) { + clean_child_exit(0); + } +#else + ++requests_this_child; +#endif + + (void) ap_update_child_status(my_child_num, SERVER_READY, (request_rec *) NULL); + + /* + * Wait for an acceptable connection to arrive. + */ + + /* Lock around "accept", if necessary */ + SAFE_ACCEPT(accept_mutex_on()); + + for (;;) { + if (ap_listeners->next != ap_listeners) { + /* more than one socket */ + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + srv = ap_select(listenmaxfd + 1, &main_fds, NULL, NULL, NULL); + + if (srv < 0 && errno != EINTR) { + /* Single Unix documents select as returning errnos + * EBADF, EINTR, and EINVAL... and in none of those + * cases does it make sense to continue. In fact + * on Linux 2.0.x we seem to end up with EFAULT + * occasionally, and we'd loop forever due to it. + */ + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, "select: (listen)"); + clean_child_exit(1); + } + + if (srv <= 0) + continue; + + lr = find_ready_listener(&main_fds); + if (lr == NULL) + continue; + sd = lr->fd; + } + else { + /* only one socket, just pretend we did the other stuff */ + sd = ap_listeners->fd; + } + + /* if we accept() something we don't want to die, so we have to + * defer the exit + */ + deferred_die = 0; + usr1_just_die = 0; + for (;;) { + clen = sizeof(sa_client); + csd = ap_accept(sd, &sa_client, &clen); + if (csd >= 0 || errno != EINTR) + break; + if (deferred_die) { + /* we didn't get a socket, and we were told to die */ + clean_child_exit(0); + } + } + + if (csd >= 0) + break; /* We have a socket ready for reading */ + else { + + /* Our old behaviour here was to continue after accept() + * errors. But this leads us into lots of troubles + * because most of the errors are quite fatal. For + * example, EMFILE can be caused by slow descriptor + * leaks (say in a 3rd party module, or libc). It's + * foolish for us to continue after an EMFILE. We also + * seem to tickle kernel bugs on some platforms which + * lead to never-ending loops here. So it seems best + * to just exit in most cases. + */ + switch (errno) { + +#if defined(HPUX11) && defined(ENOBUFS) + /* On HPUX 11.x, the 'ENOBUFS, No buffer space available' + * error occures because the accept() cannot complete. + * You will not see ENOBUFS at 10.20 because the kernel + * hides any occurrence from being returned from user space. + * ENOBUFS at 11.0 TCP/IP is quite possible, and could + * occur intermittently. As a work-around, we are going to + * ingnore ENOBUFS. + */ + case ENOBUFS: +#endif + +#ifdef EPROTO + /* EPROTO on certain older kernels really means + * ECONNABORTED, so we need to ignore it for them. + * See discussion in new-httpd archives nh.9701 + * search for EPROTO. + * + * Also see nh.9603, search for EPROTO: + * There is potentially a bug in Solaris 2.x x<6, + * and other boxes that implement tcp sockets in + * userland (i.e. on top of STREAMS). On these + * systems, EPROTO can actually result in a fatal + * loop. See PR#981 for example. It's hard to + * handle both uses of EPROTO. + */ + case EPROTO: +#endif +#ifdef ECONNABORTED + case ECONNABORTED: +#endif + /* Linux generates the rest of these, other tcp + * stacks (i.e. bsd) tend to hide them behind + * getsockopt() interfaces. They occur when + * the net goes sour or the client disconnects + * after the three-way handshake has been done + * in the kernel but before userland has picked + * up the socket. + */ +#ifdef ECONNRESET + case ECONNRESET: +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: +#endif +#ifdef EHOSTUNREACH + case EHOSTUNREACH: +#endif +#ifdef ENETUNREACH + case ENETUNREACH: +#endif + /* EAGAIN/EWOULDBLOCK can be returned on BSD-derived + * TCP stacks when the connection is aborted before + * we call connect, but only because our listener + * sockets are non-blocking (NONBLOCK_WHEN_MULTI_LISTEN) + */ +#ifdef EAGAIN + case EAGAIN: +#endif +#ifdef EWOULDBLOCK +#if !defined(EAGAIN) || EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif +#endif + break; +#ifdef ENETDOWN + case ENETDOWN: + /* + * When the network layer has been shut down, there + * is not much use in simply exiting: the parent + * would simply re-create us (and we'd fail again). + * Use the CHILDFATAL code to tear the server down. + * @@@ Martin's idea for possible improvement: + * A different approach would be to define + * a new APEXIT_NETDOWN exit code, the reception + * of which would make the parent shutdown all + * children, then idle-loop until it detected that + * the network is up again, and restart the children. + * Ben Hyde noted that temporary ENETDOWN situations + * occur in mobile IP. + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "accept: giving up."); + clean_child_exit(APEXIT_CHILDFATAL); +#endif /*ENETDOWN*/ + +#ifdef TPF + case EINACT: + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, + server_conf, "offload device inactive"); + clean_child_exit(APEXIT_CHILDFATAL); + break; + default: + if (getppid() != 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, + server_conf, "select/accept error (%u)", + errno); + } + clean_child_exit(APEXIT_CHILDFATAL); +#else + default: + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "accept: (client socket)"); + clean_child_exit(1); +#endif + } + } + + /* go around again, safe to die */ + usr1_just_die = 1; + if (deferred_die) { + /* ok maybe not, see ya later */ + clean_child_exit(0); + } + /* or maybe we missed a signal, you never know on systems + * without reliable signals + */ + ap_sync_scoreboard_image(); + if (ap_scoreboard_image->global.running_generation != ap_my_generation) { + clean_child_exit(0); + } + } + + SAFE_ACCEPT(accept_mutex_off()); /* unlock after "accept" */ + + /* We've got a socket, let's at least process one request off the + * socket before we accept a graceful restart request. + */ + signal(SIGUSR1, SIG_IGN); + + ap_note_cleanups_for_socket_ex(ptrans, csd, 1); + + /* protect various fd_sets */ +#ifdef CHECK_FD_SETSIZE + if (csd >= FD_SETSIZE) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, + "[csd] filedescriptor (%u) larger than FD_SETSIZE (%u) " + "found, you probably need to rebuild Apache with a " + "larger FD_SETSIZE", csd, FD_SETSIZE); + continue; + } +#endif + + /* + * We now have a connection, so set it up with the appropriate + * socket options, file descriptors, and read/write buffers. + */ + +#ifdef NONBLOCK_WHEN_MULTI_LISTEN + /* This assumes that on this platform the non-blocking setting of + * a listening socket is inherited. If that isn't the case, + * this is wasted effort. + */ + if (ap_listeners != ap_listeners->next) { + if (soblock(csd) != 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, NULL, + "couldn't make socket descriptor (%d) blocking again", + csd); + continue; + } + } +#endif /* NONBLOCK_WHEN_MULTI_LISTEN */ + + clen = sizeof(sa_server); + if (getsockname(csd, &sa_server, &clen) < 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf, + "getsockname, client %pA probably dropped the " + "connection", + &((struct sockaddr_in *)&sa_client)->sin_addr); + continue; + } + + sock_disable_nagle(csd, (struct sockaddr_in *)&sa_client); + + (void) ap_update_child_status(my_child_num, SERVER_BUSY_READ, + (request_rec *) NULL); + + conn_io = ap_bcreate(ptrans, B_RDWR | B_SOCKET); + +#ifdef B_SFIO + (void) sfdisc(conn_io->sf_in, SF_POPDISC); + sfdisc(conn_io->sf_in, bsfio_new(conn_io->pool, conn_io)); + sfsetbuf(conn_io->sf_in, NULL, 0); + + (void) sfdisc(conn_io->sf_out, SF_POPDISC); + sfdisc(conn_io->sf_out, bsfio_new(conn_io->pool, conn_io)); + sfsetbuf(conn_io->sf_out, NULL, 0); +#endif + + dupped_csd = csd; +#if defined(NEED_DUPPED_CSD) + if ((dupped_csd = dup(csd)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "dup: couldn't duplicate csd"); + dupped_csd = csd; /* Oh well... */ + } + ap_note_cleanups_for_socket_ex(ptrans, dupped_csd, 1); + + /* protect various fd_sets */ +#ifdef CHECK_FD_SETSIZE + if (dupped_csd >= FD_SETSIZE) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, + "[dupped_csd] filedescriptor (%u) larger than FD_SETSIZE (%u) " + "found, you probably need to rebuild Apache with a " + "larger FD_SETSIZE", dupped_csd, FD_SETSIZE); + continue; + } +#endif +#endif + ap_bpushfd(conn_io, csd, dupped_csd); + + current_conn = new_connection(ptrans, server_conf, conn_io, + (struct sockaddr_in *) &sa_client, + (struct sockaddr_in *) &sa_server, + my_child_num); + + /* + * Read and process each request found on our connection + * until no requests are left or we decide to close. + */ + + while ((r = ap_read_request(current_conn)) != NULL) { + + /* read_request_line has already done a + * signal (SIGUSR1, SIG_IGN); + */ + + (void) ap_update_child_status(my_child_num, SERVER_BUSY_WRITE, r); + + /* process the request if it was read without error */ + + if (r->status == HTTP_OK) + ap_process_request(r); + + if(ap_extended_status) + increment_counts(my_child_num, r); + +#ifdef TPF_HAVE_NSD + /* Update the TPF Network Services Database message counters */ + tpf_tcpip_message_cnt(NSDB_INPUT_CNT, + ((struct sockaddr_in *)&sa_server)->sin_port, + NSDB_TCP_S, 1); + + tpf_tcpip_message_cnt(NSDB_OUTPUT_CNT, + ((struct sockaddr_in *)&sa_server)->sin_port, + NSDB_TCP_S, 1); +#endif /* TPF_HAVE_NSD */ + + if (!current_conn->keepalive || current_conn->aborted) + break; + + ap_destroy_pool(r->pool); + (void) ap_update_child_status(my_child_num, SERVER_BUSY_KEEPALIVE, + (request_rec *) NULL); + + ap_sync_scoreboard_image(); + if (ap_scoreboard_image->global.running_generation != ap_my_generation) { + ap_bclose(conn_io); + clean_child_exit(0); + } + + /* In case we get a graceful restart while we're blocked + * waiting for the request. + * + * XXX: This isn't perfect, we might actually read the + * request and then just die without saying anything to + * the client. This can be fixed by using deferred_die + * but you have to teach buff.c about it so that it can handle + * the EINTR properly. + * + * In practice though browsers (have to) expect keepalive + * connections to close before receiving a response because + * of network latencies and server timeouts. + */ + usr1_just_die = 1; + signal(SIGUSR1, usr1_handler); + } + + /* + * Close the connection, being careful to send out whatever is still + * in our buffers. If possible, try to avoid a hard close until the + * client has ACKed our FIN and/or has stopped sending us data. + */ + +#ifdef NO_LINGCLOSE + ap_bclose(conn_io); /* just close it */ +#else + if (r && r->connection + && !r->connection->aborted + && r->connection->client + && (r->connection->client->fd >= 0)) { + + lingering_close(r); + } + else { + ap_bsetflag(conn_io, B_EOUT, 1); + ap_bclose(conn_io); + } +#endif + } +} + +#ifdef TPF +static void reset_tpf_listeners(APACHE_TPF_INPUT *input_parms) +{ + int count; + listen_rec *lr; + + count = 0; + listenmaxfd = -1; + FD_ZERO(&listenfds); + lr = ap_listeners; + + for(;;) { + lr->fd = input_parms->listeners[count]; + if(lr->fd >= 0) { + FD_SET(lr->fd, &listenfds); + if(lr->fd > listenmaxfd) + listenmaxfd = lr->fd; + } + if(lr->next == NULL) + break; + lr = lr->next; + count++; + } + lr->next = ap_listeners; + head_listener = ap_listeners; + close_unused_listeners(); +} + +#endif /* TPF */ + +static int make_child(server_rec *s, int slot, time_t now) +{ + int pid; + + if (slot + 1 > max_daemons_limit) { + max_daemons_limit = slot + 1; + } + + if (one_process) { + signal(SIGHUP, just_die); + signal(SIGINT, just_die); +#ifdef SIGQUIT + signal(SIGQUIT, SIG_DFL); +#endif + signal(SIGTERM, just_die); + child_main(slot); + } + + /* avoid starvation */ + head_listener = head_listener->next; + + Explain1("Starting new child in slot %d", slot); + (void) ap_update_child_status(slot, SERVER_STARTING, (request_rec *) NULL); + + +#ifdef _OSD_POSIX + /* BS2000 requires a "special" version of fork() before a setuid() call */ + if ((pid = os_fork(ap_user_name)) == -1) { +#elif defined(TPF) + if ((pid = os_fork(s, slot)) == -1) { +#else + if ((pid = fork()) == -1) { +#endif + ap_log_error(APLOG_MARK, APLOG_ERR, s, "fork: Unable to fork new process"); + + /* fork didn't succeed. Fix the scoreboard or else + * it will say SERVER_STARTING forever and ever + */ + (void) ap_update_child_status(slot, SERVER_DEAD, (request_rec *) NULL); + + /* In case system resources are maxxed out, we don't want + Apache running away with the CPU trying to fork over and + over and over again. */ + sleep(10); + + return -1; + } + + if (!pid) { +#ifdef AIX_BIND_PROCESSOR +/* by default AIX binds to a single processor + * this bit unbinds children which will then bind to another cpu + */ +#include <sys/processor.h> + int status = bindprocessor(BINDPROCESS, (int)getpid(), + PROCESSOR_CLASS_ANY); + if (status != OK) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, server_conf, + "processor unbind failed %d", status); + } +#endif + RAISE_SIGSTOP(MAKE_CHILD); + MONCONTROL(1); + /* Disable the restart signal handlers and enable the just_die stuff. + * Note that since restart() just notes that a restart has been + * requested there's no race condition here. + */ + signal(SIGHUP, just_die); + signal(SIGUSR1, just_die); + signal(SIGTERM, just_die); + child_main(slot); + } + +#ifdef OPTIMIZE_TIMEOUTS + ap_scoreboard_image->parent[slot].last_rtime = now; +#endif + ap_scoreboard_image->parent[slot].pid = pid; + set_pid_table(pid); +#ifdef SCOREBOARD_FILE + lseek(scoreboard_fd, XtOffsetOf(scoreboard, parent[slot]), 0); + force_write(scoreboard_fd, &ap_scoreboard_image->parent[slot], + sizeof(parent_score)); +#endif + + return 0; +} + + +/* start up a bunch of children */ +static void startup_children(int number_to_start) +{ + int i; + time_t now = time(NULL); + + for (i = 0; number_to_start && i < ap_daemons_limit; ++i) { + if (ap_scoreboard_image->servers[i].status != SERVER_DEAD) { + continue; + } + if (make_child(server_conf, i, now) < 0) { + break; + } + --number_to_start; + } +} + + +/* + * idle_spawn_rate is the number of children that will be spawned on the + * next maintenance cycle if there aren't enough idle servers. It is + * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by + * without the need to spawn. + */ +static int idle_spawn_rate = 1; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif +static int hold_off_on_exponential_spawning; + +/* + * Define the signal that is used to kill off children if idle_count + * is greater then ap_daemons_max_free. Usually we will use SIGUSR1 + * to gracefully shutdown, but unfortunatly some OS will need other + * signals to ensure that the child process is terminated and the + * scoreboard pool is not growing to infinity. Also set the signal we + * use to kill of childs that exceed timeout. This effect has been +* seen at least on Cygwin 1.x. -- Stipe Tolj <tolj@wapme-systems.de> + */ +#if defined(CYGWIN) +#define SIG_IDLE_KILL SIGKILL +#define SIG_TIMEOUT_KILL SIGUSR2 +#else +#define SIG_IDLE_KILL SIGUSR1 +#define SIG_TIMEOUT_KILL SIGALRM +#endif + +static void perform_idle_server_maintenance(void) +{ + int i; + int to_kill; + int idle_count; + int pid; + short_score *ss; + time_t now = time(NULL); + int free_length; + int free_slots[MAX_SPAWN_RATE]; + int last_non_dead; + int total_non_dead; + + /* initialize the free_list */ + free_length = 0; + + to_kill = -1; + idle_count = 0; + last_non_dead = -1; + total_non_dead = 0; + + ap_sync_scoreboard_image(); + for (i = 0; i < ap_daemons_limit; ++i) { + int status; + + if (i >= max_daemons_limit && free_length == idle_spawn_rate) + break; + ss = &ap_scoreboard_image->servers[i]; + status = ss->status; + if (status == SERVER_DEAD) { + /* try to keep children numbers as low as possible */ + if (free_length < idle_spawn_rate) { + free_slots[free_length] = i; + ++free_length; + } + } + else { + /* We consider a starting server as idle because we started it + * at least a cycle ago, and if it still hasn't finished starting + * then we're just going to swamp things worse by forking more. + * So we hopefully won't need to fork more if we count it. + * This depends on the ordering of SERVER_READY and SERVER_STARTING. + */ + if (status <= SERVER_READY) { + ++ idle_count; + /* always kill the highest numbered child if we have to... + * no really well thought out reason ... other than observing + * the server behaviour under linux where lower numbered children + * tend to service more hits (and hence are more likely to have + * their data in cpu caches). + */ + to_kill = i; + } + + ++total_non_dead; + last_non_dead = i; +#ifdef OPTIMIZE_TIMEOUTS + if (ss->timeout_len) { + /* if it's a live server, with a live timeout then + * start checking its timeout */ + parent_score *ps = &ap_scoreboard_image->parent[i]; + if (ss->cur_vtime != ps->last_vtime) { + /* it has made progress, so update its last_rtime, + * last_vtime */ + ps->last_rtime = now; + ps->last_vtime = ss->cur_vtime; + } + else if (ps->last_rtime + ss->timeout_len < now) { + /* no progress, and the timeout length has been exceeded */ + ss->timeout_len = 0; + pid = ps->pid; + if (in_pid_table(pid)) { + kill(pid, SIG_TIMEOUT_KILL); + } + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "Bad pid (%d) in scoreboard slot %d", pid, i); + } + } + } +#endif + } + } + max_daemons_limit = last_non_dead + 1; + if (idle_count > ap_daemons_max_free) { + /* kill off one child... we use SIGUSR1 because that'll cause it to + * shut down gracefully, in case it happened to pick up a request + * while we were counting. Use the define SIG_IDLE_KILL to reflect + * which signal should be used on the specific OS. + */ + pid = ap_scoreboard_image->parent[to_kill].pid; + if (in_pid_table(pid)) { + kill(pid, SIG_IDLE_KILL); + idle_spawn_rate = 1; +#ifdef TPF + ap_update_child_status(to_kill, SERVER_DEAD, (request_rec *)NULL); +#endif + } + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "Bad pid (%d) in scoreboard slot %d", pid, to_kill); + } + } + else if (idle_count < ap_daemons_min_free) { + /* terminate the free list */ + if (free_length == 0) { + /* only report this condition once */ + static int reported = 0; + + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "server reached MaxClients setting, consider" + " raising the MaxClients setting"); + reported = 1; + } + idle_spawn_rate = 1; + } + else { + if (idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "server seems busy, (you may need " + "to increase StartServers, or Min/MaxSpareServers), " + "spawning %d children, there are %d idle, and " + "%d total children", idle_spawn_rate, + idle_count, total_non_dead); + } + for (i = 0; i < free_length; ++i) { +#ifdef TPF + if(make_child(server_conf, free_slots[i], now) == -1) { + if(free_length == 1) { + shutdown_pending = 1; + ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, + "No active child processes: shutting down"); + } + } +#else + make_child(server_conf, free_slots[i], now); +#endif /* TPF */ + } + /* the next time around we want to spawn twice as many if this + * wasn't good enough, but not if we've just done a graceful + */ + if (hold_off_on_exponential_spawning) { + --hold_off_on_exponential_spawning; + } + else if (idle_spawn_rate < MAX_SPAWN_RATE) { + idle_spawn_rate *= 2; + } + } + } + else { + idle_spawn_rate = 1; + } +} + + +static void process_child_status(int pid, ap_wait_t status) +{ + /* Child died... if it died due to a fatal error, + * we should simply bail out. + */ + if ((WIFEXITED(status)) && + WEXITSTATUS(status) == APEXIT_CHILDFATAL) { + /* cleanup pid file -- it is useless after our exiting */ + const char *pidfile = NULL; +#ifdef TPF + /* safer on TPF to go through normal shutdown process */ + if (!shutdown_pending) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, server_conf, + "Child %d returned a Fatal error... \n" + "Apache is shutting down!", pid); + shutdown_pending = 1; + } + return; +#endif + pidfile = ap_server_root_relative (pconf, ap_pid_fname); + if ( pidfile != NULL && unlink(pidfile) == 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, + server_conf, + "removed PID file %s (pid=%ld)", + pidfile, (long)getpid()); + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, server_conf, + "Child %d returned a Fatal error... \n" + "Apache is exiting!", + pid); + exit(APEXIT_CHILDFATAL); + } + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGTERM: + case SIGHUP: + case SIGUSR1: + case SIGKILL: + break; + default: +#ifdef SYS_SIGLIST +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, + server_conf, + "child pid %d exit signal %s (%d), " + "possible coredump in %s", + pid, (WTERMSIG(status) >= NumSIG) ? "" : + SYS_SIGLIST[WTERMSIG(status)], WTERMSIG(status), + ap_coredump_dir); + } + else { +#endif + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, + server_conf, + "child pid %d exit signal %s (%d)", pid, + SYS_SIGLIST[WTERMSIG(status)], WTERMSIG(status)); +#ifdef WCOREDUMP + } +#endif +#else + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, + server_conf, + "child pid %d exit signal %d", + pid, WTERMSIG(status)); +#endif + } + } +} + + +/***************************************************************** + * Executive routines. + */ + +#ifndef STANDALONE_MAIN +#define STANDALONE_MAIN standalone_main + +static void standalone_main(int argc, char **argv) +{ + int remaining_children_to_start; + +#ifdef OS2 + printf("%s \n", ap_get_server_version()); +#endif + + ap_standalone = 1; + + is_graceful = 0; + + if (!one_process) { + detach(); + } + else { + MONCONTROL(1); + } + + my_pid = getpid(); + + do { + copy_listeners(pconf); + if (!is_graceful) { + ap_restart_time = time(NULL); + } +#ifdef SCOREBOARD_FILE + else if (scoreboard_fd != -1) { + ap_kill_cleanup(pconf, NULL, cleanup_scoreboard_file); + ap_kill_cleanups_for_fd(pconf, scoreboard_fd); + } +#endif + ap_clear_pool(pconf); +#ifdef AP_ENABLE_EXCEPTION_HOOK + ap_register_cleanup(pconf, NULL, except_hook_cleanup, ap_null_cleanup); +#endif + ptrans = ap_make_sub_pool(pconf); + + ap_init_mutex_method(ap_default_mutex_method()); + + server_conf = ap_read_config(pconf, ptrans, ap_server_confname); + setup_listeners(pconf); + ap_clear_pool(plog); + ap_open_logs(server_conf, plog); + ap_log_pid(pconf, ap_pid_fname); + ap_set_version(); /* create our server_version string */ + ap_init_modules(pconf, server_conf); + version_locked++; /* no more changes to server_version */ + SAFE_ACCEPT(accept_mutex_init(pconf)); + if (!is_graceful) { + reinit_scoreboard(pconf); + } +#ifdef SCOREBOARD_FILE + else { + ap_scoreboard_fname = ap_server_root_relative(pconf, ap_scoreboard_fname); + ap_note_cleanups_for_fd_ex(pconf, scoreboard_fd, 1); /* close on exec */ + } +#endif + + set_signals(); + + if (ap_daemons_max_free < ap_daemons_min_free + 1) /* Don't thrash... */ + ap_daemons_max_free = ap_daemons_min_free + 1; + + /* If we're doing a graceful_restart then we're going to see a lot + * of children exiting immediately when we get into the main loop + * below (because we just sent them SIGUSR1). This happens pretty + * rapidly... and for each one that exits we'll start a new one until + * we reach at least daemons_min_free. But we may be permitted to + * start more than that, so we'll just keep track of how many we're + * supposed to start up without the 1 second penalty between each fork. + */ + remaining_children_to_start = ap_daemons_to_start; + if (remaining_children_to_start > ap_daemons_limit) { + remaining_children_to_start = ap_daemons_limit; + } + if (!is_graceful) { + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + } + else { + /* give the system some time to recover before kicking into + * exponential mode */ + hold_off_on_exponential_spawning = 10; + } + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "%s configured -- resuming normal operations", + ap_get_server_version()); + if (ap_suexec_enabled) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "suEXEC mechanism enabled (wrapper: %s)", SUEXEC_BIN); + } + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "Server built: %s", ap_get_server_built()); + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "Accept mutex: %s (Default: %s)", + amutex->name, ap_default_mutex_method()); + restart_pending = shutdown_pending = 0; + + while (!restart_pending && !shutdown_pending) { + int child_slot; + ap_wait_t status; + int pid = wait_or_timeout(&status); + + /* XXX: if it takes longer than 1 second for all our children + * to start up and get into IDLE state then we may spawn an + * extra child + */ +#ifdef TPF + if (shutdown_pending += os_check_server(tpf_server_name)) { + break; + } +#endif + if (pid >= 0) { + unset_pid_table(pid); + process_child_status(pid, status); + /* non-fatal death... note that it's gone in the scoreboard. */ + ap_sync_scoreboard_image(); + child_slot = find_child_by_pid(pid); + Explain2("Reaping child %d slot %d", pid, child_slot); + if (child_slot >= 0) { + (void) ap_update_child_status(child_slot, SERVER_DEAD, + (request_rec *) NULL); + if (remaining_children_to_start + && child_slot < ap_daemons_limit) { + /* we're still doing a 1-for-1 replacement of dead + * children with new children + */ + make_child(server_conf, child_slot, time(NULL)); + --remaining_children_to_start; + } +#ifndef NO_OTHER_CHILD + } + else if (reap_other_child(pid, status) == 0) { + /* handled */ +#endif + } + else if (is_graceful) { + /* Great, we've probably just lost a slot in the + * scoreboard. Somehow we don't know about this + * child. + */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, server_conf, + "long lost child came home! (pid %d)", pid); + } + /* Don't perform idle maintenance when a child dies, + * only do it when there's a timeout. Remember only a + * finite number of children can die, and it's pretty + * pathological for a lot to die suddenly. + */ + continue; + } + else if (remaining_children_to_start) { + /* we hit a 1 second timeout in which none of the previous + * generation of children needed to be reaped... so assume + * they're all done, and pick up the slack if any is left. + */ + startup_children(remaining_children_to_start); + remaining_children_to_start = 0; + /* In any event we really shouldn't do the code below because + * few of the servers we just started are in the IDLE state + * yet, so we'd mistakenly create an extra server. + */ + continue; + } + + perform_idle_server_maintenance(); + } + + if (shutdown_pending) { + /* Time to gracefully shut down: + * Kill child processes, tell them to call child_exit, etc... + */ + if (ap_killpg(pgrp, SIGTERM) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "killpg SIGTERM"); + } + reclaim_child_processes(1); /* Start with SIGTERM */ + + /* cleanup pid file on normal shutdown */ + { + const char *pidfile = NULL; + pidfile = ap_server_root_relative (pconf, ap_pid_fname); + if ( pidfile != NULL && unlink(pidfile) == 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, + server_conf, + "removed PID file %s (pid=%ld)", + pidfile, (long)getpid()); + } + + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "caught SIGTERM, shutting down"); + clean_parent_exit(0); + } + + /* we've been told to restart */ + signal(SIGHUP, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + + if (one_process) { + /* not worth thinking about */ + clean_parent_exit(0); + } + + /* advance to the next generation */ + /* XXX: we really need to make sure this new generation number isn't in + * use by any of the children. + */ + ++ap_my_generation; + ap_scoreboard_image->global.running_generation = ap_my_generation; + update_scoreboard_global(); + + if (is_graceful) { +#ifndef SCOREBOARD_FILE + int i; +#endif + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "SIGUSR1 received. Doing graceful restart"); + + /* kill off the idle ones */ + if (ap_killpg(pgrp, SIGUSR1) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "killpg SIGUSR1"); + } +#ifndef SCOREBOARD_FILE + /* This is mostly for debugging... so that we know what is still + * gracefully dealing with existing request. But we can't really + * do it if we're in a SCOREBOARD_FILE because it'll cause + * corruption too easily. + */ + ap_sync_scoreboard_image(); + for (i = 0; i < ap_daemons_limit; ++i) { + if (ap_scoreboard_image->servers[i].status != SERVER_DEAD) { + ap_scoreboard_image->servers[i].status = SERVER_GRACEFUL; + } + } +#endif + } + else { + /* Kill 'em off */ + if (ap_killpg(pgrp, SIGHUP) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "killpg SIGHUP"); + } + reclaim_child_processes(0); /* Not when just starting up */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf, + "SIGHUP received. Attempting to restart"); + } + } while (restart_pending); + + /*add_common_vars(NULL);*/ +} /* standalone_main */ +#else +/* prototype */ +void STANDALONE_MAIN(int argc, char **argv); +#endif /* STANDALONE_MAIN */ + +extern char *optarg; +extern int optind; + +/* Cygwin 1.x SHARED_CORE support needs REALMAIN to be declared as dllexport, + * so we can later while SHARED_CORE_BOOTSTRAP is compiled and linked see the + * dllimport for it. -- Stipe Tolj <tolj@wapme-systems.de> + */ + +#if defined(CYGWIN) +__declspec(dllexport) +#endif + +int REALMAIN(int argc, char *argv[]) +{ + int c; + int sock_in; + int sock_out; + char *s; + +#ifdef SecureWare + if (set_auth_parameters(argc, argv) < 0) + perror("set_auth_parameters"); + if (getluid() < 0) + if (setluid(getuid()) < 0) + perror("setluid"); + if (setreuid(0, 0) < 0) + perror("setreuid"); +#endif + +#ifdef SOCKS + SOCKSinit(argv[0]); +#endif + +#ifdef TPF + EBW_AREA input_parms; + ecbptr()->ebrout = PRIMECRAS; + input_parms = * (EBW_AREA *)(&(ecbptr()->ebw000)); +#endif + + MONCONTROL(0); + + common_init(); + + if ((s = strrchr(argv[0], PATHSEPARATOR)) != NULL) { + ap_server_argv0 = ++s; + } + else { + ap_server_argv0 = argv[0]; + } + + ap_cpystrn(ap_server_root, HTTPD_ROOT, sizeof(ap_server_root)); + ap_cpystrn(ap_server_confname, SERVER_CONFIG_FILE, sizeof(ap_server_confname)); + + ap_setup_prelinked_modules(); + + while ((c = getopt(argc, argv, + "D:C:c:xXd:Ff:vVlLR:StTh" +#ifdef DEBUG_SIGSTOP + "Z:" +#endif + )) != -1) { + char **new; + switch (c) { + case 'c': + new = (char **)ap_push_array(ap_server_post_read_config); + *new = ap_pstrdup(pcommands, optarg); + break; + case 'C': + new = (char **)ap_push_array(ap_server_pre_read_config); + *new = ap_pstrdup(pcommands, optarg); + break; + case 'D': + new = (char **)ap_push_array(ap_server_config_defines); + *new = ap_pstrdup(pcommands, optarg); + break; + case 'd': + ap_cpystrn(ap_server_root, optarg, sizeof(ap_server_root)); + break; + case 'F': + do_detach = 0; + break; + case 'f': + ap_cpystrn(ap_server_confname, optarg, sizeof(ap_server_confname)); + break; + case 'v': + ap_set_version(); + printf("Server version: %s\n", ap_get_server_version()); + printf("Server built: %s\n", ap_get_server_built()); + exit(0); + case 'V': + ap_set_version(); + show_compile_settings(); + exit(0); + case 'l': + ap_suexec_enabled = init_suexec(); + ap_show_modules(); + exit(0); + case 'L': + ap_show_directives(); + exit(0); + case 'X': + ++one_process; /* Weird debugging mode. */ + break; +#ifdef TPF + case 'x': + os_tpf_child(&input_parms.child); + set_signals(); + break; +#endif +#ifdef DEBUG_SIGSTOP + case 'Z': + raise_sigstop_flags = atoi(optarg); + break; +#endif +#ifdef SHARED_CORE + case 'R': + /* just ignore this option here, because it has only + * effect when SHARED_CORE is used and then it was + * already handled in the Shared Core Bootstrap + * program. + */ + break; +#endif + case 'S': + ap_dump_settings = 1; + break; + case 't': + ap_configtestonly = 1; + ap_docrootcheck = 1; + break; + case 'T': + ap_configtestonly = 1; + ap_docrootcheck = 0; + break; + case 'h': + usage(argv[0]); + case '?': + usage(argv[0]); + } + } + + ap_suexec_enabled = init_suexec(); + server_conf = ap_read_config(pconf, ptrans, ap_server_confname); + + if (ap_configtestonly) { + fprintf(stderr, "Syntax OK\n"); + exit(0); + } + if (ap_dump_settings) { + exit(0); + } + + child_timeouts = !ap_standalone || one_process; + +#ifdef BEOS + /* make sure we're running in single_process mode - Yuck! */ + one_process = 1; +#endif + +#ifndef TPF + if (ap_standalone) { + ap_open_logs(server_conf, plog); + ap_set_version(); + ap_init_modules(pconf, server_conf); + version_locked++; + STANDALONE_MAIN(argc, argv); + } +#else + if (!tpf_child) { + memcpy(tpf_server_name, input_parms.parent.servname, + INETD_SERVNAME_LENGTH); + tpf_server_name[INETD_SERVNAME_LENGTH] = '\0'; + sprintf(tpf_mutex_key, "%.*x", (int) TPF_MUTEX_KEY_SIZE - 1, getpid()); + tpf_parent_pid = getppid(); + ap_open_logs(server_conf, plog); + ap_tpf_zinet_checks(ap_standalone, tpf_server_name, server_conf); + ap_tpf_save_argv(argc, argv); /* save argv parms for children */ + } + if (ap_standalone) { + ap_set_version(); + ap_init_modules(pconf, server_conf); + version_locked++; + if(tpf_child) { + server_conf->error_log = stderr; +#ifdef HAVE_SYSLOG + /* if ErrorLog is syslog call ap_open_logs from the child since + syslog isn't redirected to stderr by the Apache parent */ + if (strncasecmp(server_conf->error_fname, "syslog", 6) == 0) { + ap_open_logs(server_conf, plog); + } +#endif /* HAVE_SYSLOG */ + copy_listeners(pconf); + reset_tpf_listeners(&input_parms.child); +#ifdef SCOREBOARD_FILE + ap_scoreboard_image = &_scoreboard_image; +#else /* must be USE_SHMGET_SCOREBOARD */ + ap_scoreboard_image = + (scoreboard *)input_parms.child.scoreboard_heap; +#endif + ap_init_mutex_method(ap_default_mutex_method()); + child_main(input_parms.child.slot); + } + else + STANDALONE_MAIN(argc, argv); + } +#endif + else { + conn_rec *conn; + request_rec *r; + struct sockaddr sa_server, sa_client; + BUFF *cio; + NET_SIZE_T l; + + ap_set_version(); + /* Yes this is called twice. */ + ap_init_modules(pconf, server_conf); + version_locked++; + ap_open_logs(server_conf, plog); + ap_init_modules(pconf, server_conf); + set_group_privs(); + +#ifdef MPE + /* No such thing as root on MPE, so try to switch unconditionally */ + GETPRIVMODE(); + if (setuid(ap_user_id) == -1) { + GETUSERMODE(); + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "setuid: unable to change to uid: %d", ap_user_id); + exit(1); + } + GETUSERMODE(); +#else + /* + * Only try to switch if we're running as root + * In case of Cygwin we have the special super-user named SYSTEM + * with a pre-defined uid. + */ +#ifdef CYGWIN + if ((getuid() == SYSTEM_UID) && setuid(ap_user_id) == -1) { +#else + if (!geteuid() && setuid(ap_user_id) == -1) { +#endif + ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf, + "setuid: unable to change to uid: %ld", + (long) ap_user_id); + exit(1); + } +#endif + if (ap_setjmp(jmpbuffer)) { + exit(0); + } + +#ifdef MPE +/* HP MPE 5.5 inetd only passes the incoming socket as stdin (fd 0), whereas + HPUX inetd passes the incoming socket as stdin (fd 0) and stdout (fd 1). + Go figure. SR 5003355016 has been submitted to request that the existing + functionality be documented, and then to enhance the functionality to be + like HPUX. */ + sock_in = fileno(stdin); + sock_out = fileno(stdin); +#else + sock_in = fileno(stdin); + sock_out = fileno(stdout); +#endif + + l = sizeof(sa_client); + if ((getpeername(sock_in, &sa_client, &l)) < 0) { +/* get peername will fail if the input isn't a socket */ + perror("getpeername"); + memset(&sa_client, '\0', sizeof(sa_client)); + } + + l = sizeof(sa_server); + if (getsockname(sock_in, &sa_server, &l) < 0) { + perror("getsockname"); + fprintf(stderr, "Error getting local address\n"); + exit(1); + } + server_conf->port = ntohs(((struct sockaddr_in *) &sa_server)->sin_port); + cio = ap_bcreate(ptrans, B_RDWR | B_SOCKET); + cio->fd = sock_out; + cio->fd_in = sock_in; + conn = new_connection(ptrans, server_conf, cio, + (struct sockaddr_in *) &sa_client, + (struct sockaddr_in *) &sa_server, -1); + + while ((r = ap_read_request(conn)) != NULL) { + + if (r->status == HTTP_OK) + ap_process_request(r); + + if (!conn->keepalive || conn->aborted) + break; + + ap_destroy_pool(r->pool); + } + + ap_bclose(cio); + } + exit(0); +} + +#else /* ndef MULTITHREAD */ + + +/********************************************************************** + * Multithreaded implementation + * + * This code is fairly specific to Win32. + * + * The model used to handle requests is a set of threads. One "main" + * thread listens for new requests. When something becomes + * available, it does a select and places the newly available socket + * onto a list of "jobs" (add_job()). Then any one of a fixed number + * of "worker" threads takes the top job off the job list with + * remove_job() and handles that connection to completion. After + * the connection has finished the thread is free to take another + * job from the job list. + * + * In the code, the "main" thread is running within the worker_main() + * function. The first thing this function does is create the + * worker threads, which operate in the child_sub_main() function. The + * main thread then goes into a loop within worker_main() where they + * do a select() on the listening sockets. The select times out once + * per second so that the thread can check for an "exit" signal + * from the parent process (see below). If this signal is set, the + * thread can exit, but only after it has accepted all incoming + * connections already in the listen queue (since Win32 appears + * to through away listened but unaccepted connections when a + * process dies). + * + * Because the main and worker threads exist within a single process + * they are vulnerable to crashes or memory leaks (crashes can also + * be caused within modules, of course). There also needs to be a + * mechanism to perform restarts and shutdowns. This is done by + * creating the main & worker threads within a subprocess. A + * main process (the "parent process") creates one (or more) + * processes to do the work, then the parent sits around waiting + * for the working process to die, in which case it starts a new + * one. The parent process also handles restarts (by creating + * a new working process then signalling the previous working process + * exit ) and shutdowns (by signalling the working process to exit). + * The parent process operates within the master_main() function. This + * process also handles requests from the service manager (NT only). + * + * Signalling between the parent and working process uses a Win32 + * event. Each child has a unique name for the event, which is + * passed to it with the -Z argument when the child is spawned. The + * parent sets (signals) this event to tell the child to die. + * At present all children do a graceful die - they finish all + * current jobs _and_ empty the listen queue before they exit. + * A non-graceful die would need a second event. The -Z argument in + * the child is also used to create the shutdown and restart events, + * since the prefix (apPID) contains the parent process PID. + * + * The code below starts with functions at the lowest level - + * worker threads, and works up to the top level - the main() + * function of the parent process. + * + * The scoreboard (in process memory) contains details of the worker + * threads (within the active working process). There is no shared + * "scoreboard" between processes, since only one is ever active + * at once (or at most, two, when one has been told to shutdown but + * is processes outstanding requests, and a new one has been started). + * This is controlled by a "start_mutex" which ensures only one working + * process is active at once. + **********************************************************************/ + +/* The code protected by #ifdef UNGRACEFUL_RESTARTS/#endif sections + * could implement a sort-of ungraceful restart for Win32. instead of + * graceful restarts. + * + * However it does not work too well because it does not intercept a + * connection already in progress (in child_sub_main()). We'd have to + * get that to poll on the exit event. + */ + +/* + * Definition of jobs, shared by main and worker threads. + */ + +typedef struct joblist_s { + struct joblist_s *next; + int sock; +} joblist; + +/* + * Globals common to main and worker threads. This structure is not + * used by the parent process. + */ + +typedef struct globals_s { +#ifdef UNGRACEFUL_RESTART + HANDLE thread_exit_event; +#else + int exit_now; +#endif + semaphore *jobsemaphore; + joblist *jobhead; + joblist *jobtail; + mutex *jobmutex; + int jobcount; +} globals; + +globals allowed_globals = +{0, NULL, NULL, NULL, NULL, 0}; + +/* + * add_job()/remove_job() - add or remove an accepted socket from the + * list of sockets connected to clients. allowed_globals.jobmutex protects + * against multiple concurrent access to the linked list of jobs. + */ + +void add_job(int sock) +{ + joblist *new_job; + + ap_assert(allowed_globals.jobmutex); + + /* TODO: If too many jobs in queue, sleep, check for problems */ + ap_acquire_mutex(allowed_globals.jobmutex); + new_job = (joblist *) malloc(sizeof(joblist)); + if (new_job == NULL) { + fprintf(stderr, "Ouch! Out of memory in add_job()!\n"); + } + new_job->next = NULL; + new_job->sock = sock; + if (allowed_globals.jobtail != NULL) + allowed_globals.jobtail->next = new_job; + allowed_globals.jobtail = new_job; + if (!allowed_globals.jobhead) + allowed_globals.jobhead = new_job; + allowed_globals.jobcount++; + release_semaphore(allowed_globals.jobsemaphore); + ap_release_mutex(allowed_globals.jobmutex); +} + +int remove_job(int csd) +{ + static reported = 0; + static active_threads = 0; + joblist *job; + int sock; + + /* Decline decrementing active_threads count on the first call + * to remove_job. csd == -1 implies that this is the thread's + * first call to remove_job. + */ + if (csd != -1) { + active_threads--; + } + +#ifdef UNGRACEFUL_RESTART + HANDLE hObjects[2]; + int rv; + + hObjects[0] = allowed_globals.jobsemaphore; + hObjects[1] = allowed_globals.thread_exit_event; + + rv = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); + ap_assert(rv != WAIT_FAILED); + if (rv == WAIT_OBJECT_0 + 1) { + /* thread_exit_now */ + APD1("thread got exit now event"); + return -1; + } + /* must be semaphore */ +#else + acquire_semaphore(allowed_globals.jobsemaphore); +#endif + ap_assert(allowed_globals.jobmutex); + +#ifdef UNGRACEFUL_RESTART + if (!allowed_globals.jobhead) { +#else + ap_acquire_mutex(allowed_globals.jobmutex); + if (allowed_globals.exit_now && !allowed_globals.jobhead) { +#endif + ap_release_mutex(allowed_globals.jobmutex); + return (-1); + } + + job = allowed_globals.jobhead; + ap_assert(job); + allowed_globals.jobhead = job->next; + if (allowed_globals.jobhead == NULL) + allowed_globals.jobtail = NULL; + + ap_release_mutex(allowed_globals.jobmutex); + sock = job->sock; + free(job); + + /* If sock == -1 then the thread is about to exit so + * don't count it as active. + */ + if (sock != -1) + active_threads++; + + if (!reported && (active_threads == ap_threads_per_child)) { + reported = 1; + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf, + "Server ran out of threads to serve requests. Consider " + "raising the ThreadsPerChild setting"); + } + return (sock); +} + +/* + * child_sub_main() - this is the main loop for the worker threads + * + * Each thread runs within this function. They wait within remove_job() + * for a job to become available, then handle all the requests on that + * connection until it is closed, then return to remove_job(). + * + * The worker thread will exit when it removes a job which contains + * socket number -1. This provides a graceful thread exit, since + * it will never exit during a connection. + * + * This code in this function is basically equivalent to the child_main() + * from the multi-process (Unix) environment, except that we + * + * - do not call child_init_modules (child init API phase) + * - block in remove_job, and when unblocked we have an already + * accepted socket, instead of blocking on a mutex or select(). + */ + +static void child_sub_main(int child_num) +{ + NET_SIZE_T clen; + struct sockaddr sa_server; + struct sockaddr sa_client; + pool *ptrans; + int requests_this_child = 0; + int csd = -1; + int dupped_csd = -1; + int srv = 0; + +#ifdef NETWARE + TSD* tsd = NULL; + + while(tsd == NULL) { + tsd = (TSD*) Thread_Data_Area; + ThreadSwitchWithDelay(); + } + init_name_space(); +#endif + ap_thread_count++; + ptrans = ap_make_sub_pool(pconf); + + (void) ap_update_child_status(child_num, SERVER_READY, (request_rec *) NULL); + + /* + * Setup the jump buffers so that we can return here after a timeout. + */ +#if defined(USE_LONGJMP) + setjmp(jmpbuffer); +#else + sigsetjmp(jmpbuffer, 1); +#endif +#if defined(SIGURG) + signal(SIGURG, timeout); +#endif + +#ifdef NETWARE + tsd = (TSD*) Thread_Data_Area; +#endif + + while (1) { + BUFF *conn_io; + request_rec *r; + +#ifdef NETWARE + ThreadSwitch(); +#endif + /* + * (Re)initialize this child to a pre-connection state. + */ + + ap_set_callback_and_alarm(NULL, 0); /* Cancel any outstanding alarms */ + timeout_req = NULL; /* No request in progress */ + current_conn = NULL; + ap_clear_pool(ptrans); + + (void) ap_update_child_status(child_num, SERVER_READY, + (request_rec *) NULL); + + /* Get job from the job list. This will block until a job is ready. + * If -1 is returned then the main thread wants us to exit. + */ + csd = remove_job(csd); + if (csd == -1) + break; /* time to exit */ + + requests_this_child++; + + ap_note_cleanups_for_socket_ex(ptrans, csd, 1); + + /* + * We now have a connection, so set it up with the appropriate + * socket options, file descriptors, and read/write buffers. + */ + + clen = sizeof(sa_server); + if (getsockname(csd, &sa_server, &clen) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "getsockname"); + continue; + } + clen = sizeof(sa_client); + if ((getpeername(csd, &sa_client, &clen)) < 0) { + /* get peername will fail if the input isn't a socket */ + perror("getpeername"); + memset(&sa_client, '\0', sizeof(sa_client)); + } + + sock_disable_nagle(csd, (struct sockaddr_in *)&sa_client); + + (void) ap_update_child_status(child_num, SERVER_BUSY_READ, + (request_rec *) NULL); + + conn_io = ap_bcreate(ptrans, B_RDWR | B_SOCKET); + dupped_csd = csd; +#if defined(NEED_DUPPED_CSD) + if ((dupped_csd = dup(csd)) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "dup: couldn't duplicate csd"); + dupped_csd = csd; /* Oh well... */ + } + ap_note_cleanups_for_socket_ex(ptrans, dupped_csd, 1); +#endif + ap_bpushfd(conn_io, csd, dupped_csd); + + current_conn = new_connection(ptrans, server_conf, conn_io, + (struct sockaddr_in *) &sa_client, + (struct sockaddr_in *) &sa_server, + child_num); + + /* + * Read and process each request found on our connection + * until no requests are left or we decide to close. + */ + while ((r = ap_read_request(current_conn)) != NULL) { + (void) ap_update_child_status(child_num, SERVER_BUSY_WRITE, r); + + if (r->status == HTTP_OK) + ap_process_request(r); + + if (ap_extended_status) + increment_counts(child_num, r); + if (!current_conn->keepalive || current_conn->aborted) + break; + /* If the server is shutting down, do not allow anymore requests + * to be handled on the keepalive connection. Leave the thread + * alive to drain the job queue. This check is particularly + * important on the threaded server to allow the process to be + * quickly taken down cleanly. + */ + if (allowed_globals.exit_now) + break; + ap_destroy_pool(r->pool); + (void) ap_update_child_status(child_num, SERVER_BUSY_KEEPALIVE, + (request_rec *) NULL); + + ap_sync_scoreboard_image(); + } + + /* + * Close the connection, being careful to send out whatever is still + * in our buffers. If possible, try to avoid a hard close until the + * client has ACKed our FIN and/or has stopped sending us data. + */ + ap_kill_cleanups_for_socket(ptrans, csd); + +#ifdef NO_LINGCLOSE + ap_bclose(conn_io); /* just close it */ +#else + if (r && r->connection + && !r->connection->aborted + && r->connection->client + && (r->connection->client->fd >= 0)) { + + lingering_close(r); + } + else { + ap_bsetflag(conn_io, B_EOUT, 1); + ap_bclose(conn_io); + } +#endif + } + ap_destroy_pool(ptrans); + (void) ap_update_child_status(child_num, SERVER_DEAD, NULL); + + ap_thread_count--; +} + + +#ifdef NETWARE +void child_main(void* child_num_arg) +#else +void child_main(int child_num_arg) +#endif +{ + /* + * Only reason for this function, is to pass in + * arguments to child_sub_main() on its stack so + * that longjump doesn't try to corrupt its local + * variables and I don't need to make those + * damn variables static/global + */ +#ifdef NETWARE + TSD Tsd; + int *thread_ptr; + memset(&Tsd, 0, sizeof(TSD)); + thread_ptr = __get_thread_data_area_ptr(); + *thread_ptr = (int) &Tsd; + child_sub_main((int)child_num_arg); +#else + child_sub_main(child_num_arg); +#endif +} + + + +void cleanup_thread(thread **handles, int *thread_cnt, int thread_to_clean) +{ + int i; + + free_thread(handles[thread_to_clean]); + for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++) + handles[i] = handles[i + 1]; + (*thread_cnt)--; +} +#ifdef WIN32 +/* + * The Win32 call WaitForMultipleObjects will only allow you to wait for + * a maximum of MAXIMUM_WAIT_OBJECTS (current 64). Since the threading + * model in the multithreaded version of apache wants to use this call, + * we are restricted to a maximum of 64 threads. This is a simplistic + * routine that will increase this size. + */ +static DWORD wait_for_many_objects(DWORD nCount, CONST HANDLE *lpHandles, + DWORD dwSeconds) +{ + time_t tStopTime; + DWORD dwRet = WAIT_TIMEOUT; + DWORD dwIndex=0; + BOOL bFirst = TRUE; + + tStopTime = time(NULL) + dwSeconds; + + do { + if (!bFirst) + Sleep(1000); + else + bFirst = FALSE; + + for (dwIndex = 0; dwIndex * MAXIMUM_WAIT_OBJECTS < nCount; dwIndex++) { + dwRet = WaitForMultipleObjects( + min(MAXIMUM_WAIT_OBJECTS, + nCount - (dwIndex * MAXIMUM_WAIT_OBJECTS)), + lpHandles + (dwIndex * MAXIMUM_WAIT_OBJECTS), + 0, 0); + + if (dwRet != WAIT_TIMEOUT) { + break; + } + } + } while((time(NULL) < tStopTime) && (dwRet == WAIT_TIMEOUT)); + + return dwRet; +} +#endif +/***************************************************************** + * Executive routines. + */ + +extern void main_control_server(void *); /* in hellop.c */ + +event *exit_event; +mutex *start_mutex; + +#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */ +char signal_name_prefix[MAX_SIGNAL_NAME]; +char signal_restart_name[MAX_SIGNAL_NAME]; +char signal_shutdown_name[MAX_SIGNAL_NAME]; + +#define MAX_SELECT_ERRORS 100 + +/* + * Initialise the signal names, in the global variables signal_name_prefix, + * signal_restart_name and signal_shutdown_name. + */ + +void setup_signal_names(char *prefix) +{ + ap_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix); + ap_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), + "%s_shutdown", signal_name_prefix); + ap_snprintf(signal_restart_name, sizeof(signal_restart_name), + "%s_restart", signal_name_prefix); + + APD2("signal prefix %s", signal_name_prefix); +} + +#ifndef NETWARE +static void setup_inherited_listeners(pool *p) +{ + HANDLE pipe; + listen_rec *lr; + int fd; + WSAPROTOCOL_INFO WSAProtocolInfo; + DWORD BytesRead; + + /* Setup the listeners */ + listenmaxfd = -1; + FD_ZERO(&listenfds); + + /* Open the pipe to the parent process to receive the inherited socket + * data. The sockets have been set to listening in the parent process. + */ + pipe = GetStdHandle(STD_INPUT_HANDLE); + for (lr = ap_listeners; lr; lr = lr->next) { + if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO), + &BytesRead, (LPOVERLAPPED) NULL)) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR|APLOG_CRIT, server_conf, + "setup_inherited_listeners: Unable to read socket data from parent"); + signal_parent(0); /* tell parent to die */ + exit(1); + } + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "BytesRead = %d WSAProtocolInfo = %x20", BytesRead, WSAProtocolInfo); + fd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, + &WSAProtocolInfo, 0, 0); + if (fd == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR|APLOG_CRIT, server_conf, + "setup_inherited_listeners: WSASocket failed to open the inherited socket."); + signal_parent(0); /* tell parent to die */ + exit(1); + } + if (fd >= 0) { + FD_SET(fd, &listenfds); + if (fd > listenmaxfd) + listenmaxfd = fd; + } + ap_note_cleanups_for_socket_ex(p, fd, 1); + lr->fd = fd; + if (lr->next == NULL) { + /* turn the list into a ring */ + lr->next = ap_listeners; + break; + } + } + head_listener = ap_listeners; + close_unused_listeners(); + CloseHandle(pipe); + return; +} +#endif + +/* + * worker_main() is main loop for the child process. The loop in + * this function becomes the controlling thread for the actually working + * threads (which run in a loop in child_sub_main()). + */ + +#ifdef NETWARE +void worker_main(void) +{ + int nthreads; + fd_set main_fds; + int srv; + int clen; + int csd; + struct sockaddr_in sa_client; + thread **child_handles; + int rv; + int i; + struct timeval tv; + int my_pid; + int count_select_errors = 0; + pool *pchild; + module **m; + listen_rec* lr; + + + pchild = ap_make_sub_pool(pconf); + + ap_standalone = 1; + sd = -1; + nthreads = ap_threads_per_child; + + if (nthreads <= 0) + nthreads = 40; + + my_pid = getpid(); + + ++ap_my_generation; + + copy_listeners(pconf); + ap_restart_time = time(NULL); + + reinit_scoreboard(pconf); + setup_listeners(pconf); + + if (listenmaxfd == -1) { + /* Help, no sockets were made, better log something and exit */ + ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, NULL, + "No sockets were created for listening"); + + ap_destroy_pool(pchild); + cleanup_scoreboard(); + exit(1); + } + + set_signals(); + + /* Display listening ports */ + printf(" Listening on port(s):"); + lr = ap_listeners; + do { + printf(" %d", ntohs(lr->local_addr.sin_port)); + lr = lr->next; + } while(lr && lr != ap_listeners); + + /* Display dynamic modules loaded */ + printf("\n"); + for (m = ap_loaded_modules; *m != NULL; m++) { + if (((module*)*m)->dynamic_load_handle) { + printf(" Loaded dynamic module %s\n", ap_find_module_name(*m)); + } + } + + /* + * - Initialize allowed_globals + * - Create the thread table + * - Spawn off threads + * - Create listen socket set (done above) + * - loop { + * wait for request + * create new job + * } while (!time to exit) + * - Close all listeners + * - Wait for all threads to complete + * - Exit + */ + + ap_child_init_modules(pconf, server_conf); + allowed_globals.jobmutex = ap_create_mutex(NULL); + allowed_globals.jobsemaphore = create_semaphore(0); + + /* spawn off the threads */ + child_handles = (thread *) malloc(nthreads * sizeof(int)); + + for (i = 0; i < nthreads; i++) { + child_handles[i] = create_thread((void (*)(void *)) child_main, (void *) i); + } + + if (nthreads > max_daemons_limit) { + max_daemons_limit = nthreads; + } + + while (1) { + tv.tv_sec = 1; + tv.tv_usec = 0; + + ThreadSwitch(); + + if (shutdown_pending) + break; + + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + srv = ap_select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); + + if (srv == 0) { + count_select_errors = 0; /* reset count of errors */ + continue; + } + else if (srv == SOCKET_ERROR) { + if (h_errno != WSAEINTR) { + /* A "real" error occurred, log it and increment the count of + * select errors. This count is used to ensure we don't go into + * a busy loop of continuous errors. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, + "select failed with errno %d", h_errno); + count_select_errors++; + if (count_select_errors > MAX_SELECT_ERRORS) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, server_conf, + "Too many errors in select loop. Child process exiting."); + break; + } + } + continue; + } else { + listen_rec *lr; + + lr = find_ready_listener(&main_fds); + + if (lr != NULL) { + sd = lr->fd; + } + } + + do { + clen = sizeof(sa_client); + csd = accept(sd, (struct sockaddr *) &sa_client, &clen); + + if (csd == INVALID_SOCKET) { + csd = -1; + } + } while (csd < 0 && h_errno == WSAEINTR); + + if (csd == INVALID_SOCKET) { + if ((h_errno != WSAECONNABORTED) && (h_errno != WSAEWOULDBLOCK)) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "accept: (client socket) failed with errno = %d",h_errno); + } + } + else { + u_long one = 0; + + if (soblock(csd) != 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "%d couldn't make socket descriptor (%d) blocking again.", h_errno, csd); + continue; + } + add_job(csd); + } + } + + APD2("process PID %d exiting", my_pid); + + /* Get ready to shutdown and exit */ + allowed_globals.exit_now = 1; + + for (i = 0; i < nthreads; i++) { + add_job(-1); + } + + APD2("process PID %d waiting for worker threads to exit", my_pid); + while(ap_thread_count) + ThreadSwitch(); + + destroy_semaphore(allowed_globals.jobsemaphore); + ap_destroy_mutex(allowed_globals.jobmutex); + + ap_child_exit_modules(pconf, server_conf); + ap_destroy_pool(pchild); + free(child_handles); + cleanup_scoreboard(); + + APD2("process PID %d exited", my_pid); + clean_parent_exit(0); + +} +#else +void worker_main(void) +{ + int nthreads; + fd_set main_fds; + int srv; + int clen; + int csd; + struct sockaddr_in sa_client; + int total_jobs = 0; + thread **child_handles; + int rv; + time_t end_time; + int i; + struct timeval tv; + int wait_time = 1; + int max_jobs_per_exe; + int max_jobs_after_exit_request; + HANDLE hObjects[2]; + + int count_select_errors = 0; + pool *pchild; + + pchild = ap_make_sub_pool(pconf); + + ap_standalone = 1; + sd = -1; + nthreads = ap_threads_per_child; + max_jobs_after_exit_request = ap_excess_requests_per_child; + max_jobs_per_exe = ap_max_requests_per_child; + if (nthreads <= 0) + nthreads = 40; + if (max_jobs_per_exe <= 0) + max_jobs_per_exe = 0; + if (max_jobs_after_exit_request <= 0) + max_jobs_after_exit_request = max_jobs_per_exe / 10; + + if (!one_process) + detach(); + + my_pid = getpid(); + + ++ap_my_generation; + + copy_listeners(pconf); + ap_restart_time = time(NULL); + + reinit_scoreboard(pconf); + + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. Also wait on exit_event, + * in case we (this child) is told to die before we get a chance to + * serve any requests. + */ + hObjects[0] = (HANDLE)start_mutex; + hObjects[1] = (HANDLE)exit_event; + rv = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); + if (rv == WAIT_FAILED) { + ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "Waiting for start_mutex or exit_event -- process will exit"); + + ap_destroy_pool(pchild); + cleanup_scoreboard(); + exit(1); + } + if (rv == WAIT_OBJECT_0 + 1) { + /* exit event signalled - exit now */ + ap_destroy_pool(pchild); + cleanup_scoreboard(); + exit(0); + } + /* start_mutex obtained, continue into the select() loop */ + if (one_process) { + setup_listeners(pconf); + } else { + /* Get listeners from the parent process */ + setup_inherited_listeners(pconf); + } + + if (listenmaxfd == -1) { + /* Help, no sockets were made, better log something and exit */ + ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, NULL, + "No sockets were created for listening"); + + signal_parent(0); /* tell parent to die */ + + ap_destroy_pool(pchild); + cleanup_scoreboard(); + exit(1); + } + set_signals(); + + /* + * - Initialize allowed_globals + * - Create the thread table + * - Spawn off threads + * - Create listen socket set (done above) + * - loop { + * wait for request + * create new job + * } while (!time to exit) + * - Close all listeners + * - Wait for all threads to complete + * - Exit + */ + + ap_child_init_modules(pconf, server_conf); + + allowed_globals.jobsemaphore = create_semaphore(0); + allowed_globals.jobmutex = ap_create_mutex(NULL); + + /* spawn off the threads */ + child_handles = (thread *) alloca(nthreads * sizeof(int)); + for (i = 0; i < nthreads; i++) { + child_handles[i] = create_thread((void (*)(void *)) child_main, (void *) i); + if (child_handles[i] == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "create_thread rc = %d", errno); + } + } + if (nthreads > max_daemons_limit) { + max_daemons_limit = nthreads; + } + + while (1) { + if (max_jobs_per_exe && (total_jobs > max_jobs_per_exe)) { + /* Reached MaxRequestsPerChild. Stop accepting new connections + * and signal the parent to start a new child process. + */ + ap_start_restart(1); + break; + } + /* Always check for the exit event being signaled. + */ + rv = WaitForSingleObject(exit_event, 0); + ap_assert((rv == WAIT_TIMEOUT) || (rv == WAIT_OBJECT_0)); + if (rv == WAIT_OBJECT_0) { + APD1("child: exit event signalled, exiting"); + break; + } + + tv.tv_sec = wait_time; + tv.tv_usec = 0; + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + + srv = ap_select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); + + if (srv == 0) { + count_select_errors = 0; /* reset count of errors */ + continue; + } + else if (srv == SOCKET_ERROR) { + if (h_errno != WSAEINTR) { + /* A "real" error occurred, log it and increment the count of + * select errors. This count is used to ensure we don't go into + * a busy loop of continuous errors. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, + "select failed with errno %d", h_errno); + count_select_errors++; + if (count_select_errors > MAX_SELECT_ERRORS) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, server_conf, + "Too many errors in select loop. Child process exiting."); + break; + } + } + continue; + } else { + listen_rec *lr; + + lr = find_ready_listener(&main_fds); + if (lr != NULL) { + sd = lr->fd; + } + } + do { + clen = sizeof(sa_client); + csd = accept(sd, (struct sockaddr *) &sa_client, &clen); + if (csd == INVALID_SOCKET) { + csd = -1; + } + } while (csd < 0 && h_errno == WSAEINTR); + + if (csd < 0) { + if (h_errno != WSAECONNABORTED) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "accept: (client socket) failed with errno = %d",h_errno); + } + } + else { + add_job(csd); + total_jobs++; + } + } + + APD2("process PID %d exiting", my_pid); + + /* Get ready to shutdown and exit */ + allowed_globals.exit_now = 1; + ap_release_mutex(start_mutex); + +#ifdef UNGRACEFUL_RESTART + SetEvent(allowed_globals.thread_exit_event); +#else + for (i = 0; i < nthreads; i++) { + add_job(-1); + } +#endif + + APD2("process PID %d waiting for worker threads to exit", my_pid); + /* Wait for all your children */ + end_time = time(NULL) + 180; + while (nthreads) { + rv = wait_for_many_objects(nthreads, child_handles, + end_time - time(NULL)); + if (rv != WAIT_TIMEOUT) { + rv = rv - WAIT_OBJECT_0; + ap_assert((rv >= 0) && (rv < nthreads)); + cleanup_thread(child_handles, &nthreads, rv); + continue; + } + break; + } + + APD2("process PID %d killing remaining worker threads", my_pid); + for (i = 0; i < nthreads; i++) { + kill_thread(child_handles[i]); + free_thread(child_handles[i]); + } +#ifdef UNGRACEFUL_RESTART + ap_assert(CloseHandle(allowed_globals.thread_exit_event)); +#endif + destroy_semaphore(allowed_globals.jobsemaphore); + ap_destroy_mutex(allowed_globals.jobmutex); + + ap_child_exit_modules(pconf, server_conf); + ap_destroy_pool(pchild); + + cleanup_scoreboard(); + + APD2("process PID %d exited", my_pid); + clean_parent_exit(0); +} /* standalone_main */ + +/* + * Spawn a child Apache process. The child process has the command line arguments from + * argc and argv[], plus a -Z argument giving the name of an event. The child should + * open and poll or wait on this event. When it is signalled, the child should die. + * prefix is a prefix string for the event name. + * + * The child_num argument on entry contains a serial number for this child (used to create + * a unique event name). On exit, this number will have been incremented by one, ready + * for the next call. + * + * On exit, the value pointed to be *ev will contain the event created + * to signal the new child process. + * + * The return value is the handle to the child process if successful, else -1. If -1 is + * returned the error will already have been logged by ap_log_error(). + */ + +/********************************************************************** + * master_main - this is the parent (main) process. We create a + * child process to do the work, then sit around waiting for either + * the child to exit, or a restart or exit signal. If the child dies, + * we just respawn a new one. If we have a shutdown or graceful restart, + * tell the child to die when it is ready. If it is a non-graceful + * restart, force the child to die immediately. + **********************************************************************/ + +#define MAX_PROCESSES 50 /* must be < MAX_WAIT_OBJECTS-1 */ + +static void cleanup_process(HANDLE *handles, HANDLE *events, int position, int *processes) +{ + int i; + int handle = 0; + + CloseHandle(handles[position]); + CloseHandle(events[position]); + + handle = (int)handles[position]; + + for (i = position; i < (*processes)-1; i++) { + handles[i] = handles[i + 1]; + events[i] = events[i + 1]; + } + (*processes)--; + + APD4("cleanup_processes: removed child in slot %d handle %d, max=%d", position, handle, *processes); +} + +static int create_process(pool *p, HANDLE *handles, HANDLE *events, + int *processes, int *child_num, char *kill_event_name, int argc, char **argv) +{ + + int rv, i; + HANDLE kill_event; + char buf[1024]; + char exit_event_name[40]; /* apPID_C# */ + char *pCommand; + + STARTUPINFO si; /* Filled in prior to call to CreateProcess */ + PROCESS_INFORMATION pi; /* filled in on call to CreateProces */ + LPWSAPROTOCOL_INFO lpWSAProtocolInfo; + listen_rec *lr; + DWORD BytesWritten; + HANDLE hPipeRead = NULL; + HANDLE hPipeWrite = NULL; + HANDLE hPipeWriteDup; + HANDLE hNullOutput = NULL; + HANDLE hShareError = NULL; + HANDLE hCurrentProcess; + SECURITY_ATTRIBUTES sa = {0}; + + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + /* Build the command line. Should look something like this: + * C:/apache/bin/apache.exe -Z exit_event -f ap_server_confname + * First, get the path to the executable... + */ + rv = GetModuleFileName(NULL, buf, sizeof(buf)); + if (rv == sizeof(buf)) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: Path to Apache process too long"); + return -1; + } else if (rv == 0) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: GetModuleFileName() returned NULL for current process."); + return -1; + } + + /* Create the exit event (apPID_C#). Parent signals this event to tell the + * child to exit + */ + ap_snprintf(exit_event_name, sizeof(exit_event_name), "%s_C%d", kill_event_name, ++(*child_num)); + kill_event = CreateEvent(NULL, TRUE, FALSE, exit_event_name); + if (!kill_event) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: Could not create exit event for child process"); + return -1; + } + + /* service children must be created with the -z option, + * while console mode (interactive apache) children are created + * with the -Z option + */ + pCommand = ap_psprintf(p, "\"%s\" -%c %s -f \"%s\"", buf, + isProcessService() ? 'z' : 'Z', + exit_event_name, ap_server_confname); + + for (i = 1; i < argc; i++) { + if ((argv[i][0] == '-') && ((argv[i][1] == 'k') || (argv[i][1] == 'n'))) + ++i; + else + pCommand = ap_pstrcat(p, pCommand, " \"", argv[i], "\"", NULL); + } + + /* Create a pipe to send socket info to the child */ + if (!CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0)) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: Unable to create pipe to child process.\n"); + return -1; + } + + /* Open a null handle to soak info from the child */ + hNullOutput = CreateFile("nul", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, OPEN_EXISTING, 0, NULL); + if (hNullOutput == INVALID_HANDLE_VALUE) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: Unable to create null output pipe for child process.\n"); + return -1; + } + + /* Child's initial stderr -> our main server error log (or, failing that, stderr) */ + if (server_conf->error_log) { + hShareError = (HANDLE)_get_osfhandle(fileno(server_conf->error_log)); + if (hShareError == INVALID_HANDLE_VALUE) { + hShareError = GetStdHandle(STD_ERROR_HANDLE); + } + } + + hCurrentProcess = GetCurrentProcess(); + if (DuplicateHandle(hCurrentProcess, hPipeWrite, hCurrentProcess, + &hPipeWriteDup, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + CloseHandle(hPipeWrite); + hPipeWrite = hPipeWriteDup; + } + + /* Give the read in of the pipe (hPipeRead) to the child as stdin. The + * parent will write the socket data to the child on this pipe. + */ + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdInput = hPipeRead; + si.hStdOutput = hNullOutput; + si.hStdError = hShareError; + + if (!CreateProcess(NULL, pCommand, NULL, NULL, + TRUE, /* Inherit handles */ + 0, /* Creation flags */ + NULL, NULL, + &si, &pi)) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: Not able to create the child process."); + /* + * We must close the handles to the new process and its main thread + * to prevent handle and memory leaks. + */ + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + CloseHandle(hPipeRead); + CloseHandle(hPipeWrite); + CloseHandle(hNullOutput); + + return -1; + } + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, server_conf, + "Parent: Created child process %d", pi.dwProcessId); + + /* Assume the child process lives. Update the process and event tables */ + handles[*processes] = pi.hProcess; + events[*processes] = kill_event; + (*processes)++; + + /* We never store the thread's handle, so close it now. */ + CloseHandle(pi.hThread); + + /* Run the chain of open sockets. For each socket, duplicate it + * for the target process then send the WSAPROTOCOL_INFO + * (returned by dup socket) to the child */ + lr = ap_listeners; + while (lr != NULL) { + lpWSAProtocolInfo = ap_pcalloc(p, sizeof(WSAPROTOCOL_INFO)); + ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, server_conf, + "Parent: Duplicating socket %d and sending it to child process %d", lr->fd, pi.dwProcessId); + if (WSADuplicateSocket(lr->fd, + pi.dwProcessId, + lpWSAProtocolInfo) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: WSADuplicateSocket failed for socket %d.", lr->fd ); + return -1; + } + + if (!WriteFile(hPipeWrite, lpWSAProtocolInfo, (DWORD) sizeof(WSAPROTOCOL_INFO), + &BytesWritten, + (LPOVERLAPPED) NULL)) { + ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf, + "Parent: Unable to write duplicated socket %d to the child.", lr->fd ); + return -1; + } + + lr = lr->next; + if (lr == ap_listeners) + break; + } + } + CloseHandle(hPipeRead); + CloseHandle(hPipeWrite); + CloseHandle(hNullOutput); + + return 0; +} + +/* To share the semaphores with other processes, we need a NULL ACL + * Code from MS KB Q106387 + */ + +static PSECURITY_ATTRIBUTES GetNullACL() +{ + PSECURITY_DESCRIPTOR pSD; + PSECURITY_ATTRIBUTES sa; + + sa = (PSECURITY_ATTRIBUTES) LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES)); + pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, + SECURITY_DESCRIPTOR_MIN_LENGTH); + if (pSD == NULL || sa == NULL) { + return NULL; + } + /* + * Win98 returns nonzero on failure; check LastError to make sure. + */ + SetLastError(0); + if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) + || GetLastError()) { + LocalFree( pSD ); + LocalFree( sa ); + return NULL; + } + if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL) NULL, FALSE) + || GetLastError()) { + LocalFree( pSD ); + LocalFree( sa ); + return NULL; + } + sa->nLength = sizeof(sa); + sa->lpSecurityDescriptor = pSD; + sa->bInheritHandle = TRUE; + return sa; +} + + +static void CleanNullACL( void *sa ) { + if( sa ) { + LocalFree( ((PSECURITY_ATTRIBUTES)sa)->lpSecurityDescriptor); + LocalFree( sa ); + } +} + +int master_main(int argc, char **argv) +{ + /* returns NULL if invalid (Win95?) */ + PSECURITY_ATTRIBUTES sa = GetNullACL(); + int nchild = ap_daemons_to_start; + int child_num = 0; + int rv, cld; + char signal_prefix_string[100]; + int i; + time_t tmstart; + HANDLE signal_shutdown_event; /* used to signal shutdown to parent */ + HANDLE signal_restart_event; /* used to signal a restart to parent */ + HANDLE process_handles[MAX_PROCESSES]; + HANDLE process_kill_events[MAX_PROCESSES]; + int current_live_processes = 0; /* number of child process we know about */ + int processes_to_create = 0; /* number of child processes to create */ + pool *pparent = NULL; /* pool for the parent process. Cleaned on each restart */ + + nchild = 1; /* only allowed one child process for current generation */ + processes_to_create = nchild; + + is_graceful = 0; + + ap_snprintf(signal_prefix_string, sizeof(signal_prefix_string), + "ap%d", getpid()); + setup_signal_names(signal_prefix_string); + + /* Create shutdown event, apPID_shutdown, where PID is the parent + * Apache process ID. Shutdown is signaled by 'apache -k shutdown'. + */ + signal_shutdown_event = CreateEvent(sa, TRUE, FALSE, signal_shutdown_name); + if (!signal_shutdown_event) { + ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, + "master_main: Cannot create shutdown event %s", signal_shutdown_name); + CleanNullACL((void *)sa); + exit(1); + } + + /* Create restart event, apPID_restart, where PID is the parent + * Apache process ID. Restart is signaled by 'apache -k restart'. + */ + signal_restart_event = CreateEvent(sa, TRUE, FALSE, signal_restart_name); + if (!signal_restart_event) { + CloseHandle(signal_shutdown_event); + ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, + "master_main: Cannot create restart event %s", signal_restart_name); + CleanNullACL((void *)sa); + exit(1); + } + CleanNullACL((void *)sa); + + /* Create the start mutex, apPID, where PID is the parent Apache process ID. + * Ths start mutex is used during a restart to prevent more than one + * child process from entering the accept loop at once. + */ + start_mutex = ap_create_mutex(signal_prefix_string); + restart_pending = shutdown_pending = 0; + + do { /* restart-pending */ + if (!is_graceful) { + ap_restart_time = time(NULL); + } + copy_listeners(pconf); + ap_clear_pool(pconf); + pparent = ap_make_sub_pool(pconf); + + server_conf = ap_read_config(pconf, pparent, ap_server_confname); + setup_listeners(pconf); + ap_clear_pool(plog); + ap_open_logs(server_conf, plog); + ap_set_version(); + ap_init_modules(pconf, server_conf); + version_locked++; + service_set_status(SERVICE_START_PENDING); + /* Create child processes */ + while (processes_to_create--) { + if (create_process(pconf, process_handles, process_kill_events, + ¤t_live_processes, &child_num, signal_prefix_string, argc, argv) < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "master_main: create child process failed. Exiting."); + goto die_now; + } + } + service_set_status(SERVICE_RUNNING); + restart_pending = shutdown_pending = 0; + + /* Wait for either the shutdown or restart events to be signaled */ + process_handles[current_live_processes] = signal_shutdown_event; + process_handles[current_live_processes+1] = signal_restart_event; + rv = WaitForMultipleObjects(current_live_processes+2, (HANDLE *)process_handles, + FALSE, INFINITE); + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_WIN32ERROR, server_conf, + "master_main: : WaitForMultipeObjects on process handles and apache-signal -- doing shutdown"); + shutdown_pending = 1; + break; + } + if (rv == WAIT_TIMEOUT) { + /* Hey, this cannot happen */ + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "master_main: WaitForMultipeObjects with INFINITE wait exited with WAIT_TIMEOUT"); + shutdown_pending = 1; + } + + cld = rv - WAIT_OBJECT_0; + APD4("main process: wait finished, cld=%d handle %d (max=%d)", cld, process_handles[cld], current_live_processes); + if (cld == current_live_processes) { + /* apPID_shutdown event signalled, we should exit now */ + shutdown_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "master_main: Shutdown event signaled. Shutting the server down."); + if (ResetEvent(signal_shutdown_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "ResetEvent(signal_shutdown_event)"); + } + /* Signal each child processes to die */ + for (i = 0; i < current_live_processes; i++) { + APD3("master_main: signalling child %d, handle %d to die", i, process_handles[i]); + if (SetEvent(process_kill_events[i]) == 0) + ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "master_main: SetEvent for child process in slot #%d failed", i); + } + break; + } else if (cld == current_live_processes+1) { + /* apPID_restart event signalled. + * Signal the child to shutdown and start a new child process. + * The restart event can be signaled by a command line restart or + * by the child process when it handles MaxRequestPerChild connections. + */ + int children_to_kill = current_live_processes; + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "master_main: Restart event signaled. Doing a graceful restart."); + if (ResetEvent(signal_restart_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "master_main: ResetEvent(signal_restart_event) failed."); + } + /* Signal each child process to die */ + for (i = 0; i < children_to_kill; i++) { + APD3("master_main: signalling child #%d handle %d to die", i, process_handles[i]); + if (SetEvent(process_kill_events[i]) == 0) + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "master_main: SetEvent for child process in slot #%d failed", i); + /* Remove the process (and event) from the process table */ + cleanup_process(process_handles, process_kill_events, i, ¤t_live_processes); + } + processes_to_create = 1; + ++ap_my_generation; + continue; + } else { + /* The child process exited premeturely because of a fatal error condition + * (eg, seg fault). Cleanup and restart the child process. + */ + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, + "master_main: Child processed exited prematurely. Restarting the child process."); + ap_assert(cld < current_live_processes); + cleanup_process(process_handles, process_kill_events, cld, ¤t_live_processes); + APD2("main_process: child in slot %d died", rv); + processes_to_create = 1; + continue; + } + + } while (1); + + /* If we dropped out of the loop we definitly want to die completely. We need to + * make sure we wait for all the child process to exit first. + */ + + APD2("*** main process shutdown, processes=%d ***", current_live_processes); + +die_now: + + tmstart = time(NULL); + while (current_live_processes && ((tmstart+60) > time(NULL))) { + service_set_status(SERVICE_STOP_PENDING); + rv = WaitForMultipleObjects(current_live_processes, (HANDLE *)process_handles, FALSE, 2000); + if (rv == WAIT_TIMEOUT) + continue; + ap_assert(rv != WAIT_FAILED); + cld = rv - WAIT_OBJECT_0; + ap_assert(rv < current_live_processes); + APD4("main_process: child in #%d handle %d died, left=%d", + rv, process_handles[rv], current_live_processes); + cleanup_process(process_handles, process_kill_events, cld, ¤t_live_processes); + } + for (i = 0; i < current_live_processes; i++) { + ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, server_conf, + "forcing termination of child #%d (handle %d)", i, process_handles[i]); + TerminateProcess((HANDLE) process_handles[i], 1); + } + + CloseHandle(signal_restart_event); + CloseHandle(signal_shutdown_event); + + /* cleanup pid file on normal shutdown */ + { + const char *pidfile = NULL; + pidfile = ap_server_root_relative (pparent, ap_pid_fname); + if ( pidfile != NULL && unlink(pidfile) == 0) + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, + server_conf, + "removed PID file %s (pid=%ld)", + pidfile, (long)getpid()); + } + + if (pparent) { + ap_destroy_pool(pparent); + } + + ap_destroy_mutex(start_mutex); + return (0); +} +#endif + +/* + * Send signal to a running Apache. On entry signal should contain + * either "shutdown" or "restart" + */ + +int send_signal(pool *p, char *signal) +{ + char prefix[20]; + FILE *fp; + int nread; + char *fname; + int end; + + fname = ap_server_root_relative (p, ap_pid_fname); + + fp = fopen(fname, "r"); + if (!fp) { + printf("Cannot read apache PID file %s\n", fname); + return FALSE; + } + prefix[0] = 'a'; + prefix[1] = 'p'; + + nread = fread(prefix+2, 1, sizeof(prefix)-3, fp); + if (nread == 0) { + fclose(fp); + printf("PID file %s was empty\n", fname); + return FALSE; + } + fclose(fp); + + /* Terminate the prefix string */ + end = 2 + nread - 1; + while (end > 0 && (prefix[end] == '\r' || prefix[end] == '\n')) + end--; + prefix[end + 1] = '\0'; + + setup_signal_names(prefix); + + if (!strcasecmp(signal, "shutdown")) + ap_start_shutdown(); + else if (!strcasecmp(signal, "restart")) + ap_start_restart(1); + else { + printf("Unknown signal name \"%s\". Use either shutdown or restart.\n", + signal); + return FALSE; + } + return TRUE; +} + +void post_parse_init() +{ + ap_set_version(); + ap_init_modules(pconf, server_conf); + ap_suexec_enabled = init_suexec(); + version_locked++; + ap_open_logs(server_conf, plog); + set_group_privs(); +} + + +#ifdef NETWARE +extern char *optarg; + +void signal_handler(int sig) +{ + switch (sig) { + case SIGTERM: + shutdown_pending = 1; + + while(!ap_main_finished) + ThreadSwitchWithDelay(); + + break; + } + return; +} +#endif + +#if defined(NETWARE) +int apache_main(int argc, char *argv[]) +#elif defined(WIN32) + __declspec(dllexport) +int apache_main(int argc, char *argv[]) +#else +int REALMAIN(int argc, char *argv[]) +#endif +{ + int c; + int child = 0; + char *cp; + char *s; + int conf_specified = 0; + +#ifdef WIN32 + jmp_buf reparse_args; + char *service_name = NULL; + int install = 0; + int reparsed = 0; + int is_child_of_service = 0; + char *signal_to_send = NULL; + + /* Service application under WinNT the first time through only... + * service_main immediately resets real_exit_code to zero + */ + if (real_exit_code && isWindowsNT()) + { + if (((argc == 1) && isProcessService()) + || ((argc == 2) && !strcmp(argv[1], "--ntservice"))) + { + service_main(apache_main, argc, argv); + /* this was the end of the service control thread... + * cleanups already ran when second thread of apache_main + * terminated, so simply... + */ + exit(0); + } + } + + /* This behavior is voided by setting real_exit_code to 0 */ + atexit(hold_console_open_on_error); +#endif + +#ifdef NETWARE + int currentScreen = GetCurrentScreen(); + /* If top_module is not NULL then APACHEC was not exited cleanly + * and is in a bad state. Simply clean up and exit. + */ + check_clean_load (top_module); + init_name_space(); + signal(SIGTERM, signal_handler); + atexit(clean_shutdown_on_exit); + init_tsd(); +#endif + + /* Console application or a child process. */ + + if ((s = strrchr(argv[0], PATHSEPARATOR)) != NULL) { + ap_server_argv0 = ++s; + } + else { + ap_server_argv0 = argv[0]; + } + + common_init(); + ap_setup_prelinked_modules(); + + /* initialize ap_server_root to the directory of the executable, in case + * the user chooses a relative path for the -d serverroot arg a bit later + */ + +#ifdef NETWARE + if(!*ap_server_root) { + ap_cpystrn(ap_server_root, bslash2slash(remove_filename(argv[0])), + sizeof(ap_server_root)); + } +#endif + +#ifdef WIN32 + if(!*ap_server_root) { + if (GetModuleFileName(NULL, ap_server_root, sizeof(ap_server_root))) { + ap_cpystrn(ap_server_root, + ap_os_canonical_filename(pcommands, ap_server_root), + sizeof(ap_server_root)); + if (ap_os_is_path_absolute(ap_server_root) + && strchr(ap_server_root, '/')) + *strrchr(ap_server_root, '/') = '\0'; + else + *ap_server_root = '\0'; + } + } +#endif + + /* Fallback position if argv[0] wasn't deciphered + */ + if (!*ap_server_root) + ap_cpystrn(ap_server_root, HTTPD_ROOT, sizeof(ap_server_root)); + + chdir (ap_server_root); + +#ifdef WIN32 + /* If this is a service, we will need to fall back here and + * reparse the entire options list. + */ + if (setjmp(reparse_args)) { + /* Reset and reparse the command line */ + ap_server_pre_read_config = ap_make_array(pcommands, 1, sizeof(char *)); + ap_server_post_read_config = ap_make_array(pcommands, 1, sizeof(char *)); + ap_server_config_defines = ap_make_array(pcommands, 1, sizeof(char *)); + + /* Reset optreset and optind to allow getopt to work correctly + * the second time around, and assure we never come back here. + */ + optreset = 1; + optind = 1; + reparsed = 1; + } + + while ((c = getopt(argc, argv, "D:C:c:Xd:f:vVlLz:Z:wiuStThk:n:W:")) != -1) { +#else /* !WIN32 */ + while ((c = getopt(argc, argv, "D:C:c:Xd:Ff:vVlLesStTh")) != -1) { +#endif + char **new; + switch (c) { + case 'c': + new = (char **)ap_push_array(ap_server_post_read_config); + *new = ap_pstrdup(pcommands, optarg); + break; + case 'C': + new = (char **)ap_push_array(ap_server_pre_read_config); + *new = ap_pstrdup(pcommands, optarg); + break; + case 'D': + new = (char **)ap_push_array(ap_server_config_defines); + *new = ap_pstrdup(pcommands, optarg); + break; +#ifdef WIN32 + /* Shortcuts; include the -w option to hold the window open on error. + * This must not be toggled once we reset real_exit_code to 0! + */ + case 'w': + if (real_exit_code) + real_exit_code = 2; + break; + /* service children must be created with the -z option, + * while console mode (interactive apache) children are created + * with the -Z option + */ + case 'z': + is_child_of_service = 1; + case 'Z': + /* Prevent holding open the (nonexistant) console */ + real_exit_code = 0; + exit_event = open_event(optarg); + APD2("child: opened process event %s", optarg); + cp = strchr(optarg, '_'); + ap_assert(cp); + *cp = 0; + setup_signal_names(optarg); + start_mutex = ap_open_mutex(signal_name_prefix); + ap_assert(start_mutex); + child = 1; + break; + case 'n': + service_name = ap_pstrdup(pcommands, optarg); + break; + case 'i': + install = 2; + break; + case 'u': + install = -1; + break; + case 'k': + if (!strcasecmp(optarg, "stop")) + signal_to_send = "shutdown"; + else if (!strcasecmp(optarg, "install")) + install = 2; + else if (!strcasecmp(optarg, "config")) + install = 1; + else if (!strcasecmp(optarg, "uninstall")) + install = -1; + else + signal_to_send = optarg; + break; + case 'W': + /* -With a dependent service */ + if (install < 1) { + fprintf(stderr, "%s: invalid option: -W %s ignored\n" + "\t-W only modifies -k install or -k config\n", + argv[0], optarg); + } + else if (!isWindowsNT()) { + fprintf(stderr, "%s: invalid option: -W %s ignored\n" + "\t-W is only supported for Windows NT/2000\n", + argv[0], optarg); + } + break; +#endif /* WIN32 */ +#ifdef NETWARE + case 'e': + { + int screenHandle; + + /* Get a screen handle for the console screen. */ + if ((screenHandle = CreateScreen("System Console", 0)) != NULL) + { + SetAutoScreenDestructionMode(1); + SetCurrentScreen(screenHandle); /* switch to console screen I/O */ + } + } + break; + case 's': + if (DestroyScreen(GetCurrentScreen()) == 0) + { + int screenHandle; + + /* Create a screen handle for the console screen, + even though the console screen exists. */ + if ((screenHandle = CreateScreen("System Console", 0)) != NULL) + { + SetCurrentScreen(screenHandle); /* switch to console screen I/O */ + currentScreen = GetCurrentScreen(); + } + } + break; +#endif + case 'S': + ap_dump_settings = 1; + break; + case 'd': + optarg = ap_os_canonical_filename(pcommands, optarg); + if (!ap_os_is_path_absolute(optarg)) { + optarg = ap_pstrcat(pcommands, ap_server_root, "/", + optarg, NULL); + } + ap_cpystrn(ap_server_root, optarg, sizeof(ap_server_root)); + ap_getparents(ap_server_root); + ap_no2slash(ap_server_root); + if (ap_server_root[0] + && ap_server_root[strlen(ap_server_root) - 1] == '/') + ap_server_root[strlen(ap_server_root) - 1] = '\0'; + break; +#ifndef WIN32 + case 'F': + do_detach = 0; + break; +#endif + case 'f': + ap_cpystrn(ap_server_confname, + ap_os_canonical_filename(pcommands, optarg), + sizeof(ap_server_confname)); + conf_specified = 1; + break; + case 'v': + ap_set_version(); + printf("Server version: %s\n", ap_get_server_version()); + printf("Server built: %s\n", ap_get_server_built()); +#ifdef WIN32 + clean_parent_exit(1); +#else + clean_parent_exit(0); +#endif + + case 'V': + ap_set_version(); + show_compile_settings(); +#ifdef WIN32 + clean_parent_exit(1); +#else + clean_parent_exit(0); +#endif + + case 'l': + ap_show_modules(); +#ifdef WIN32 + clean_parent_exit(1); +#else + clean_parent_exit(0); +#endif + + case 'L': + ap_show_directives(); +#ifdef WIN32 + clean_parent_exit(1); +#else + clean_parent_exit(0); +#endif + + case 'X': + ++one_process; /* Weird debugging mode. */ + break; + case 't': + ap_configtestonly = 1; + ap_docrootcheck = 1; + break; + case 'T': + ap_configtestonly = 1; + ap_docrootcheck = 0; + break; + case 'h': + usage(ap_server_argv0); + case '?': + usage(ap_server_argv0); + } /* switch */ +#ifdef NETWARE + ThreadSwitch(); +#endif + } /* while */ + +#ifdef WIN32 + + if (!service_name && install) { + service_name = DEFAULTSERVICENAME; + } + + if (service_name) { + service_name = get_display_name(service_name); + } + + if (service_name && isValidService(service_name)) + { + if (install == 2) { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL, + "Service \"%s\" is already installed!", service_name); + clean_parent_exit(1); + } + /* Don't proceed if we are configuring, uninstalling + * or already merged and reparsed the service args + */ + if (!install && !reparsed) + { + int svcargc; + char **newargv, **svcargv; + if (ap_configtestonly) + fprintf(stderr, "Default command options for service %s:\n", + service_name); + + /* Merge the service's default args */ + if (ap_registry_get_service_args(pcommands, &svcargc, &svcargv, + service_name) > 0) { + newargv = (char**)malloc((svcargc + argc + 1) * sizeof(char*)); + newargv[0] = argv[0]; /* The true executable name */ + memcpy(newargv + 1, svcargv, svcargc * sizeof(char*)); + memcpy(newargv + 1 + svcargc, argv + 1, + (argc - 1) * sizeof(char*)); + argc += svcargc; /* Add the startup options args */ + argv = newargv; + argv[argc] = NULL; + + if (ap_configtestonly) { + while (svcargc-- > 0) { + if ((**svcargv == '-') && strchr("dfDCc", svcargv[0][1]) + && svcargc) { + fprintf(stderr, " %s %s\n", + *svcargv, *(svcargv + 1)); + svcargv += 2; --svcargc; + } + else + fprintf(stderr, " %s\n", *(svcargv++)); + } + } + /* Run through the command line args all over again */ + longjmp(reparse_args, 1); + } + else if (ap_configtestonly) + fprintf (stderr, " (none)\n"); + } + } + else if (service_name && (install <= 1)) + { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL, + "Service \"%s\" is not installed!", service_name); + clean_parent_exit(1); + } +#endif + + /* ServerRoot/ServerConfFile are found in this order: + * (1) serverroot set to Apache.exe's path, or HTTPD_ROOT if unparsable + * (2) arguments are grabbed for the -n named service, if given + * (3) the -d argument is taken from the given command line + * (4) the -d argument is taken from the service's default args + * (5) the -f argument is taken from the given command line + * (6) the -f argument is taken from the service's default args + * (7) if -f is omitted, then initialized to SERVER_CONFIG_FILE + * (8) if ap_server_confname is not absolute, then merge it to serverroot + */ + + if (!conf_specified) + ap_cpystrn(ap_server_confname, SERVER_CONFIG_FILE, sizeof(ap_server_confname)); + + if (!ap_os_is_path_absolute(ap_server_confname)) + ap_cpystrn(ap_server_confname, + ap_server_root_relative(pcommands, ap_server_confname), + sizeof(ap_server_confname)); + ap_getparents(ap_server_confname); + ap_no2slash(ap_server_confname); + +#ifdef WIN32 + /* Read the conf now unless we are uninstalling the service, + * or shutting down a running service + * (but do read the conf for the pidfile if we shutdown the console) + */ + if ((install >= 0) && (!service_name || !signal_to_send + || strcasecmp(signal_to_send,"shutdown"))) { + server_conf = ap_read_config(pconf, ptrans, ap_server_confname); + } + + if (install) { + if (install > 0) + InstallService(pconf, service_name, argc, argv, install == 1); + else + RemoveService(service_name); + clean_parent_exit(0); + } + + /* All NT signals, and all but the 9x start signal are handled entirely. + * Die if we failed, are on NT, or are not "start"ing the service + */ + if (service_name && signal_to_send) { + if (send_signal_to_service(service_name, signal_to_send, argc, argv)) + clean_parent_exit(0); + if (isWindowsNT() || strcasecmp(signal_to_send, "start")) + clean_parent_exit(1); + /* Still here? Then we are hanging around to detach the console + * and use this process as the Windows 9x service. + */ + } +#else /* ndef WIN32 */ + server_conf = ap_read_config(pconf, ptrans, ap_server_confname); +#endif + + if (ap_configtestonly) { + fprintf(stderr, "%s: Syntax OK\n", ap_server_root_relative(pcommands, ap_server_confname)); +#ifdef WIN32 + clean_parent_exit(1); +#else + clean_parent_exit(0); +#endif + } + + if (ap_dump_settings) { +#ifdef WIN32 + clean_parent_exit(1); +#else + clean_parent_exit(0); +#endif + } + +#ifdef WIN32 + /* Non-service Signals. (Ignore -k start for now [with or without -n arg]) */ + if (signal_to_send && strcasecmp(signal_to_send, "start")) { + send_signal(pconf, signal_to_send); + clean_parent_exit(0); + } +#endif + +#ifndef NETWARE + if (!child && !ap_dump_settings) { + ap_log_pid(pconf, ap_pid_fname); + } +#endif + + post_parse_init(); + +#if defined(OS2) + printf("%s running...\n", ap_get_server_version()); +#elif defined(WIN32) + if (!child) { + printf("%s running...\n", ap_get_server_version()); + } +#elif defined(NETWARE) + if (currentScreen != GetCurrentScreen()) { + SetCurrentScreen(currentScreen); /* switch to console screen I/O */ + SetAutoScreenDestructionMode(0); + } + + printf("%s running...\n", ap_get_server_version()); +#endif + +#ifndef NETWARE + if (one_process && !exit_event) + exit_event = create_event(0, 0, NULL); + if (one_process && !start_mutex) + start_mutex = ap_create_mutex(NULL); +#endif + +#ifdef NETWARE + worker_main(); + destroy_semaphore(allowed_globals.jobsemaphore); + + while((ap_thread_count) || (!shutdown_pending)) + ThreadSwitchWithDelay(); +#else + /* + * In the future, the main will spawn off a couple + * of children and monitor them. As soon as a child + * exits, it spawns off a new one + */ + if (child || one_process) { + if (!exit_event || !start_mutex) + exit(-1); +#ifdef WIN32 + if (child) + ap_start_child_console(is_child_of_service); + else + ap_start_console_monitor(); +#endif + worker_main(); + ap_destroy_mutex(start_mutex); + destroy_event(exit_event); + } +#ifdef WIN32 + /* Windows NT service second time around ... we have all the overrides + * from the NT SCM, so go to town and return to the SCM when we quit. + */ + if (isWindowsNT() && isProcessService()) + { + master_main(argc, argv); + } + else if (service_name && signal_to_send && !isWindowsNT() + && !strcasecmp(signal_to_send, "start")) { + /* service95_main will call master_main() */ + service95_main(master_main, argc, argv, service_name); + } + else + { + /* Let's go fishing for some signals including ctrl+c, ctrl+break, + * logoff, close and shutdown, while the server is running + */ + ap_start_console_monitor(); + master_main(argc, argv); + } +#else /* ndef WIN32 */ + else + { + master_main(argc, argv); + } +#endif /* ndef WIN32 */ +#endif /* ndef NETWARE */ + + clean_parent_exit(0); + return 0; /* purely to avoid a warning */ +} + +#endif /* ndef MULTITHREAD */ + +#else /* ndef SHARED_CORE_TIESTATIC */ + +/* +** Standalone Tie Program for Shared Core support +** +** It's purpose is to tie the static libraries and +** the shared core library under link-time and +** passing execution control to the real main function +** in the shared core library under run-time. +*/ + +extern int ap_main(int argc, char *argv[]); + +int main(int argc, char *argv[]) +{ + return ap_main(argc, argv); +} + +#endif /* ndef SHARED_CORE_TIESTATIC */ +#else /* ndef SHARED_CORE_BOOTSTRAP */ + +#if defined(OS2) || defined(CYGWIN) +/* Shared core loader for OS/2 and Cygwin */ + +#if defined(CYGWIN) +__declspec(dllimport) +#endif + + +int ap_main(int argc, char *argv[]); /* Load time linked from cyghttpd.dll */ + +int main(int argc, char *argv[]) +{ + return ap_main(argc, argv); +} + +#else + +/* +** Standalone Bootstrap Program for Shared Core support +** +** It's purpose is to initialise the LD_LIBRARY_PATH +** environment variable therewith the Unix loader is able +** to start the Standalone Tie Program (see above) +** and then replacing itself with this program by +** immediately passing execution to it. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ap_config.h" +#include "httpd.h" + +#if defined(HPUX) || defined(HPUX10) || defined(HPUX11) +#define VARNAME "SHLIB_PATH" +#else +#define VARNAME "LD_LIBRARY_PATH" +#endif + +#ifndef SHARED_CORE_DIR +#define SHARED_CORE_DIR HTTPD_ROOT "/libexec" +#endif + +#ifndef SHARED_CORE_EXECUTABLE_PROGRAM +#define SHARED_CORE_EXECUTABLE_PROGRAM "lib" TARGET ".ep" +#endif + +extern char *optarg; +extern int optind; + +int main(int argc, char *argv[], char *envp[]) +{ + char prog[MAX_STRING_LEN]; + char llp_buf[MAX_STRING_LEN]; + char **llp_slot; + char *llp_existing; + char *llp_dir; + char **envpnew; + int c, i, l; + +#ifdef MPE + /* + * MPE doesn't currently initialize the envp parameter. Instead, we must + * use the global variable environ. + */ + envp = environ; +#endif + + /* + * parse argument line, + * but only handle the -L option + */ + llp_dir = SHARED_CORE_DIR; + while ((c = getopt(argc, argv, "D:C:c:Xd:Ff:vVlLR:SZ:tTh")) != -1) { + switch (c) { + case 'D': + case 'C': + case 'c': + case 'X': + case 'd': + case 'F': + case 'f': + case 'v': + case 'V': + case 'l': + case 'L': + case 'S': + case 'Z': + case 't': + case 'T': + case 'h': + case '?': + break; + case 'R': + llp_dir = strdup(optarg); + break; + } + } + +#ifdef MPE + /* + * MPE doesn't currently initialize the envp parameter. Instead, we must + * use the global variable environ. + */ + envp = environ; +#endif + + /* + * create path to SHARED_CORE_EXECUTABLE_PROGRAM + */ + ap_snprintf(prog, sizeof(prog), "%s/%s", llp_dir, SHARED_CORE_EXECUTABLE_PROGRAM); + + /* + * adjust process environment therewith the Unix loader + * is able to start the SHARED_CORE_EXECUTABLE_PROGRAM. + */ + llp_slot = NULL; + llp_existing = NULL; + l = strlen(VARNAME); + for (i = 0; envp[i] != NULL; i++) { + if (strncmp(envp[i], VARNAME "=", l+1) == 0) { + llp_slot = &envp[i]; + llp_existing = strchr(envp[i], '=') + 1; + } + } + if (llp_slot == NULL) { + envpnew = (char **)malloc(sizeof(char *)*(i + 2)); + if (envpnew == NULL) { + fprintf(stderr, "Ouch! Out of memory generating envpnew!\n"); + } + memcpy(envpnew, envp, sizeof(char *)*i); + envp = envpnew; + llp_slot = &envp[i++]; + envp[i] = NULL; + } + if (llp_existing != NULL) + ap_snprintf(llp_buf, sizeof(llp_buf), "%s=%s:%s", VARNAME, llp_dir, llp_existing); + else + ap_snprintf(llp_buf, sizeof(llp_buf), "%s=%s", VARNAME, llp_dir); + *llp_slot = strdup(llp_buf); + + /* + * finally replace our process with + * the SHARED_CORE_EXECUTABLE_PROGRAM + */ + if (execve(prog, argv, envp) == -1) { + fprintf(stderr, + "%s: Unable to exec Shared Core Executable Program `%s'\n", + argv[0], prog); + return 1; + } + else + return 0; +} + +#endif /* def OS2 */ +#endif /* ndef SHARED_CORE_BOOTSTRAP */ + +#ifndef SHARED_CORE_BOOTSTRAP +#include "httpd.h" +/* + * Force ap_validate_password() into the image so that modules like + * mod_auth can use it even if they're dynamically loaded. + */ +void suck_in_ap_validate_password(void); +void suck_in_ap_validate_password(void) +{ + ap_validate_password("a", "b"); +} +#endif + +/* force Expat to be linked into the server executable */ +#if defined(USE_EXPAT) && !defined(SHARED_CORE_BOOTSTRAP) +#include "xmlparse.h" +const XML_LChar *suck_in_expat(void); +const XML_LChar *suck_in_expat(void) +{ + return XML_ErrorString(XML_ERROR_NONE); +} +#endif /* USE_EXPAT */ + |