blob: 5bd372bbc4b7f6e7607f50905387fbe1aad98d1f [file] [log] [blame]
/* ====================================================================
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see <http://www.apache.org/>.
*
*/
/*
* 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)
#include <netinet/tcp.h>
#endif
#ifdef HAVE_BSTRING_H
#include <bstring.h> /* for IRIX, FD_SET calls bzero() */
#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.
*/
extern void ap_force_library_loading(void);
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?)
*/
int ap_standalone=0;
int ap_configtestonly=0;
int ap_docrootcheck=1;
uid_t ap_user_id=0;
char *ap_user_name=NULL;
gid_t ap_group_id=0;
#ifdef MULTIPLE_GROUPS
gid_t group_id_list[NGROUPS_MAX];
#endif
int ap_max_requests_per_child=0;
int ap_threads_per_child=0;
int ap_excess_requests_per_child=0;
char *ap_pid_fname=NULL;
char *ap_scoreboard_fname=NULL;
char *ap_lock_fname;
char *ap_server_argv0=NULL;
struct in_addr ap_bind_address;
int ap_daemons_to_start=0;
int ap_daemons_min_free=0;
int ap_daemons_max_free=0;
int ap_daemons_limit=0;
time_t ap_restart_time=0;
int ap_suexec_enabled = 0;
int ap_listenbacklog;
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;
static listen_rec *head_listener;
API_VAR_EXPORT char ap_server_root[MAX_STRING_LEN]="";
char ap_server_confname[MAX_STRING_LEN]="";
char ap_coredump_dir[MAX_STRING_LEN];
array_header *ap_server_pre_read_config;
array_header *ap_server_post_read_config;
array_header *ap_server_config_defines;
/* *Non*-shared http_main globals... */
static server_rec *server_conf;
static JMP_BUF APACHE_TLS jmpbuffer;
static int sd;
static fd_set listenfds;
static int listenmaxfd;
static pid_t pgrp;
/* 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;
/* set if timeouts are to be handled by the children and not by the parent.
* i.e. child_timeouts = !standalone || one_process.
*/
static int child_timeouts;
#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 *pcommands; /* Pool for -C and -c switches */
static int APACHE_TLS my_pid; /* it seems silly to call getpid all the time */
#ifndef MULTITHREAD
static int my_child_num;
#endif
#ifdef TPF
int tpf_child = 0;
char tpf_server_name[INETD_SERVNAME_LENGTH+1];
#endif /* TPF */
scoreboard *ap_scoreboard_image = NULL;
/*
* 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;
/*
* 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_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++;
}
}
static APACHE_TLS int volatile exit_after_unblock = 0;
#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)
{
if (pchild) {
ap_child_exit_modules(pchild, server_conf);
ap_destroy_pool(pchild);
}
chdir_for_gprof();
exit(code);
}
#if defined(USE_FCNTL_SERIALIZED_ACCEPT) || defined(USE_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 (USE_USLOCK_SERIALIZED_ACCEPT)
#include <ulocks.h>
static ulock_t uslock = NULL;
#define accept_mutex_child_init(x)
static void accept_mutex_init(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(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(void)
{
if (usunsetlock(uslock) == -1) {
perror("usunsetlock");
clean_child_exit(APEXIT_CHILDFATAL);
}
}
#elif defined (USE_PTHREAD_SERIALIZED_ACCEPT)
/* This code probably only works on Solaris ... but it works really fast
* on Solaris. 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(void *foo)
{
if (accept_mutex != (void *)(caddr_t)-1
&& have_accept_mutex) {
pthread_mutex_unlock(accept_mutex);
}
}
static void accept_mutex_child_init(pool *p)
{
ap_register_cleanup(p, NULL, accept_mutex_child_cleanup, ap_null_cleanup);
}
static void accept_mutex_cleanup(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;
}
static void accept_mutex_init(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");
exit(APEXIT_INIT);
}
close(fd);
if ((errno = pthread_mutexattr_init(&mattr))) {
perror("pthread_mutexattr_init");
exit(APEXIT_INIT);
}
if ((errno = pthread_mutexattr_setpshared(&mattr,
PTHREAD_PROCESS_SHARED))) {
perror("pthread_mutexattr_setpshared");
exit(APEXIT_INIT);
}
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);
ap_register_cleanup(p, NULL, accept_mutex_cleanup, ap_null_cleanup);
}
static void accept_mutex_on(void)
{
int err;
if (sigprocmask(SIG_BLOCK, &accept_block_mask, &accept_previous_mask)) {
perror("sigprocmask(SIG_BLOCK)");
clean_child_exit(APEXIT_CHILDFATAL);
}
if ((err = pthread_mutex_lock(accept_mutex))) {
errno = err;
perror("pthread_mutex_lock");
clean_child_exit(APEXIT_CHILDFATAL);
}
have_accept_mutex = 1;
}
static void accept_mutex_off(void)
{
int err;
if ((err = pthread_mutex_unlock(accept_mutex))) {
errno = err;
perror("pthread_mutex_unlock");
clean_child_exit(APEXIT_CHILDFATAL);
}
/* There is a slight race condition right here... if we were to die right
* now, we'd do another pthread_mutex_unlock. Now, doing that would let
* another process into the mutex. pthread mutexes are designed to be
* fast, as such they don't have protection for things like testing if the
* thread owning a mutex is actually unlocking it (or even any way of
* testing who owns the mutex).
*
* If we were to unset have_accept_mutex prior to releasing the mutex
* then the race could result in the server unable to serve hits. Doing
* it this way means that the server can continue, but an additional
* child might be in the critical section ... at least it's still serving
* hits.
*/
have_accept_mutex = 0;
if (sigprocmask(SIG_SETMASK, &accept_previous_mask, NULL)) {
perror("sigprocmask(SIG_SETMASK)");
clean_child_exit(1);
}
}
#elif defined (USE_SYSVSEM_SERIALIZED_ACCEPT)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#ifdef NEED_UNION_SEMUN
/* it makes no sense, but this isn't defined on solaris */
union semun {
long 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(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(x)
static void accept_mutex_init(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, 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(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(void)
{
while (semop(sem_id, &op_off, 1) < 0) {
if (errno != EINTR) {
perror("accept_mutex_off");
clean_child_exit(APEXIT_CHILDFATAL);
}
}
}
#elif defined(USE_FCNTL_SERIALIZED_ACCEPT)
static struct flock lock_it;
static struct flock unlock_it;
static int lock_fd = -1;
#define accept_mutex_child_init(x)
/*
* Initialize mutex lock.
* Must be safe to call this on a restart.
*/
static void accept_mutex_init(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(p, ap_lock_fname, O_CREAT | O_WRONLY | O_EXCL, 0644);
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(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(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);
}
}
#elif defined(USE_FLOCK_SERIALIZED_ACCEPT)
static int lock_fd = -1;
static void accept_mutex_cleanup(void *foo)
{
unlink(ap_lock_fname);
}
/*
* Initialize mutex lock.
* Done by each child at it's birth
*/
static void accept_mutex_child_init(pool *p)
{
lock_fd = ap_popenf(p, ap_lock_fname, O_WRONLY, 0600);
if (lock_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(pool *p)
{
expand_lock_fname(p);
unlink(ap_lock_fname);
lock_fd = ap_popenf(p, ap_lock_fname, O_CREAT | O_WRONLY | O_EXCL, 0600);
if (lock_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, ap_null_cleanup);
}
static void accept_mutex_on(void)
{
int ret;
while ((ret = flock(lock_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(void)
{
if (flock(lock_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);
}
}
#elif defined(USE_OS2SEM_SERIALIZED_ACCEPT)
static HMTX lock_sem = -1;
static void accept_mutex_cleanup(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(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, ap_null_cleanup);
}
}
/*
* Initialize mutex lock.
* Must be safe to call this on a restart.
*/
static void accept_mutex_init(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, ap_null_cleanup);
}
static void accept_mutex_on(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(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);
}
}
#elif defined(USE_TPF_CORE_SERIALIZED_ACCEPT)
static int tpf_core_held;
static void accept_mutex_cleanup(void *foo)
{
if(tpf_core_held)
coruc(RESOURCE_KEY);
}
#define accept_mutex_init(x)
static void accept_mutex_child_init(pool *p)
{
ap_register_cleanup(p, NULL, accept_mutex_cleanup, ap_null_cleanup);
tpf_core_held = 0;
}
static void accept_mutex_on(void)
{
corhc(RESOURCE_KEY);
tpf_core_held = 1;
ap_check_signals();
}
static void accept_mutex_off(void)
{
coruc(RESOURCE_KEY);
tpf_core_held = 0;
ap_check_signals();
}
#else
/* Default --- no serialization. Other methods *could* go here,
* as #elifs...
*/
#if !defined(MULTITHREAD)
/* Multithreaded systems don't complete between processes for
* the sockets. */
#define NO_SERIALIZED_ACCEPT
#define accept_mutex_child_init(x)
#define accept_mutex_init(x)
#define accept_mutex_on()
#define accept_mutex_off()
#endif
#endif
/* 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 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]\n", pad);
#ifdef WIN32
fprintf(stderr, " %s [-n service] [-k signal] [-i] [-u]\n", pad);
#endif
fprintf(stderr, "Options:\n");
#ifdef SHARED_CORE
fprintf(stderr, " -R directory : specify an alternate location for shared object files\n");
#endif
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");
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");
#ifdef WIN32
fprintf(stderr, " -n name : set service name and use its ServerConfigFile\n");
fprintf(stderr, " -k 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, " -i : install an Apache service\n");
fprintf(stderr, " -u : uninstall an Apache service\n");
#endif
exit(1);
}
/*****************************************************************
*
* 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;
static void timeout(int sig)
{
void *dirconf;
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) {
/* 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(0);
}
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;
}
}
#ifndef TPF
/*
* 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)
{
++alarms_blocked;
}
API_EXPORT(void) ap_unblock_alarms(void)
{
--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);
}
}
}
#endif /* TPF */
static APACHE_TLS void (*volatile alarm_fn) (int) = NULL;
#ifdef WIN32
static APACHE_TLS unsigned int alarm_expiry_time = 0;
#endif /* WIN32 */
#ifndef WIN32
static void alrm_handler(int sig)
{
if (alarm_fn) {
(*alarm_fn) (sig);
}
}
#endif
unsigned int ap_set_callback_and_alarm(void (*fn) (int), int x)
{
unsigned int old;
#ifdef WIN32
old = alarm_expiry_time;
if (old)
old -= time(0);
if (x == 0) {
alarm_fn = NULL;
alarm_expiry_time = 0;
}
else {
alarm_fn = fn;
alarm_expiry_time = time(NULL) + 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);
}
#ifdef WIN32
API_EXPORT(int) ap_check_alarm(void)
{
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 */
/* 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;
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);
}
}
void ap_keepalive_timeout(char *name, request_rec *r)
{
unsigned int to;
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)
{
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)
{
timeout_name = name;
ap_set_callback_and_alarm(timeout, r->server->timeout);
}
API_EXPORT(void) ap_kill_timeout(request_rec *dummy)
{
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)
{
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)
{
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.
*/
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) &&
(read(lsd, dummybuf, sizeof dummybuf) > 0));
/* 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, -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, -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
*/
}
else { /* only worry about permissions if we attached the segment */
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)
{
}
#elif defined(USE_TPF_SCOREBOARD)
static void cleanup_scoreboard_heap()
{
int rv;
rv = rsysc(ap_scoreboard_image, SCOREBOARD_FRAMES, SCOREBOARD_NAME);
if(rv == RSYSC_ERROR) {
ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
"rsysc() could not release scoreboard system heap");
}
}
static void setup_shared_mem(pool *p)
{
cinfc(CINFC_WRITE, CINFC_CMMCTK2);
ap_scoreboard_image = (scoreboard *) gsysc(SCOREBOARD_FRAMES, SCOREBOARD_NAME);
if (!ap_scoreboard_image) {
fprintf(stderr, "httpd: Could not create scoreboard system heap storage.\n");
exit(APEXIT_INIT);
}
ap_register_cleanup(p, NULL, cleanup_scoreboard_heap, ap_null_cleanup);
ap_scoreboard_image->global.running_generation = 0;
}
static void reopen_scoreboard(pool *p)
{
cinfc(CINFC_WRITE, CINFC_CMMCTK2);
}
#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(p, ap_scoreboard_fname, O_CREAT | O_BINARY | O_RDWR, 0666);
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(p, ap_scoreboard_fname, O_CREAT | O_BINARY | O_RDWR, 0644);
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 */
static void clean_parent_exit(int code) __attribute__((noreturn));
static void clean_parent_exit(int code)
{
/* Clear the pool - including any registered cleanups */
ap_destroy_pool(pglobal);
exit(code);
}
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 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;
}
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_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;
#ifndef NO_OTHER_CHILD
other_child_rec *ocr, *nocr;
#endif
ap_sync_scoreboard_image();
for (tries = terminate ? 4 : 1; tries <= 9; ++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.
*/
tv.tv_sec = waittime / 1000000;
tv.tv_usec = waittime % 1000000;
waittime = waittime * 4;
ap_select(0, NULL, NULL, NULL, &tv);
/* 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;
waitret = waitpid(pid, &status, WNOHANG);
if (waitret == pid || waitret == -1) {
ap_scoreboard_image->parent[i].pid = 0;
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);
break;
case 9: /* 14 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;
}
}
#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_DEATH, ocr->data, status);
}
else if (waitret == 0) {
(*ocr->maintenance) (OC_REASON_RESTART, ocr->data, -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, -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();
if (ap_scoreboard_image->servers[n].status != SERVER_DEAD &&
kill((pid = ap_scoreboard_image->parent[n].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));
return(pid);
}
}
return 0;
}
#endif
/* Finally, this routine is used by the caretaker process to wait for
* a while...
*/
/* 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;
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;
h[hi++] = (HANDLE) ap_scoreboard_image->parent[pi].pid;
}
}
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 */
}
#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[] */
/* handle all varieties of core dumping signals */
static void sig_coredump(int sig)
{
chdir(ap_coredump_dir);
signal(sig, SIG_DFL);
#ifndef WIN32
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 */
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;
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.
*/
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.
*/
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 */
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)
{
#ifndef WIN32
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 */
}
signal(SIGTERM, sig_term);
#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)
int x;
chdir("/");
#if !defined(MPE) && !defined(OS2) && !defined(TPF)
/* Don't detach for MPE because child processes can't survive the death of
the parent. */
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);
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();
#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)
{
#ifndef WIN32
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.
*/
if (setgid(ap_group_id) == -1) {
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);
}
/* 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 /* ndef WIN32 */
}
/* check to see if we have the 'suexec' setuid wrapper installed */
static int init_suexec(void)
{
#ifndef WIN32
struct stat wrapper;
if ((stat(SUEXEC_BIN, &wrapper)) != 0)
return (ap_suexec_enabled);
if ((wrapper.st_mode & S_ISUID) && wrapper.st_uid == 0) {
ap_suexec_enabled = 1;
}
#endif /* ndef WIN32 */
return (ap_suexec_enabled);
}
/*****************************************************************
* 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)
{
/* 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) {
ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
"setsockopt: (TCP_NODELAY)");
}
}
#else
#define sock_disable_nagle(s) /* 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(p, s); /* arrange to close on exec or restart */
#ifdef TPF
os_note_additional_cleanups(p, s);
#endif /* TPF */
#endif
#ifndef MPE
/* MPE does not support SO_REUSEADDR and SO_KEEPALIVE */
#ifndef _OSD_POSIX
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) {
ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
"make_sock: for %s, setsockopt: (SO_REUSEADDR)", addr);
#ifdef BEOS
closesocket(s);
#else
close(s);
#endif
ap_unblock_alarms();
return -1;
}
#endif /*_OSD_POSIX*/
one = 1;
#ifdef SO_KEEPALIVE
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);
#ifdef BEOS
closesocket(s);
#else
close(s);
#endif
ap_unblock_alarms();
return -1;
}
#endif
#endif
sock_disable_nagle(s);
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
#ifdef BEOS
closesocket(s);
#else
close(s);
#endif
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);
#ifdef BEOS
closesocket(s);
#else
close(s);
#endif
ap_unblock_alarms();
exit(1);
}
#ifdef WORKAROUND_SOLARIS_BUG
s = ap_slack(s, AP_SLACK_HIGH);
ap_note_cleanups_for_socket(p, s); /* 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);
#ifdef BEOS
closesocket(s);
#else
close(s);
#endif
return -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;
}
/* 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(p, fd);
}
if (fd >= 0) {
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 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;
}
#ifdef WIN32
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(1, 1);
err = WSAStartup((WORD) iVersionRequested, &wsaData);
if (err) {
s_iInitCount = -1;
return (s_iInitCount);
}
if (LOBYTE(wsaData.wVersion) != 1 ||
HIBYTE(wsaData.wVersion) != 1) {
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 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 USE_FCNTL_SERIALIZED_ACCEPT
printf(" -D USE_FCNTL_SERIALIZED_ACCEPT\n");
#endif
#ifdef USE_FLOCK_SERIALIZED_ACCEPT
printf(" -D USE_FLOCK_SERIALIZED_ACCEPT\n");
#endif
#ifdef USE_USLOCK_SERIALIZED_ACCEPT
printf(" -D USE_USLOCK_SERIALIZED_ACCEPT\n");
#endif
#ifdef USE_SYSVSEM_SERIALIZED_ACCEPT
printf(" -D USE_SYSVSEM_SERIALIZED_ACCEPT\n");
#endif
#ifdef USE_PTHREAD_SERIALIZED_ACCEPT
printf(" -D USE_PTHREAD_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
#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
/* This list displays the compiled-in default paths: */
#ifdef HTTPD_ROOT
printf(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n");
#endif
#ifdef SUEXEC_BIN
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_XFERLOG
printf(" -D DEFAULT_XFERLOG=\"" DEFAULT_XFERLOG "\"\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)
{
INIT_SIGLIST()
#ifdef AUX3
(void) set42sig();
#endif
#ifdef WIN32
/* Initialize the stupid sockets */
AMCSocketInitialize();
#endif /* WIN32 */
pglobal = ap_init_alloc();
pconf = ap_make_sub_pool(pglobal);
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 *));
}
#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);
/* needs to be done before we switch UIDs so we have permissions */
reopen_scoreboard(pchild);
SAFE_ACCEPT(accept_mutex_child_init(pchild));
set_group_privs();
#ifdef MPE
/* Only try to switch if we're running as MANAGER.SYS */
if (geteuid() == 1 && ap_user_id > 1) {
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 */
if (!geteuid() && (
#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
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) {
#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
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_EMERG, server_conf,
"offload device inactive");
clean_child_exit(APEXIT_CHILDFATAL);
break;
default:
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 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" */
#ifdef TPF
if (csd == 0) /* 0 is invalid socket for TPF */
continue;
#endif
/* 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_fd(ptrans, csd);
/* 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.
*/
clen = sizeof(sa_server);
if (getsockname(csd, &sa_server, &clen) < 0) {
ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, "getsockname");
continue;
}
sock_disable_nagle(csd);
(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_fd(ptrans, dupped_csd);
/* 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);
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;
#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(0);
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;
static void perform_idle_server_maintenance(void)
{
int i;
int to_kill;
int idle_count;
short_score *ss;
time_t now = time(0);
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;
kill(ps->pid, SIGALRM);
}
}
#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
*/
kill(ap_scoreboard_image->parent[to_kill].pid, SIGUSR1);
idle_spawn_rate = 1;
}
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) {
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);
ptrans = ap_make_sub_pool(pconf);
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(pconf, scoreboard_fd);
}
#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());
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
*/
if (pid >= 0) {
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(0));
--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();
#ifdef TPF
shutdown_pending = os_check_server(tpf_server_name);
ap_check_signals();
sleep(1);
#endif /*TPF */
}
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;
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
APACHE_TPF_INPUT input_parms;
ecbptr()->ebrout = PRIMECRAS;
input_parms = * (APACHE_TPF_INPUT *)(&(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:f: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':
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_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);
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;
#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 (ap_standalone) {
if(!tpf_child) {
memcpy(tpf_server_name, input_parms.inetd_server.servname, INETD_SERVNAME_LENGTH);
tpf_server_name[INETD_SERVNAME_LENGTH+1] = '\0';
ap_open_logs(server_conf, pconf);
}
ap_set_version();
ap_init_modules(pconf, server_conf);
version_locked++;
if(tpf_child) {
copy_listeners(pconf);
reset_tpf_listeners(&input_parms);
server_conf->error_log = NULL;
#ifdef SCOREBOARD_FILE
scoreboard_fd = input_parms.scoreboard_fd;
ap_scoreboard_image = &_scoreboard_image;
#else /* must be USE_TPF_SCOREBOARD or USE_SHMGET_SCOREBOARD */
ap_scoreboard_image = (scoreboard *)input_parms.scoreboard_heap;
#endif
child_main(input_parms.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
/* Only try to switch if we're running as MANAGER.SYS */
if (geteuid() == 1 && ap_user_id > 1) {
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 */
if (!geteuid() && 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);
exit(1);
}
#endif
if (ap_setjmp(jmpbuffer)) {
exit(0);
}
#ifdef TPF
/* TPF only passes the incoming socket number from the internet daemon
in ebw000 */
sock_in = * (int*)(&(ecbptr()->ebw000));
sock_out = * (int*)(&(ecbptr()->ebw000));
/* TPF also needs a signal set for alarm in inetd mode */
signal(SIGALRM, alrm_handler);
#elif defined(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(void)
{
joblist *job;
int sock;
#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);
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;
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
#ifdef SIGURG
signal(SIGURG, timeout);
#endif
while (1) {
BUFF *conn_io;
request_rec *r;
/*
* (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();
if (csd == -1)
break; /* time to exit */
requests_this_child++;
ap_note_cleanups_for_socket(ptrans, csd);
/*
* 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);
(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(ptrans, dupped_csd);
#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;
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);
}
void child_main(int child_num_arg)
{
/*
* 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
*/
child_sub_main(child_num_arg);
}
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);
}
static void setup_inherited_listeners(pool *p)
{
HANDLE pipe;
listen_rec *lr;
int fd;
WSAPROTOCOL_INFO WSAProtocolInfo;
DWORD BytesRead;
/* 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);
/* Setup the listeners */
listenmaxfd = -1;
FD_ZERO(&listenfds);
lr = ap_listeners;
FD_ZERO(&listenfds);
for (;;) {
fd = find_listener(lr);
if (fd < 0) {
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");
exit(1);
}
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 get inherit the socket.");
exit(1);
}
APD2("setup_inherited_listeners: WSASocket() returned socket %d", fd);
}
else {
ap_note_cleanups_for_socket(p, fd);
}
if (fd >= 0) {
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();
CloseHandle(pipe);
return;
}
/*
* 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()).
*/
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(0);
}
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(0);
}
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 (nthreads > max_daemons_limit) {
max_daemons_limit = nthreads;
}
while (1) {
if (max_jobs_per_exe && (total_jobs > max_jobs_per_exe)) {
/* MaxRequestsPerChild hit...
*/
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);
#ifdef WIN32
if (srv == SOCKET_ERROR) {
/* Map the Win32 error into a standard Unix error condition */
errno = WSAGetLastError();
srv = -1;
}
#endif /* WIN32 */
if (srv < 0) {
/* Error occurred - if EINTR, loop around with problem */
if (errno != EINTR) {
/* 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_ERR, server_conf, "select: (listen)");
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;
}
count_select_errors = 0; /* reset count of errors */
if (srv == 0) {
continue;
}
{
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);
#ifdef WIN32
if (csd == INVALID_SOCKET) {
csd = -1;
errno = WSAGetLastError();
}
#endif /* WIN32 */
} while (csd < 0 && errno == EINTR);
if (csd < 0) {
#if defined(EPROTO) && defined(ECONNABORTED)
if ((errno != EPROTO) && (errno != ECONNABORTED))
#elif defined(EPROTO)
if (errno != EPROTO)
#elif defined(ECONNABORTED)
if (errno != ECONNABORTED)
#endif
ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
"accept: (client socket)");
}
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;
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;
}
pCommand = ap_psprintf(p, "\"%s\" -Z %s -f \"%s\"", buf, exit_event_name, ap_server_confname);
for (i = 1; i < argc; i++) {
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;
}
/* Give the read in of teh 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_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = hPipeRead;
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);
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);
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;
}
/*
* We can safely use GetLastError() here without presetting it;
* {Initialize,Set}SecurityDescriptor() have been verified as clearing it
* on successful completion.
*/
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,
&current_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, restart the child process */
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, &current_live_processes);
}
processes_to_create = nchild;
++ap_my_generation;
continue;
} else {
/* A child process must have exited because of MaxRequestPerChild being hit
* or a fatal error condition (seg fault, etc.). Remove the dead process
* from the process_handles and process_kill_events table and create a new
* child process.
*/
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf,
"master_main: Child processed exited (due to MaxRequestsPerChild?). Restarting the child process.");
ap_assert(cld < current_live_processes);
cleanup_process(process_handles, process_kill_events, cld, &current_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, &current_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);
service_set_status(SERVICE_STOPPED);
return (0);
}
/*
* 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();
}
int service_init()
{
common_init();
ap_cpystrn(ap_server_root, HTTPD_ROOT, sizeof(ap_server_root));
if (ap_registry_get_service_conf(pconf, ap_server_confname, sizeof(ap_server_confname),
ap_server_argv0))
return FALSE;
ap_setup_prelinked_modules();
server_conf = ap_read_config(pconf, ptrans, ap_server_confname);
ap_log_pid(pconf, ap_pid_fname);
post_parse_init();
return TRUE;
}
#ifdef 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;
char *service_name = NULL;
int install = 0;
int conf_specified = 0;
char *signal_to_send = NULL;
char cwd[MAX_STRING_LEN];
/* Service application
* Configuration file in registry at:
* HKLM\System\CurrentControlSet\Services\[Svc name]\Parameters\ConfPath
*/
if (isProcessService()) {
service_main(master_main, argc, argv);
clean_parent_exit(0);
}
/* 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();
if(!GetCurrentDirectory(sizeof(cwd),cwd)) {
ap_log_error(APLOG_MARK,APLOG_WIN32ERROR, NULL,
"GetCurrentDirectory() failure");
return -1;
}
ap_cpystrn(cwd, ap_os_canonical_filename(pcommands, cwd), sizeof(cwd));
ap_cpystrn(ap_server_root, cwd, sizeof(ap_server_root));
while ((c = getopt(argc, argv, "D:C:c:Xd:f:vVlLZ:iusStThk:n:")) != -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;
#ifdef WIN32
case 'Z':
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);
if (isValidService(optarg)) {
ap_registry_get_service_conf(pconf, ap_server_confname, sizeof(ap_server_confname),
optarg);
conf_specified = 1;
}
break;
case 'i':
install = 1;
break;
case 'u':
install = -1;
break;
case 'S':
ap_dump_settings = 1;
break;
case 'k':
signal_to_send = optarg;
break;
#endif /* WIN32 */
case 'd':
optarg = ap_os_canonical_filename(pcommands, optarg);
if (!ap_os_is_path_absolute(optarg)) {
optarg = ap_pstrcat(pcommands, cwd, optarg, NULL);
ap_getparents(optarg);
}
if (optarg[strlen(optarg)-1] != '/')
optarg = ap_pstrcat(pcommands, optarg, "/", NULL);
ap_cpystrn(ap_server_root,
optarg,
sizeof(ap_server_root));
break;
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());
exit(0);
case 'V':
ap_set_version();
show_compile_settings();
exit(0);
case 'l':
ap_show_modules();
exit(0);
case 'L':
ap_show_directives();
exit(0);
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 */
} /* while */
/* ServerConfFile is found in this order:
* (1) -f or -n
* (2) [-d]/SERVER_CONFIG_FILE
* (3) ./SERVER_CONFIG_FILE
* (4) [Registry: HKLM\Software\[product]\ServerRoot]/SERVER_CONFIG_FILE
* (5) /HTTPD_ROOT/SERVER_CONFIG_FILE
*/
if (!conf_specified) {
ap_cpystrn(ap_server_confname, SERVER_CONFIG_FILE, sizeof(ap_server_confname));
if (access(ap_server_root_relative(pcommands, ap_server_confname), 0)) {
ap_registry_get_server_root(pconf, ap_server_root, sizeof(ap_server_root));
if (!*ap_server_root)
ap_cpystrn(ap_server_root, HTTPD_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_confname)) {
char *full_conf_path;
full_conf_path = ap_pstrcat(pcommands, ap_server_root, "/", ap_server_confname, NULL);
full_conf_path = ap_os_canonical_filename(pcommands, full_conf_path);
ap_cpystrn(ap_server_confname, full_conf_path, sizeof(ap_server_confname));
}
ap_getparents(ap_server_confname);
ap_no2slash(ap_server_confname);
#ifdef WIN32
if (install) {
if (!service_name)
service_name = ap_pstrdup(pconf, DEFAULTSERVICENAME);
if (install > 0)
InstallService(service_name, ap_server_root_relative(pcommands, ap_server_confname));
else
RemoveService(service_name);
clean_parent_exit(0);
}
if (service_name && signal_to_send) {
send_signal_to_service(service_name, signal_to_send);
clean_parent_exit(0);
}
if (service_name && !conf_specified) {
printf("Unknown service: %s\n", service_name);
clean_parent_exit(0);
}
#endif
server_conf = ap_read_config(pconf, ptrans, ap_server_confname);
if (ap_configtestonly) {
fprintf(stderr, "%s: Syntax OK\n", ap_server_root_relative(pcommands, ap_server_confname));
clean_parent_exit(0);
}
if (ap_dump_settings) {
clean_parent_exit(0);
}
/* Treat -k start confpath as just -f confpath */
if (signal_to_send && strcasecmp(signal_to_send, "start")) {
send_signal(pconf, signal_to_send);
clean_parent_exit(0);
}
if (!child && !ap_dump_settings) {
ap_log_pid(pconf, ap_pid_fname);
}
post_parse_init();
#ifdef OS2
printf("%s running...\n", ap_get_server_version());
#endif
#ifdef WIN32
if (!child) {
printf("%s running...\n", ap_get_server_version());
}
#endif
if (one_process && !exit_event)
exit_event = create_event(0, 0, NULL);
if (one_process && !start_mutex)
start_mutex = ap_create_mutex(NULL);
/*
* 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);
worker_main();
ap_destroy_mutex(start_mutex);
destroy_event(exit_event);
}
else
master_main(argc, argv);
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 */
#ifdef OS2
/* Shared core loader for OS/2 */
int ap_main(int argc, char *argv[]); /* Load time linked from libhttpd.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;
/*
* parse argument line,
* but only handle the -L option
*/
llp_dir = SHARED_CORE_DIR;
while ((c = getopt(argc, argv, "D:C:c:Xd:f:vVlLR:SZ:tTh")) != -1) {
switch (c) {
case 'D':
case 'C':
case 'c':
case 'X':
case 'd':
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;
}
}
/*
* 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
/*
* 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 */