/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * httpd.c: simple http daemon for answering WWW file requests
 *
 * 
 * 03-21-93  Rob McCool wrote original code (up to NCSA HTTPd 1.3)
 * 
 * 03-06-95  blong
 *  changed server number for child-alone processes to 0 and changed name
 *   of processes
 *
 * 03-10-95  blong
 *      Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu) 
 *      including set group before fork, and call gettime before to fork
 *      to set up libraries.
 *
 * 04-14-95  rst / rh
 *      Brandon's code snarfed from NCSA 1.4, but tinkered to work with the
 *      Apache server, and also to have child processes do accept() directly.
 *
 * April-July '95 rst
 *      Extensive rework for Apache.
 */

#ifndef SHARED_CORE_BOOTSTRAP
#ifndef SHARED_CORE_TIESTATIC

#ifdef SHARED_CORE
#define REALMAIN ap_main
int ap_main(int argc, char *argv[]);
#else
#define REALMAIN main
#endif

#define CORE_PRIVATE

#include "httpd.h"
#include "http_main.h"
#include "http_log.h"
#include "http_config.h"	/* for read_config */
#include "http_protocol.h"	/* for read_request */
#include "http_request.h"	/* for process_request */
#include "http_conf_globals.h"
#include "http_core.h"		/* for get_remote_host */
#include "http_vhost.h"
#include "util_script.h"	/* to force util_script.c linking */
#include "util_uri.h"
#include "scoreboard.h"
#include "multithread.h"
#include <sys/stat.h>
#ifdef USE_SHMGET_SCOREBOARD
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#endif
#ifdef SecureWare
#include <sys/security.h>
#include <sys/audit.h>
#include <prot.h>
#endif
#ifdef WIN32
#include "../os/win32/getopt.h"
#elif !defined(BEOS) && !defined(TPF) && !defined(NETWARE) && !defined(OS390) && !defined(CYGWIN)
#include <netinet/tcp.h>
#endif
#ifdef HAVE_BSTRING_H
#include <bstring.h>		/* for IRIX, FD_SET calls bzero() */
#endif
#ifdef HAVE_SET_DUMPABLE /* certain levels of Linux */
#include <sys/prctl.h>
#endif

#ifdef MULTITHREAD
/* special debug stuff -- PCS */

/* Set this non-zero if you are prepared to put up with more than one log entry per second */
#define SEVERELY_VERBOSE	    0

  /* APD1() to APD5() are macros to help us debug. They can either
   * log to the screen or the error_log file. In release builds, these
   * macros do nothing. In debug builds, they send messages at priority
   * "debug" to the error log file, or if DEBUG_TO_CONSOLE is defined,
   * to the console.
   */

# ifdef _DEBUG
#  ifndef DEBUG_TO_CONSOLE
#   define APD1(a) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a)
#   define APD2(a,b) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b)
#   define APD3(a,b,c) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b,c)
#   define APD4(a,b,c,d) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b,c,d)
#   define APD5(a,b,c,d,e) ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,server_conf,a,b,c,d,e)
#  else
#   define APD1(a) printf("%s\n",a)
#   define APD2(a,b) do { printf(a,b);putchar('\n'); } while(0);
#   define APD3(a,b,c) do { printf(a,b,c);putchar('\n'); } while(0);
#   define APD4(a,b,c,d) do { printf(a,b,c,d);putchar('\n'); } while(0);
#   define APD5(a,b,c,d,e) do { printf(a,b,c,d,e);putchar('\n'); } while(0);
#  endif
# else /* !_DEBUG */
#  define APD1(a) 
#  define APD2(a,b) 
#  define APD3(a,b,c) 
#  define APD4(a,b,c,d) 
#  define APD5(a,b,c,d,e) 
# endif /* _DEBUG */
#endif /* MULTITHREAD */

/* This next function is never used. It is here to ensure that if we
 * make all the modules into shared libraries that core httpd still
 * includes the full Apache API. Without this function the objects in
 * main/util_script.c would not be linked into a minimal httpd.
 * And the extra prototype is to make gcc -Wmissing-prototypes quiet.
 */
API_EXPORT(void) ap_force_library_loading(void);
API_EXPORT(void) ap_force_library_loading(void) {
    ap_add_cgi_vars(NULL);
}

#include "explain.h"

#if !defined(max)
#define max(a,b)        (a > b ? a : b)
#endif

#ifdef WIN32
#include "../os/win32/service.h"
#include "../os/win32/registry.h"
#define DEFAULTSERVICENAME "Apache"
#define PATHSEPARATOR '\\'
#else
#define PATHSEPARATOR '/'
#endif


#ifdef MINT
long _stksize = 32768;
#endif

#ifdef USE_OS2_SCOREBOARD
    /* Add MMAP style functionality to OS/2 */
#define INCL_DOSMEMMGR
#define INCL_DOSEXCEPTIONS
#define INCL_DOSSEMAPHORES
#include <os2.h>
#include <umalloc.h>
#include <stdio.h>
caddr_t create_shared_heap(const char *, size_t);
caddr_t get_shared_heap(const char *);
#endif

DEF_Explain

/* Defining GPROF when compiling uses the moncontrol() function to
 * disable gprof profiling in the parent, and enable it only for
 * request processing in children (or in one_process mode).  It's
 * absolutely required to get useful gprof results under linux
 * because the profile itimers and such are disabled across a
 * fork().  It's probably useful elsewhere as well.
 */
#ifdef GPROF
extern void moncontrol(int);
#define MONCONTROL(x) moncontrol(x)
#else
#define MONCONTROL(x)
#endif

#ifndef MULTITHREAD
/* this just need to be anything non-NULL */
void *ap_dummy_mutex = &ap_dummy_mutex;
#endif

/*
 * Actual definitions of config globals... here because this is
 * for the most part the only code that acts on 'em.  (Hmmm... mod_main.c?)
 */
#ifdef NETWARE
BOOL ap_main_finished = FALSE;
unsigned int ap_thread_stack_size = 65536;
#endif
int ap_thread_count = 0;
API_VAR_EXPORT int ap_standalone=0;
API_VAR_EXPORT int ap_configtestonly=0;
int ap_docrootcheck=1;
API_VAR_EXPORT uid_t ap_user_id=0;
API_VAR_EXPORT char *ap_user_name=NULL;
API_VAR_EXPORT gid_t ap_group_id=0;
#ifdef MULTIPLE_GROUPS
gid_t group_id_list[NGROUPS_MAX];
#endif
API_VAR_EXPORT int ap_max_requests_per_child=0;
API_VAR_EXPORT int ap_threads_per_child=0;
API_VAR_EXPORT int ap_excess_requests_per_child=0;
API_VAR_EXPORT char *ap_pid_fname=NULL;
API_VAR_EXPORT char *ap_scoreboard_fname=NULL;
API_VAR_EXPORT char *ap_lock_fname=NULL;
API_VAR_EXPORT char *ap_server_argv0=NULL;
API_VAR_EXPORT struct in_addr ap_bind_address={0};
API_VAR_EXPORT int ap_daemons_to_start=0;
API_VAR_EXPORT int ap_daemons_min_free=0;
API_VAR_EXPORT int ap_daemons_max_free=0;
API_VAR_EXPORT int ap_daemons_limit=0;
API_VAR_EXPORT time_t ap_restart_time=0;
API_VAR_EXPORT int ap_suexec_enabled = 0;
API_VAR_EXPORT int ap_listenbacklog=0;
#ifdef AP_ENABLE_EXCEPTION_HOOK
int ap_exception_hook_enabled=0;
#endif

struct accept_mutex_methods_s {
    void (*child_init)(pool *p);
    void (*init)(pool *p);
    void (*on)(void);
    void (*off)(void);
    char *name;
};
typedef struct accept_mutex_methods_s accept_mutex_methods_s;
accept_mutex_methods_s *amutex;

#ifdef SO_ACCEPTFILTER
int ap_acceptfilter =
#ifdef AP_ACCEPTFILTER_OFF
	0;
#else
	1;
#endif
#endif

int ap_dump_settings = 0;
API_VAR_EXPORT int ap_extended_status = 0;

/*
 * The max child slot ever assigned, preserved across restarts.  Necessary
 * to deal with MaxClients changes across SIGUSR1 restarts.  We use this
 * value to optimize routines that have to scan the entire scoreboard.
 */
static int max_daemons_limit = -1;

/*
 * During config time, listeners is treated as a NULL-terminated list.
 * child_main previously would start at the beginning of the list each time
 * through the loop, so a socket early on in the list could easily starve out
 * sockets later on in the list.  The solution is to start at the listener
 * after the last one processed.  But to do that fast/easily in child_main it's
 * way more convenient for listeners to be a ring that loops back on itself.
 * The routine setup_listeners() is called after config time to both open up
 * the sockets and to turn the NULL-terminated list into a ring that loops back
 * on itself.
 *
 * head_listener is used by each child to keep track of what they consider
 * to be the "start" of the ring.  It is also set by make_child to ensure
 * that new children also don't starve any sockets.
 *
 * Note that listeners != NULL is ensured by read_config().
 */
listen_rec *ap_listeners=NULL;
static listen_rec *head_listener;

API_VAR_EXPORT char ap_server_root[MAX_STRING_LEN]="";
API_VAR_EXPORT char ap_server_confname[MAX_STRING_LEN]="";
API_VAR_EXPORT char ap_coredump_dir[MAX_STRING_LEN]="";
int ap_coredump_dir_configured=0;

API_VAR_EXPORT array_header *ap_server_pre_read_config=NULL;
API_VAR_EXPORT array_header *ap_server_post_read_config=NULL;
API_VAR_EXPORT array_header *ap_server_config_defines=NULL;

/* *Non*-shared http_main globals... */

static server_rec *server_conf;
#ifndef NETWARE
static JMP_BUF APACHE_TLS jmpbuffer;
#endif
static int sd;
static fd_set listenfds;
static int listenmaxfd;
#ifndef NETWARE
static pid_t pgrp;
#endif

/* one_process --- debugging mode variable; can be set from the command line
 * with the -X flag.  If set, this gets you the child_main loop running
 * in the process which originally started up (no detach, no make_child),
 * which is a pretty nice debugging environment.  (You'll get a SIGHUP
 * early in standalone_main; just continue through.  This is the server
 * trying to kill off any child processes which it might have lying
 * around --- Apache doesn't keep track of their pids, it just sends
 * SIGHUP to the process group, ignoring it in the root process.
 * Continue through and you'll be fine.).
 */

static int one_process = 0;

static int do_detach = 1;

/* set if timeouts are to be handled by the children and not by the parent.
 * i.e. child_timeouts = !standalone || one_process.
 */
#ifndef NETWARE
static int child_timeouts;
#endif

#ifdef DEBUG_SIGSTOP
int raise_sigstop_flags;
#endif

#ifndef NO_OTHER_CHILD
/* used to maintain list of children which aren't part of the scoreboard */
typedef struct other_child_rec other_child_rec;
struct other_child_rec {
    other_child_rec *next;
    int pid;
    void (*maintenance) (int, void *, ap_wait_t);
    void *data;
    int write_fd;
};
static other_child_rec *other_children;
#endif

static pool *pglobal;		/* Global pool */
static pool *pconf;		/* Pool for config stuff */
static pool *plog;		/* Pool for error-logging files */
static pool *ptrans;		/* Pool for per-transaction stuff */
static pool *pchild;		/* Pool for httpd child stuff */
static pool *pmutex;            /* Pool for accept mutex in child */
static pool *pcommands;	/* Pool for -C and -c switches */

#ifndef NETWARE
static int APACHE_TLS my_pid;	/* it seems silly to call getpid all the time */
#endif
#ifndef MULTITHREAD
static int my_child_num;
#endif

#ifdef TPF
pid_t tpf_parent_pid;
int tpf_child = 0;
char tpf_server_name[INETD_SERVNAME_LENGTH+1];
char tpf_mutex_key[TPF_MUTEX_KEY_SIZE];
#endif /* TPF */

/*
 * Shared memory scoreboard
 */
scoreboard *ap_scoreboard_image = NULL;

/*
 * Parent process local storage of child pids
 */
static int pid_table[HARD_SERVER_LIMIT];

/*
 * Pieces for managing the contents of the Server response header
 * field.
 */
static char *server_version = NULL;
static int version_locked = 0;

/* Global, alas, so http_core can talk to us */
enum server_token_type ap_server_tokens = SrvTk_FULL;

/* Also global, for http_core and http_protocol */
API_VAR_EXPORT int ap_protocol_req_check = 1;

API_VAR_EXPORT int ap_change_shmem_uid = 0;

/*
 * Check the pid table to see if the actual pid exists
 */

static int in_pid_table(int pid) {
    int i;
    for (i = 0; i < HARD_SERVER_LIMIT; i++) {
        if (pid_table[i] == pid) {
            return 1;
        }
    }
    return 0;
}

static void set_pid_table(int pid) {
    int i;
    for (i = 0; i < HARD_SERVER_LIMIT; i++) {
        if (pid_table[i] == 0) {
            pid_table[i] = pid;
            break;
        }
    }
    /* NOTE: Error detection?? */
}

static void unset_pid_table(int pid) {
    int i;
    for (i = 0; i < HARD_SERVER_LIMIT; i++) {
        if (pid_table[i] == pid) {
            pid_table[i] = 0;
            break;
        }
    }
}

/*
 * This routine is called when the pconf pool is vacuumed.  It resets the
 * server version string to a known value and [re]enables modifications
 * (which are disabled by configuration completion). 
 */
static void reset_version(void *dummy)
{
    version_locked = 0;
    ap_server_tokens = SrvTk_FULL;
    server_version = NULL;
}

API_EXPORT(const char *) ap_get_server_version(void)
{
    return (server_version ? server_version : SERVER_BASEVERSION);
}

API_EXPORT(void) ap_add_version_component(const char *component)
{
    if (! version_locked) {
        /*
         * If the version string is null, register our cleanup to reset the
         * pointer on pool destruction. We also know that, if NULL,
	 * we are adding the original SERVER_BASEVERSION string.
         */
        if (server_version == NULL) {
	    ap_register_cleanup(pconf, NULL, (void (*)(void *))reset_version, 
				ap_null_cleanup);
	    server_version = ap_pstrdup(pconf, component);
	}
	else {
	    /*
	     * Tack the given component identifier to the end of
	     * the existing string.
	     */
	    server_version = ap_pstrcat(pconf, server_version, " ",
					component, NULL);
	}
    }
}

/*
 * This routine adds the real server base identity to the version string,
 * and then locks out changes until the next reconfig.
 */
static void ap_set_version(void)
{
    if (ap_server_tokens == SrvTk_PRODUCT_ONLY) {
	ap_add_version_component(SERVER_PRODUCT);
    }
    else if (ap_server_tokens == SrvTk_MIN) {
	ap_add_version_component(SERVER_BASEVERSION);
    }
    else {
	ap_add_version_component(SERVER_BASEVERSION " (" PLATFORM ")");
    }
    /*
     * Lock the server_version string if we're not displaying
     * the full set of tokens
     */
    if (ap_server_tokens != SrvTk_FULL) {
	version_locked++;
    }
}

#ifndef NETWARE
static APACHE_TLS int volatile exit_after_unblock = 0;
#endif

#ifdef GPROF
/* 
 * change directory for gprof to plop the gmon.out file
 * configure in httpd.conf:
 * GprofDir logs/   -> $ServerRoot/logs/gmon.out
 * GprofDir logs/%  -> $ServerRoot/logs/gprof.$pid/gmon.out
 */
static void chdir_for_gprof(void)
{
    core_server_config *sconf = 
	ap_get_module_config(server_conf->module_config, &core_module);    
    char *dir = sconf->gprof_dir;

    if(dir) {
	char buf[512];
	int len = strlen(sconf->gprof_dir) - 1;
	if(*(dir + len) == '%') {
	    dir[len] = '\0';
	    ap_snprintf(buf, sizeof(buf), "%sgprof.%d", dir, (int)getpid());
	} 
	dir = ap_server_root_relative(pconf, buf[0] ? buf : dir);
	if(mkdir(dir, 0755) < 0 && errno != EEXIST) {
	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
			 "gprof: error creating directory %s", dir);
	}
    }
    else {
	dir = ap_server_root_relative(pconf, "logs");
    }

    chdir(dir);
}
#else
#define chdir_for_gprof()
#endif

/* a clean exit from a child with proper cleanup */
static void clean_child_exit(int code) __attribute__ ((noreturn));
static void clean_child_exit(int code)
{
#ifdef TPF
    /* run ptrans cleanups since TPF's sockets don't close upon exit */
    if (ptrans) { 
       ap_clear_pool(ptrans);
    }
#endif /* TPF */

    if (pchild) {
        /* make sure the accept mutex is released before calling child
         * exit hooks and cleanups...  otherwise, modules can segfault
         * in such code and, depending on the mutex mechanism, leave
         * the server deadlocked...  even if the module doesn't segfault,
         * if it performs extensive processing it can temporarily prevent
         * the server from accepting new connections
         */
        ap_clear_pool(pmutex);
	ap_child_exit_modules(pchild, server_conf);
	ap_destroy_pool(pchild);
    }
    chdir_for_gprof();
    exit(code);
}

/*
 * Start of accept() mutex fluff:
 *  Concept: Each method has it's own distinct set of mutex functions,
 *   which it shoves in a nice struct for us. We then pick
 *   which struct to use. We tell Apache which methods we
 *   support via HAVE_FOO_SERIALIZED_ACCEPT. We can
 *   specify the default via USE_FOO_SERIALIZED_ACCEPT
 *   (this pre-1.3.21 builds which use that at the command-
 *   line during builds work as expected). Without a set
 *   method, we pick the 1st from the following order:
 *   uslock, pthread, sysvsem, fcntl, flock, os2sem, tpfcore and none.
 */

#if defined(HAVE_FCNTL_SERIALIZED_ACCEPT) || defined(HAVE_FLOCK_SERIALIZED_ACCEPT)
static void expand_lock_fname(pool *p)
{
    /* XXXX possibly bogus cast */
    ap_lock_fname = ap_psprintf(p, "%s.%lu",
	ap_server_root_relative(p, ap_lock_fname), (unsigned long)getpid());
}
#endif

#if defined (HAVE_USLOCK_SERIALIZED_ACCEPT)
#include <ulocks.h>
static ulock_t uslock = NULL;

#define accept_mutex_child_init_uslock(x)

static void accept_mutex_init_uslock(pool *p)
{
    ptrdiff_t old;
    usptr_t *us;


    /* default is 8, allocate enough for all the children plus the parent */
    if ((old = usconfig(CONF_INITUSERS, HARD_SERVER_LIMIT + 1)) == -1) {
	perror("usconfig(CONF_INITUSERS)");
	exit(-1);
    }

    if ((old = usconfig(CONF_LOCKTYPE, US_NODEBUG)) == -1) {
	perror("usconfig(CONF_LOCKTYPE)");
	exit(-1);
    }
    if ((old = usconfig(CONF_ARENATYPE, US_SHAREDONLY)) == -1) {
	perror("usconfig(CONF_ARENATYPE)");
	exit(-1);
    }
    if ((us = usinit("/dev/zero")) == NULL) {
	perror("usinit");
	exit(-1);
    }

    if ((uslock = usnewlock(us)) == NULL) {
	perror("usnewlock");
	exit(-1);
    }
}

static void accept_mutex_on_uslock(void)
{
    switch (ussetlock(uslock)) {
    case 1:
	/* got lock */
	break;
    case 0:
	fprintf(stderr, "didn't get lock\n");
	clean_child_exit(APEXIT_CHILDFATAL);
    case -1:
	perror("ussetlock");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

static void accept_mutex_off_uslock(void)
{
    if (usunsetlock(uslock) == -1) {
	perror("usunsetlock");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

accept_mutex_methods_s accept_mutex_uslock_s = {
    NULL,
    accept_mutex_init_uslock,
    accept_mutex_on_uslock,
    accept_mutex_off_uslock,
    "uslock"
};
#endif

#if defined (HAVE_PTHREAD_SERIALIZED_ACCEPT)

/* This code probably doesn't work on many platforms, but it is
 * known to work really fast (at least on Solaris and AIX).
 * Note that pthread mutexes are *NOT* released when a task
 * dies ... the task has to free it itself.  So we block signals and
 * try to be nice about releasing the mutex.
 */

#include <pthread.h>

static pthread_mutex_t *accept_mutex = (void *)(caddr_t) -1;
static int have_accept_mutex;
static sigset_t accept_block_mask;
static sigset_t accept_previous_mask;

static void accept_mutex_child_cleanup_pthread(void *foo)
{
    if (accept_mutex != (void *)(caddr_t)-1
	&& have_accept_mutex) {
	pthread_mutex_unlock(accept_mutex);
    }
}

static void accept_mutex_child_init_pthread(pool *p)
{
    ap_register_cleanup(p, NULL, accept_mutex_child_cleanup_pthread, ap_null_cleanup);
}

static void accept_mutex_cleanup_pthread(void *foo)
{
    if (accept_mutex != (void *)(caddr_t)-1
	&& munmap((caddr_t) accept_mutex, sizeof(*accept_mutex))) {
	perror("munmap");
    }
    accept_mutex = (void *)(caddr_t)-1;
}

/* remove_sync_sigs() is from APR 0.9.4
 *
 * It is invalid to block synchronous signals, as such signals must
 * be delivered on the thread that generated the original error
 * (e.g., invalid storage reference).  Blocking them interferes
 * with proper recovery.
 */
static void remove_sync_sigs(sigset_t *sig_mask)
{
#ifdef SIGABRT
    sigdelset(sig_mask, SIGABRT);
#endif
#ifdef SIGBUS
    sigdelset(sig_mask, SIGBUS);
#endif
#ifdef SIGEMT
    sigdelset(sig_mask, SIGEMT);
#endif
#ifdef SIGFPE
    sigdelset(sig_mask, SIGFPE);
#endif
#ifdef SIGILL
    sigdelset(sig_mask, SIGILL);
#endif
#ifdef SIGIOT
    sigdelset(sig_mask, SIGIOT);
#endif
#ifdef SIGPIPE
    sigdelset(sig_mask, SIGPIPE);
#endif
#ifdef SIGSEGV
    sigdelset(sig_mask, SIGSEGV);
#endif
#ifdef SIGSYS
    sigdelset(sig_mask, SIGSYS);
#endif
#ifdef SIGTRAP
    sigdelset(sig_mask, SIGTRAP);
#endif

/* APR logic to remove SIGUSR2 not copied */
}

static void accept_mutex_init_pthread(pool *p)
{
    pthread_mutexattr_t mattr;
    int fd;

    fd = open("/dev/zero", O_RDWR);
    if (fd == -1) {
	perror("open(/dev/zero)");
	exit(APEXIT_INIT);
    }
    accept_mutex = (pthread_mutex_t *) mmap((caddr_t) 0, sizeof(*accept_mutex),
				 PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (accept_mutex == (void *) (caddr_t) - 1) {
	perror("mmap /dev/zero");
	exit(APEXIT_INIT);
    }
    close(fd);
    if ((errno = pthread_mutexattr_init(&mattr))) {
	perror("pthread_mutexattr_init");
	exit(APEXIT_INIT);
    }
#if !defined(CYGWIN)
    /* Cygwin has problems with this pthread call claiming that these 
     * are "Invalid arguements", Stipe Tolj <tolj@wapme-systems.de>
     */
    if ((errno = pthread_mutexattr_setpshared(&mattr,
						PTHREAD_PROCESS_SHARED))) {
	perror("pthread_mutexattr_setpshared");
	exit(APEXIT_INIT);
    }
#endif
    if ((errno = pthread_mutex_init(accept_mutex, &mattr))) {
	perror("pthread_mutex_init");
	exit(APEXIT_INIT);
    }
    sigfillset(&accept_block_mask);
    sigdelset(&accept_block_mask, SIGHUP);
    sigdelset(&accept_block_mask, SIGTERM);
    sigdelset(&accept_block_mask, SIGUSR1);
    remove_sync_sigs(&accept_block_mask);
    ap_register_cleanup(p, NULL, accept_mutex_cleanup_pthread, ap_null_cleanup);
}

static void accept_mutex_on_pthread(void)
{
    int err;

    if (sigprocmask(SIG_BLOCK, &accept_block_mask, &accept_previous_mask)) {
	perror("sigprocmask(SIG_BLOCK)");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
    /* We need to block alarms here, since if we get killed *right* after 
     * locking the mutex, have_accept_mutex will not be set, and our
     * child cleanup will not work.
     */
    ap_block_alarms();
    if ((err = pthread_mutex_lock(accept_mutex))) {
	errno = err;
	perror("pthread_mutex_lock");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
    have_accept_mutex = 1;
    ap_unblock_alarms();
}

static void accept_mutex_off_pthread(void)
{
    int err;

    /* Have to block alarms here, or else we might have a double-unlock, which
     * is possible with pthread mutexes, since they are designed to be fast,
     * and hence not necessarily make checks for ownership or multiple unlocks.
     */
    ap_block_alarms(); 
    if ((err = pthread_mutex_unlock(accept_mutex))) {
	errno = err;
	perror("pthread_mutex_unlock");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
    have_accept_mutex = 0;
    ap_unblock_alarms();
    if (sigprocmask(SIG_SETMASK, &accept_previous_mask, NULL)) {
	perror("sigprocmask(SIG_SETMASK)");
	clean_child_exit(1);
    }
}

accept_mutex_methods_s accept_mutex_pthread_s = {
    accept_mutex_child_init_pthread,
    accept_mutex_init_pthread,
    accept_mutex_on_pthread,
    accept_mutex_off_pthread,
    "pthread"
};
#endif

#if defined (HAVE_SYSVSEM_SERIALIZED_ACCEPT)

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#ifdef NEED_UNION_SEMUN
union semun {
    int val;
    struct semid_ds *buf;
    ushort *array;
};

#endif

static int sem_id = -1;
static struct sembuf op_on;
static struct sembuf op_off;

/* We get a random semaphore ... the lame sysv semaphore interface
 * means we have to be sure to clean this up or else we'll leak
 * semaphores.
 */
static void accept_mutex_cleanup_sysvsem(void *foo)
{
    union semun ick;

    if (sem_id < 0)
	return;
    /* this is ignored anyhow */
    ick.val = 0;
    semctl(sem_id, 0, IPC_RMID, ick);
}

#define accept_mutex_child_init_sysvsem(x)

static void accept_mutex_init_sysvsem(pool *p)
{
    union semun ick;
    struct semid_ds buf;

    /* acquire the semaphore */
    sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    if (sem_id < 0) {
	perror("semget");
	exit(APEXIT_INIT);
    }
    ick.val = 1;
    if (semctl(sem_id, 0, SETVAL, ick) < 0) {
	perror("semctl(SETVAL)");
	exit(APEXIT_INIT);
    }
    if (!getuid()) {
	/* restrict it to use only by the appropriate user_id ... not that this
	 * stops CGIs from acquiring it and dinking around with it.
	 */
	buf.sem_perm.uid = ap_user_id;
	buf.sem_perm.gid = ap_group_id;
	buf.sem_perm.mode = 0600;
	ick.buf = &buf;
	if (semctl(sem_id, 0, IPC_SET, ick) < 0) {
	    perror("semctl(IPC_SET)");
	    exit(APEXIT_INIT);
	}
    }
    ap_register_cleanup(p, NULL, accept_mutex_cleanup_sysvsem, ap_null_cleanup);

    /* pre-initialize these */
    op_on.sem_num = 0;
    op_on.sem_op = -1;
    op_on.sem_flg = SEM_UNDO;
    op_off.sem_num = 0;
    op_off.sem_op = 1;
    op_off.sem_flg = SEM_UNDO;
}

static void accept_mutex_on_sysvsem(void)
{
    while (semop(sem_id, &op_on, 1) < 0) {
	if (errno != EINTR) {
	    perror("accept_mutex_on");
	    clean_child_exit(APEXIT_CHILDFATAL);
	}
    }
}

static void accept_mutex_off_sysvsem(void)
{
    while (semop(sem_id, &op_off, 1) < 0) {
	if (errno != EINTR) {
	    perror("accept_mutex_off");
	    clean_child_exit(APEXIT_CHILDFATAL);
	}
    }
}

accept_mutex_methods_s accept_mutex_sysvsem_s = {
    NULL,
    accept_mutex_init_sysvsem,
    accept_mutex_on_sysvsem,
    accept_mutex_off_sysvsem,
    "sysvsem"
};
#endif

#if defined(HAVE_FCNTL_SERIALIZED_ACCEPT)
static struct flock lock_it;
static struct flock unlock_it;

static int lock_fd = -1;

#define accept_mutex_child_init_fcntl(x)

/*
 * Initialize mutex lock.
 * Must be safe to call this on a restart.
 */
static void accept_mutex_init_fcntl(pool *p)
{

    lock_it.l_whence = SEEK_SET;	/* from current point */
    lock_it.l_start = 0;		/* -"- */
    lock_it.l_len = 0;			/* until end of file */
    lock_it.l_type = F_WRLCK;		/* set exclusive/write lock */
    lock_it.l_pid = 0;			/* pid not actually interesting */
    unlock_it.l_whence = SEEK_SET;	/* from current point */
    unlock_it.l_start = 0;		/* -"- */
    unlock_it.l_len = 0;		/* until end of file */
    unlock_it.l_type = F_UNLCK;		/* set exclusive/write lock */
    unlock_it.l_pid = 0;		/* pid not actually interesting */

    expand_lock_fname(p);
    lock_fd = ap_popenf_ex(p, ap_lock_fname, O_CREAT | O_WRONLY | O_EXCL, 0644, 1);
    if (lock_fd == -1) {
	perror("open");
	fprintf(stderr, "Cannot open lock file: %s\n", ap_lock_fname);
	exit(APEXIT_INIT);
    }
    unlink(ap_lock_fname);
}

static void accept_mutex_on_fcntl(void)
{
    int ret;

    while ((ret = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0 && errno == EINTR) {
	/* nop */
    }

    if (ret < 0) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
		    "fcntl: F_SETLKW: Error getting accept lock, exiting!  "
		    "Perhaps you need to use the LockFile directive to place "
		    "your lock file on a local disk!");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

static void accept_mutex_off_fcntl(void)
{
    int ret;

    while ((ret = fcntl(lock_fd, F_SETLKW, &unlock_it)) < 0 && errno == EINTR) {
	/* nop */
    }
    if (ret < 0) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
		    "fcntl: F_SETLKW: Error freeing accept lock, exiting!  "
		    "Perhaps you need to use the LockFile directive to place "
		    "your lock file on a local disk!");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

accept_mutex_methods_s accept_mutex_fcntl_s = {
    NULL,
    accept_mutex_init_fcntl,
    accept_mutex_on_fcntl,
    accept_mutex_off_fcntl,
    "fcntl"
};
#endif

#if defined(HAVE_FLOCK_SERIALIZED_ACCEPT)

static int flock_fd = -1;

static void accept_mutex_cleanup_flock(void *foo)
{
    unlink(ap_lock_fname);
}

/*
 * Initialize mutex lock.
 * Done by each child at it's birth
 */
static void accept_mutex_child_init_flock(pool *p)
{

    flock_fd = ap_popenf_ex(p, ap_lock_fname, O_WRONLY, 0600, 1);
    if (flock_fd == -1) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
		    "Child cannot open lock file: %s", ap_lock_fname);
	clean_child_exit(APEXIT_CHILDINIT);
    }
}

/*
 * Initialize mutex lock.
 * Must be safe to call this on a restart.
 */
static void accept_mutex_init_flock(pool *p)
{
    expand_lock_fname(p);
    unlink(ap_lock_fname);
    flock_fd = ap_popenf_ex(p, ap_lock_fname, O_CREAT | O_WRONLY | O_EXCL, 0600, 1);
    if (flock_fd == -1) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
		    "Parent cannot open lock file: %s", ap_lock_fname);
	exit(APEXIT_INIT);
    }
    ap_register_cleanup(p, NULL, accept_mutex_cleanup_flock, ap_null_cleanup);
}

static void accept_mutex_on_flock(void)
{
    int ret;

    while ((ret = flock(flock_fd, LOCK_EX)) < 0 && errno == EINTR)
	continue;

    if (ret < 0) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
		    "flock: LOCK_EX: Error getting accept lock. Exiting!");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

static void accept_mutex_off_flock(void)
{
    if (flock(flock_fd, LOCK_UN) < 0) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
		    "flock: LOCK_UN: Error freeing accept lock. Exiting!");
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

accept_mutex_methods_s accept_mutex_flock_s = {
    accept_mutex_child_init_flock,
    accept_mutex_init_flock,
    accept_mutex_on_flock,
    accept_mutex_off_flock,
    "flock"
};
#endif

#if defined(HAVE_OS2SEM_SERIALIZED_ACCEPT)

static HMTX lock_sem = -1;

static void accept_mutex_cleanup_os2sem(void *foo)
{
    DosReleaseMutexSem(lock_sem);
    DosCloseMutexSem(lock_sem);
}

/*
 * Initialize mutex lock.
 * Done by each child at it's birth
 */
static void accept_mutex_child_init_os2sem(pool *p)
{
    int rc = DosOpenMutexSem(NULL, &lock_sem);

    if (rc != 0) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
		    "Child cannot open lock semaphore, rc=%d", rc);
	clean_child_exit(APEXIT_CHILDINIT);
    } else {
        ap_register_cleanup(p, NULL, accept_mutex_cleanup_os2sem, ap_null_cleanup);
    }
}

/*
 * Initialize mutex lock.
 * Must be safe to call this on a restart.
 */
static void accept_mutex_init_os2sem(pool *p)
{
    int rc = DosCreateMutexSem(NULL, &lock_sem, DC_SEM_SHARED, FALSE);

    if (rc != 0) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
		    "Parent cannot create lock semaphore, rc=%d", rc);
	exit(APEXIT_INIT);
    }

    ap_register_cleanup(p, NULL, accept_mutex_cleanup_os2sem, ap_null_cleanup);
}

static void accept_mutex_on_os2sem(void)
{
    int rc = DosRequestMutexSem(lock_sem, SEM_INDEFINITE_WAIT);

    if (rc != 0) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
		    "OS2SEM: Error %d getting accept lock. Exiting!", rc);
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

static void accept_mutex_off_os2sem(void)
{
    int rc = DosReleaseMutexSem(lock_sem);
    
    if (rc != 0) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
		    "OS2SEM: Error %d freeing accept lock. Exiting!", rc);
	clean_child_exit(APEXIT_CHILDFATAL);
    }
}

accept_mutex_methods_s accept_mutex_os2sem_s = {
    accept_mutex_child_init_os2sem,
    accept_mutex_init_os2sem,
    accept_mutex_on_os2sem,
    accept_mutex_off_os2sem,
    "os2sem"
};
#endif

#if defined(HAVE_TPF_CORE_SERIALIZED_ACCEPT)

static int tpf_core_held;

static void accept_mutex_cleanup_tpfcore(void *foo)
{
    if(tpf_core_held)
        deqc(tpf_mutex_key, QUAL_S);
}

#define accept_mutex_init_tpfcore(x)

static void accept_mutex_child_init_tpfcore(pool *p)
{
    ap_register_cleanup(p, NULL, accept_mutex_cleanup_tpfcore, ap_null_cleanup);
    tpf_core_held = 0;
}

static void accept_mutex_on_tpfcore(void)
{
    enqc(tpf_mutex_key, ENQ_WAIT, 0, QUAL_S);
    tpf_core_held = 1;
    ap_check_signals();
}

static void accept_mutex_off_tpfcore(void)
{
    deqc(tpf_mutex_key, QUAL_S);
    tpf_core_held = 0;
    ap_check_signals();
}

accept_mutex_methods_s accept_mutex_tpfcore_s = {
    accept_mutex_child_init_tpfcore,
    NULL,
    accept_mutex_on_tpfcore,
    accept_mutex_off_tpfcore,
    "tpfcore"
};
#endif

#ifdef HAVE_BEOS_SERIALIZED_ACCEPT
static sem_id _sem = -1;
static int  locked = 0;

static void accept_mutex_child_cleanup_beos(void *foo)
{
    if (_sem > 0 && locked)
        release_sem(_sem);
}

static void accept_mutex_child_init_beos(pool *p)
{
    ap_register_cleanup(p, NULL, accept_mutex_child_cleanup_beos, ap_null_cleanup);
    locked = 0;
}

static void accept_mutex_cleanup_beos(void *foo)
{
    if (_sem > 0)
        delete_sem(_sem);
}

static void accept_mutex_init_beos(pool *p)
{
    _sem = create_sem(1, "httpd_accept");
    if (_sem < 0) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
                    "Parent cannot create lock semaphore, sem=%ld", _sem);
        exit(APEXIT_INIT);
    }

    ap_register_cleanup(p, NULL, accept_mutex_cleanup_beos, ap_null_cleanup);
}                                                                                                        
void accept_mutex_on_beos(void)
{
    if (locked == 0) {
        if (acquire_sem(_sem) == B_OK)
            locked = 1;
    }
}

static void accept_mutex_off_beos(void)
{
    if (locked == 1) {
        if (release_sem(_sem) == B_OK)
            locked = 0; 
    }
}

accept_mutex_methods_s accept_mutex_beos_s = {
    accept_mutex_child_init_beos,
    accept_mutex_init_beos,
    accept_mutex_on_beos,
    accept_mutex_off_beos,
    "beos_sem"
};
#endif /* HAVE_BEOS_SERIALIZED_ACCEPT */


/* Generally, HAVE_NONE_SERIALIZED_ACCEPT simply won't work but
 * for testing purposes, here it is... */
#if defined HAVE_NONE_SERIALIZED_ACCEPT
#if !defined(MULTITHREAD)
/* Multithreaded systems don't complete between processes for
 * the sockets. */
#define NO_SERIALIZED_ACCEPT
#endif 

accept_mutex_methods_s accept_mutex_none_s = {
    NULL,
    NULL,
    NULL,
    NULL,
    "none"
};
#endif

#define AP_FPTR1(x,y)	{ if (x) ((* x)(y)); }
#define AP_FPTR0(x)	{ if (x) ((* x)()); }

#define accept_mutex_child_init(x) 	AP_FPTR1(amutex->child_init,x)
#define accept_mutex_init(x) 		AP_FPTR1(amutex->init,x)
#define accept_mutex_off() 		AP_FPTR0(amutex->off)
#define accept_mutex_on() 		AP_FPTR0(amutex->on)

char *ap_default_mutex_method(void)
{
    char *t;
#if defined USE_USLOCK_SERIALIZED_ACCEPT
    t = "uslock";
#elif defined USE_PTHREAD_SERIALIZED_ACCEPT
    t = "pthread";
#elif defined USE_SYSVSEM_SERIALIZED_ACCEPT
    t = "sysvsem";
#elif defined USE_FCNTL_SERIALIZED_ACCEPT
    t = "fcntl";
#elif defined USE_FLOCK_SERIALIZED_ACCEPT
    t = "flock";
#elif defined USE_OS2SEM_SERIALIZED_ACCEPT
    t = "os2sem";
#elif defined USE_TPF_CORE_SERIALIZED_ACCEPT
    t = "tpfcore";
#elif defined USE_BEOS_SERIALIZED_ACCEPT
    t = "beos_sem";
#elif defined USE_NONE_SERIALIZED_ACCEPT
    t = "none";
#else
    t = "default";
#endif
#if defined HAVE_USLOCK_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"uslock"))))
    	return "uslock";
#endif
#if defined HAVE_PTHREAD_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"pthread"))))
    	return "pthread";
#endif
#if defined HAVE_SYSVSEM_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"sysvsem"))))
    	return "sysvsem";
#endif
#if defined HAVE_FCNTL_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"fcntl"))))
    	return "fcntl";
#endif
#if defined HAVE_FLOCK_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"flock"))))
    	return "flock";
#endif
#if defined HAVE_OS2SEM_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"os2sem"))))
    	return "os2sem";
#endif
#if defined HAVE_TPF_CORE_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"tpfcore"))))
    	return "tpfcore";
#endif
#if defined HAVE_BEOS_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"beos_sem"))))
        return "beos_sem";
#endif
#if defined HAVE_NONE_SERIALIZED_ACCEPT
    if ((!(strcasecmp(t,"default"))) || (!(strcasecmp(t,"none"))))
    	return "none";
#endif

    fprintf(stderr, "No default accept serialization known!!\n");
    exit(APEXIT_INIT);
    /*NOTREACHED */
    return "unknown";
}

char *ap_init_mutex_method(char *t)
{
    if (!(strcasecmp(t,"default")))
	t = ap_default_mutex_method();

#if defined HAVE_USLOCK_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"uslock"))) {
    	amutex = &accept_mutex_uslock_s;
    } else 
#endif
#if defined HAVE_PTHREAD_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"pthread"))) {
    	amutex = &accept_mutex_pthread_s;
    } else 
#endif
#if defined HAVE_SYSVSEM_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"sysvsem"))) {
    	amutex = &accept_mutex_sysvsem_s;
    } else 
#endif
#if defined HAVE_FCNTL_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"fcntl"))) {
    	amutex = &accept_mutex_fcntl_s;
    } else
#endif
#if defined HAVE_FLOCK_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"flock"))) {
    	amutex = &accept_mutex_flock_s;
    } else 
#endif
#if defined HAVE_OS2SEM_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"os2sem"))) {
    	amutex = &accept_mutex_os2sem_s;
    } else 
#endif
#if defined HAVE_TPF_CORE_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"tpfcore"))) {
    	amutex = &accept_mutex_tpfcore_s;
    } else 
#endif
#if defined HAVE_BEOS_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"beos_sem"))) {
        amutex = &accept_mutex_beos_s;
    } else
#endif
#if defined HAVE_NONE_SERIALIZED_ACCEPT
    if (!(strcasecmp(t,"none"))) {
    	amutex = &accept_mutex_none_s;
    } else
#endif
    {
/* Ignore this directive on Windows */
#ifndef WIN32
    if (server_conf) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
                    "Requested serialization method '%s' not available",t);
        exit(APEXIT_INIT);
    } else {
        fprintf(stderr, "Requested serialization method '%s' not available\n", t);
        exit(APEXIT_INIT);
    }
#endif
    }
    return NULL;
}

/* On some architectures it's safe to do unserialized accept()s in the single
 * Listen case.  But it's never safe to do it in the case where there's
 * multiple Listen statements.  Define SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 * when it's safe in the single Listen case.
 */
#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
#define SAFE_ACCEPT(stmt) do {if(ap_listeners->next != ap_listeners) {stmt;}} while(0)
#else
#define SAFE_ACCEPT(stmt) do {stmt;} while(0)
#endif

static void usage(char *bin)
{
    char pad[MAX_STRING_LEN];
    unsigned i;

    for (i = 0; i < strlen(bin); i++)
	pad[i] = ' ';
    pad[i] = '\0';
#ifdef WIN32
    fprintf(stderr, "Usage: %s [-D name] [-d directory] [-f file] [-n service]\n", bin);
    fprintf(stderr, "       %s [-C \"directive\"] [-c \"directive\"] [-k signal]\n", pad);
    fprintf(stderr, "       %s [-v] [-V] [-h] [-l] [-L] [-S] [-t] [-T]\n", pad);
#else /* !WIN32 */
#ifdef SHARED_CORE
    fprintf(stderr, "Usage: %s [-R directory] [-D name] [-d directory] [-f file]\n", bin);
#else
    fprintf(stderr, "Usage: %s [-D name] [-d directory] [-f file]\n", bin);
#endif
    fprintf(stderr, "       %s [-C \"directive\"] [-c \"directive\"]\n", pad);
    fprintf(stderr, "       %s [-v] [-V] [-h] [-l] [-L] [-S] [-t] [-T] [-F]\n", pad);
    fprintf(stderr, "Options:\n");
#ifdef SHARED_CORE
    fprintf(stderr, "  -R directory     : specify an alternate location for shared object files\n");
#endif
#endif /* !WIN32 */
    fprintf(stderr, "  -D name          : define a name for use in <IfDefine name> directives\n");
    fprintf(stderr, "  -d directory     : specify an alternate initial ServerRoot\n");
    fprintf(stderr, "  -f file          : specify an alternate ServerConfigFile\n");
    fprintf(stderr, "  -C \"directive\"   : process directive before reading config files\n");
    fprintf(stderr, "  -c \"directive\"   : process directive after  reading config files\n");
    fprintf(stderr, "  -v               : show version number\n");
    fprintf(stderr, "  -V               : show compile settings\n");
    fprintf(stderr, "  -h               : list available command line options (this page)\n");
    fprintf(stderr, "  -l               : list compiled-in modules\n");
    fprintf(stderr, "  -L               : list available configuration directives\n");
    fprintf(stderr, "  -S               : show parsed settings (currently only vhost settings)\n");
#ifdef NETWARE
    fprintf(stderr, "  -e               : force the display of configuration file errors to the logger screen\n");
    fprintf(stderr, "  -s               : load Apache without a screen\n");
#endif
    fprintf(stderr, "  -t               : run syntax check for config files (with docroot check)\n");
    fprintf(stderr, "  -T               : run syntax check for config files (without docroot check)\n");
#ifndef WIN32
    fprintf(stderr, "  -F               : run main process in foreground, for process supervisors\n");
#endif
#ifdef WIN32
    fprintf(stderr, "  -n name          : name the Apache service for -k options below;\n");
    fprintf(stderr, "  -k stop|shutdown : tell running Apache to shutdown\n");
    fprintf(stderr, "  -k restart       : tell running Apache to do a graceful restart\n");
    fprintf(stderr, "  -k start         : tell Apache to start\n");
    fprintf(stderr, "  -k install   | -i: install an Apache service\n");
    fprintf(stderr, "  -k config        : reconfigure an installed Apache service\n");
    fprintf(stderr, "  -k uninstall | -u: uninstall an Apache service\n");
    fprintf(stderr, "  -W service       : after -k config|install; Apache starts after 'service'\n");
    fprintf(stderr, "  -w               : holds the window open for 30 seconds for fatal errors.\n");
#endif

#if defined(NETWARE)
    clean_parent_exit(0);
#else
    exit(1);
#endif
}



#ifdef NETWARE
/* Thread Storage Data */
typedef struct _TSD {
    conn_rec*		current_conn;
    int 			alarms_blocked;
    int				alarm_pending;
    request_rec*	timeout_req;
    char*			timeout_name;
    JMP_BUF			jmpbuffer;
    int				exit_after_unblock;
    void 			(*alarm_fn) (int);
    unsigned int 	alarm_expiry_time;
} TSD;

static TSD Tsd;

void init_tsd()
{    
    int *thread_ptr;

    memset(&Tsd, 0, sizeof(TSD));
    thread_ptr = __get_thread_data_area_ptr();
    *thread_ptr = (int) &Tsd;
}

#define get_tsd            TSD* tsd = (TSD*) Thread_Data_Area;
#define current_conn       tsd->current_conn
#define alarms_blocked     tsd->alarms_blocked
#define alarm_pending      tsd->alarm_pending
#define timeout_req        tsd->timeout_req
#define timeout_name       tsd->timeout_name
#define jmpbuffer          tsd->jmpbuffer
#define exit_after_unblock tsd->exit_after_unblock
#define alarm_fn           tsd->alarm_fn
#define alarm_expiry_time  tsd->alarm_expiry_time

#else
/*****************************************************************
 *
 * Timeout handling.  DISTINCTLY not thread-safe, but all this stuff
 * has to change for threads anyway.  Note that this code allows only
 * one timeout in progress at a time...
 */

static APACHE_TLS conn_rec *volatile current_conn;
static APACHE_TLS request_rec *volatile timeout_req;
static APACHE_TLS const char *volatile timeout_name = NULL;
static APACHE_TLS int volatile alarms_blocked = 0;
static APACHE_TLS int volatile alarm_pending = 0;
#endif


static void timeout(int sig)
{
    void *dirconf;
#ifdef NETWARE
    get_tsd
#endif    
    if (alarms_blocked) {
	alarm_pending = 1;
	return;
    }
    if (exit_after_unblock) {
	clean_child_exit(0);
    }

    if (!current_conn) {
	ap_longjmp(jmpbuffer, 1);
    }

    if (timeout_req != NULL)
	dirconf = timeout_req->per_dir_config;
    else
	dirconf = current_conn->server->lookup_defaults;
    if (!current_conn->keptalive) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO,
		     current_conn->server, "[client %s] %s timed out",
		     current_conn->remote_ip,
		     timeout_name ? timeout_name : "request");
    }

    if (timeout_req) {
	/* Someone has asked for this transaction to just be aborted
	 * if it times out...
	 */
	request_rec *log_req = timeout_req;
	request_rec *save_req = timeout_req;

	/* avoid looping... if ap_log_transaction started another
	 * timer (say via rfc1413.c) we could loop...
	 */
	timeout_req = NULL;

	while (log_req->main || log_req->prev) {
#ifdef NETWARE
            ThreadSwitch();
#endif
	    /* Get back to original request... */
	    if (log_req->main)
		log_req = log_req->main;
	    else
		log_req = log_req->prev;
	}

	if (!current_conn->keptalive) {
	    /* in some cases we come here before setting the time */
	    if (log_req->request_time == 0) {
                log_req->request_time = time(NULL);
	    }
	    ap_log_transaction(log_req);
	}

	ap_bsetflag(save_req->connection->client, B_EOUT, 1);
	ap_bclose(save_req->connection->client);
	
	if (!ap_standalone)
	    exit(0);
        ap_longjmp(jmpbuffer, 1);
    }
    else {			/* abort the connection */
	ap_bsetflag(current_conn->client, B_EOUT, 1);
	ap_bclose(current_conn->client);
	current_conn->aborted = 1;
    }
}


/*
 * These two called from alloc.c to protect its critical sections...
 * Note that they can nest (as when destroying the sub_pools of a pool
 * which is itself being cleared); we have to support that here.
 */

API_EXPORT(void) ap_block_alarms(void)
{
#ifdef NETWARE
    get_tsd
#endif
    ++alarms_blocked;
}

API_EXPORT(void) ap_unblock_alarms(void)
{
#ifdef NETWARE
    get_tsd
#endif
    --alarms_blocked;
    if (alarms_blocked == 0) {
	if (exit_after_unblock) {
	    /* We have a couple race conditions to deal with here, we can't
	     * allow a timeout that comes in this small interval to allow
	     * the child to jump back to the main loop.  Instead we block
	     * alarms again, and then note that exit_after_unblock is
	     * being dealt with.  We choose this way to solve this so that
	     * the common path through unblock_alarms() is really short.
	     */
	    ++alarms_blocked;
	    exit_after_unblock = 0;
	    clean_child_exit(0);
	}
	if (alarm_pending) {
	    alarm_pending = 0;
	    timeout(0);
	}
    }
}

#ifndef NETWARE
static APACHE_TLS void (*volatile alarm_fn) (int) = NULL;
#endif
#if defined(WIN32) || defined(CYGWIN_WINSOCK) 
static APACHE_TLS unsigned int alarm_expiry_time = 0;
#endif /* WIN32 */

#if !defined(WIN32)  && !defined(NETWARE)
static void alrm_handler(int sig)
{
#ifdef TPF41
    signal(sig, exit);
#endif
    if (alarm_fn) {
	(*alarm_fn) (sig);
    }
}
#endif

API_EXPORT(unsigned int) ap_set_callback_and_alarm(void (*fn) (int), int x)
{
    unsigned int old;

#if defined(WIN32) || defined(NETWARE)
    time_t now = time(NULL);
#ifdef NETWARE
    get_tsd
#endif
    old = alarm_expiry_time;

    if (old)
	old -= now;
    if (x == 0) {
	alarm_fn = NULL;
	alarm_expiry_time = 0;
    }
    else {
	alarm_fn = fn;
	alarm_expiry_time = now + x;
    }
#else
    if (alarm_fn && x && fn != alarm_fn) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, NULL,
	    "ap_set_callback_and_alarm: possible nested timer!");
    }
    alarm_fn = fn;
#ifndef OPTIMIZE_TIMEOUTS
    old = alarm(x);
#else
    if (child_timeouts) {
	old = alarm(x);
    }
    else {
	/* Just note the timeout in our scoreboard, no need to call the system.
	 * We also note that the virtual time has gone forward.
	 */
	ap_check_signals();
	old = ap_scoreboard_image->servers[my_child_num].timeout_len;
	ap_scoreboard_image->servers[my_child_num].timeout_len = x;
	++ap_scoreboard_image->servers[my_child_num].cur_vtime;
    }
#endif
#endif
    return (old);
}


#if defined(WIN32) || defined(NETWARE) || defined(CYGWIN_WINSOCK) 
API_EXPORT(int) ap_check_alarm(void)
{
#ifdef NETWARE
    get_tsd
#endif
    if (alarm_expiry_time) {
	unsigned int t;

	t = time(NULL);
	if (t >= alarm_expiry_time) {
	    alarm_expiry_time = 0;
	    (*alarm_fn) (0);
	    return (-1);
	}
	else {
	    return (alarm_expiry_time - t);
	}
    }
    else
	return (0);
}
#endif /* WIN32 */

#ifdef TPF
API_EXPORT(int) ap_check_alarm(void)
{
   int i;

#ifdef OPTIMIZE_TIMEOUTS
   /* just pull the timeout from the scoreboard */
   ap_sync_scoreboard_image();
   i = ap_scoreboard_image->servers[my_child_num].timeout_len;
#else
   i = ap_set_callback_and_alarm(alarm_fn, 3); /* determine time left */
   /* the 3 seconds is just an arbitrary amount of time to keep the alarm
      from expiring before it is reset on this next line: */
   ap_set_callback_and_alarm(alarm_fn, i); /* restore time left */
#endif

   return i;                               /* return the time left */
}

#endif /* TPF */

/* reset_timeout (request_rec *) resets the timeout in effect,
 * as long as it hasn't expired already.
 */

API_EXPORT(void) ap_reset_timeout(request_rec *r)
{
    int i;
#ifdef NETWARE
    get_tsd
#endif
    if (timeout_name) {		/* timeout has been set */
	i = ap_set_callback_and_alarm(alarm_fn, r->server->timeout);
	if (i == 0)		/* timeout already expired, so set it back to 0 */
	    ap_set_callback_and_alarm(alarm_fn, 0);
    }
}




API_EXPORT(void) ap_keepalive_timeout(char *name, request_rec *r)
{
    unsigned int to;
#ifdef NETWARE
    get_tsd
#endif
    timeout_req = r;
    timeout_name = name;
    if (r->connection->keptalive)
	to = r->server->keep_alive_timeout;
    else
	to = r->server->timeout;
    ap_set_callback_and_alarm(timeout, to);
}

API_EXPORT(void) ap_hard_timeout(char *name, request_rec *r)
{
#ifdef NETWARE
    get_tsd
#endif
    timeout_req = r;
    timeout_name = name;
    ap_set_callback_and_alarm(timeout, r->server->timeout);
}

API_EXPORT(void) ap_soft_timeout(char *name, request_rec *r)
{
#ifdef NETWARE
    get_tsd
#endif
    timeout_name = name;
    ap_set_callback_and_alarm(timeout, r->server->timeout);
}

API_EXPORT(void) ap_kill_timeout(request_rec *dummy)
{
#ifdef NETWARE
    get_tsd
#endif
    ap_check_signals();
    ap_set_callback_and_alarm(NULL, 0);
    timeout_req = NULL;
    timeout_name = NULL;
}


/*
 * More machine-dependent networking gooo... on some systems,
 * you've got to be *really* sure that all the packets are acknowledged
 * before closing the connection, since the client will not be able
 * to see the last response if their TCP buffer is flushed by a RST
 * packet from us, which is what the server's TCP stack will send
 * if it receives any request data after closing the connection.
 *
 * In an ideal world, this function would be accomplished by simply
 * setting the socket option SO_LINGER and handling it within the
 * server's TCP stack while the process continues on to the next request.
 * Unfortunately, it seems that most (if not all) operating systems
 * block the server process on close() when SO_LINGER is used.
 * For those that don't, see USE_SO_LINGER below.  For the rest,
 * we have created a home-brew lingering_close.
 *
 * Many operating systems tend to block, puke, or otherwise mishandle
 * calls to shutdown only half of the connection.  You should define
 * NO_LINGCLOSE in ap_config.h if such is the case for your system.
 */
#ifndef MAX_SECS_TO_LINGER
#define MAX_SECS_TO_LINGER 30
#endif

#ifdef USE_SO_LINGER
#define NO_LINGCLOSE		/* The two lingering options are exclusive */

static void sock_enable_linger(int s)
{
    struct linger li;

    li.l_onoff = 1;
    li.l_linger = MAX_SECS_TO_LINGER;

    if (setsockopt(s, SOL_SOCKET, SO_LINGER,
		   (char *) &li, sizeof(struct linger)) < 0) {
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
	            "setsockopt: (SO_LINGER)");
	/* not a fatal error */
    }
}

#else
#define sock_enable_linger(s)	/* NOOP */
#endif /* USE_SO_LINGER */

#ifndef NO_LINGCLOSE

/* Special version of timeout for lingering_close */

static void lingerout(int sig)
{
#ifdef NETWARE
    get_tsd
#endif
    if (alarms_blocked) {
	alarm_pending = 1;
	return;
    }

    if (!current_conn) {
	ap_longjmp(jmpbuffer, 1);
    }
    ap_bsetflag(current_conn->client, B_EOUT, 1);
    current_conn->aborted = 1;
}

static void linger_timeout(void)
{
#ifdef NETWARE
    get_tsd
#endif
    timeout_name = "lingering close";
    ap_set_callback_and_alarm(lingerout, MAX_SECS_TO_LINGER);
}

/* Since many clients will abort a connection instead of closing it,
 * attempting to log an error message from this routine will only
 * confuse the webmaster.  There doesn't seem to be any portable way to
 * distinguish between a dropped connection and something that might be
 * worth logging.
 */
static void lingering_close(request_rec *r)
{
    char dummybuf[512];
    struct timeval tv;
    fd_set lfds;
    int select_rv;
    int lsd;

    /* Prevent a slow-drip client from holding us here indefinitely */

    linger_timeout();

    /* Send any leftover data to the client, but never try to again */

    if (ap_bflush(r->connection->client) == -1) {
	ap_kill_timeout(r);
	ap_bclose(r->connection->client);
	return;
    }
    ap_bsetflag(r->connection->client, B_EOUT, 1);

    /* Close our half of the connection --- send the client a FIN */

    lsd = r->connection->client->fd;

    if ((shutdown(lsd, 1) != 0) || r->connection->aborted) {
	ap_kill_timeout(r);
	ap_bclose(r->connection->client);
	return;
    }

    /* Set up to wait for readable data on socket... */

    FD_ZERO(&lfds);

    /* Wait for readable data or error condition on socket;
     * slurp up any data that arrives...  We exit when we go for an
     * interval of tv length without getting any more data, get an error
     * from select(), get an error or EOF on a read, or the timer expires.
     */

    do {
	/* We use a 2 second timeout because current (Feb 97) browsers
	 * fail to close a connection after the server closes it.  Thus,
	 * to avoid keeping the child busy, we are only lingering long enough
	 * for a client that is actively sending data on a connection.
	 * This should be sufficient unless the connection is massively
	 * losing packets, in which case we might have missed the RST anyway.
	 * These parameters are reset on each pass, since they might be
	 * changed by select.
	 */
#ifdef NETWARE
        ThreadSwitch();
#endif

	FD_SET(lsd, &lfds);
	tv.tv_sec = 2;
	tv.tv_usec = 0;

	select_rv = ap_select(lsd + 1, &lfds, NULL, NULL, &tv);

    } while ((select_rv > 0) &&
#if defined(WIN32) || defined(NETWARE)
             (recv(lsd, dummybuf, sizeof(dummybuf), 0) > 0));
#else
             (read(lsd, dummybuf, sizeof(dummybuf)) > 0));
#endif

    /* Should now have seen final ack.  Safe to finally kill socket */

    ap_bclose(r->connection->client);

    ap_kill_timeout(r);
}
#endif /* ndef NO_LINGCLOSE */

/*****************************************************************
 * dealing with other children
 */

#ifndef NO_OTHER_CHILD
API_EXPORT(void) ap_register_other_child(int pid,
		       void (*maintenance) (int reason, void *, ap_wait_t status),
			  void *data, int write_fd)
{
    other_child_rec *ocr;

    ocr = ap_palloc(pconf, sizeof(*ocr));
    ocr->pid = pid;
    ocr->maintenance = maintenance;
    ocr->data = data;
    ocr->write_fd = write_fd;
    ocr->next = other_children;
    other_children = ocr;
}

/* note that since this can be called by a maintenance function while we're
 * scanning the other_children list, all scanners should protect themself
 * by loading ocr->next before calling any maintenance function.
 */
API_EXPORT(void) ap_unregister_other_child(void *data)
{
    other_child_rec **pocr, *nocr;

    for (pocr = &other_children; *pocr; pocr = &(*pocr)->next) {
	if ((*pocr)->data == data) {
	    nocr = (*pocr)->next;
	    (*(*pocr)->maintenance) (OC_REASON_UNREGISTER, (*pocr)->data, (ap_wait_t)-1);
	    *pocr = nocr;
	    /* XXX: um, well we've just wasted some space in pconf ? */
	    return;
	}
    }
}

/* test to ensure that the write_fds are all still writable, otherwise
 * invoke the maintenance functions as appropriate */
static void probe_writable_fds(void)
{
    fd_set writable_fds;
    int fd_max;
    other_child_rec *ocr, *nocr;
    struct timeval tv;
    int rc;

    if (other_children == NULL)
	return;

    fd_max = 0;
    FD_ZERO(&writable_fds);
    do {
	for (ocr = other_children; ocr; ocr = ocr->next) {
	    if (ocr->write_fd == -1)
		continue;
	    FD_SET(ocr->write_fd, &writable_fds);
	    if (ocr->write_fd > fd_max) {
		fd_max = ocr->write_fd;
	    }
	}
	if (fd_max == 0)
	    return;

	tv.tv_sec = 0;
	tv.tv_usec = 0;
	rc = ap_select(fd_max + 1, NULL, &writable_fds, NULL, &tv);
    } while (rc == -1 && errno == EINTR);

    if (rc == -1) {
	/* XXX: uhh this could be really bad, we could have a bad file
	 * descriptor due to a bug in one of the maintenance routines */
	ap_log_unixerr("probe_writable_fds", "select",
		    "could not probe writable fds", server_conf);
	return;
    }
    if (rc == 0)
	return;

    for (ocr = other_children; ocr; ocr = nocr) {
	nocr = ocr->next;
	if (ocr->write_fd == -1)
	    continue;
	if (FD_ISSET(ocr->write_fd, &writable_fds))
	    continue;
	(*ocr->maintenance) (OC_REASON_UNWRITABLE, ocr->data, (ap_wait_t)-1);
    }
}

/* possibly reap an other_child, return 0 if yes, -1 if not */
static int reap_other_child(int pid, ap_wait_t status)
{
    other_child_rec *ocr, *nocr;

    for (ocr = other_children; ocr; ocr = nocr) {
	nocr = ocr->next;
	if (ocr->pid != pid)
	    continue;
	ocr->pid = -1;
	(*ocr->maintenance) (OC_REASON_DEATH, ocr->data, status);
	return 0;
    }
    return -1;
}
#endif

/*****************************************************************
 *
 * Dealing with the scoreboard... a lot of these variables are global
 * only to avoid getting clobbered by the longjmp() that happens when
 * a hard timeout expires...
 *
 * We begin with routines which deal with the file itself... 
 */

#ifdef MULTITHREAD
/*
 * In the multithreaded mode, have multiple threads - not multiple
 * processes that need to talk to each other. Just use a simple
 * malloc. But let the routines that follow, think that you have
 * shared memory (so they use memcpy etc.)
 */

static void reinit_scoreboard(pool *p)
{
    ap_assert(!ap_scoreboard_image);
    ap_scoreboard_image = (scoreboard *) malloc(SCOREBOARD_SIZE);
    if (ap_scoreboard_image == NULL) {
	fprintf(stderr, "Ouch!  Out of memory reiniting scoreboard!\n");
    }
    memset(ap_scoreboard_image, 0, SCOREBOARD_SIZE);
}

void cleanup_scoreboard(void)
{
    ap_assert(ap_scoreboard_image);
    free(ap_scoreboard_image);
    ap_scoreboard_image = NULL;
}

API_EXPORT(void) ap_sync_scoreboard_image(void)
{
}


#else /* MULTITHREAD */
#if defined(USE_OS2_SCOREBOARD)

/* The next two routines are used to access shared memory under OS/2.  */
/* This requires EMX v09c to be installed.                           */

caddr_t create_shared_heap(const char *name, size_t size)
{
    ULONG rc;
    void *mem;
    Heap_t h;

    rc = DosAllocSharedMem(&mem, name, size,
			   PAG_COMMIT | PAG_READ | PAG_WRITE);
    if (rc != 0)
	return NULL;
    h = _ucreate(mem, size, !_BLOCK_CLEAN, _HEAP_REGULAR | _HEAP_SHARED,
		 NULL, NULL);
    if (h == NULL)
	DosFreeMem(mem);
    return (caddr_t) h;
}

caddr_t get_shared_heap(const char *Name)
{

    PVOID BaseAddress;		/* Pointer to the base address of
				   the shared memory object */
    ULONG AttributeFlags;	/* Flags describing characteristics
				   of the shared memory object */
    APIRET rc;			/* Return code */

    /* Request read and write access to */
    /*   the shared memory object       */
    AttributeFlags = PAG_WRITE | PAG_READ;

    rc = DosGetNamedSharedMem(&BaseAddress, Name, AttributeFlags);

    if (rc != 0) {
	printf("DosGetNamedSharedMem error: return code = %ld", rc);
	return 0;
    }

    return BaseAddress;
}

static void setup_shared_mem(pool *p)
{
    caddr_t m;

    int rc;

    m = (caddr_t) create_shared_heap("\\SHAREMEM\\SCOREBOARD", SCOREBOARD_SIZE);
    if (m == 0) {
	fprintf(stderr, "%s: Could not create OS/2 Shared memory pool.\n",
		ap_server_argv0);
	exit(APEXIT_INIT);
    }

    rc = _uopen((Heap_t) m);
    if (rc != 0) {
	fprintf(stderr,
		"%s: Could not uopen() newly created OS/2 Shared memory pool.\n",
		ap_server_argv0);
    }
    ap_scoreboard_image = (scoreboard *) m;
    ap_scoreboard_image->global.running_generation = 0;
}

static void reopen_scoreboard(pool *p)
{
    caddr_t m;
    int rc;

    m = (caddr_t) get_shared_heap("\\SHAREMEM\\SCOREBOARD");
    if (m == 0) {
	fprintf(stderr, "%s: Could not find existing OS/2 Shared memory pool.\n",
		ap_server_argv0);
	exit(APEXIT_INIT);
    }

    rc = _uopen((Heap_t) m);
    ap_scoreboard_image = (scoreboard *) m;
}

#elif defined(USE_POSIX_SCOREBOARD)
#include <sys/mman.h>
/* 
 * POSIX 1003.4 style
 *
 * Note 1: 
 * As of version 4.23A, shared memory in QNX must reside under /dev/shmem,
 * where no subdirectories allowed.
 *
 * POSIX shm_open() and shm_unlink() will take care about this issue,
 * but to avoid confusion, I suggest to redefine scoreboard file name
 * in httpd.conf to cut "logs/" from it. With default setup actual name
 * will be "/dev/shmem/logs.apache_status". 
 * 
 * If something went wrong and Apache did not unlinked this object upon
 * exit, you can remove it manually, using "rm -f" command.
 * 
 * Note 2:
 * <sys/mman.h> in QNX defines MAP_ANON, but current implementation 
 * does NOT support BSD style anonymous mapping. So, the order of 
 * conditional compilation is important: 
 * this #ifdef section must be ABOVE the next one (BSD style).
 *
 * I tested this stuff and it works fine for me, but if it provides 
 * trouble for you, just comment out USE_MMAP_SCOREBOARD in QNX section
 * of ap_config.h
 *
 * June 5, 1997, 
 * Igor N. Kovalenko -- infoh@mail.wplus.net
 */

static void cleanup_shared_mem(void *d)
{
    shm_unlink(ap_scoreboard_fname);
}

static void setup_shared_mem(pool *p)
{
    char buf[512];
    caddr_t m;
    int fd;

    fd = shm_open(ap_scoreboard_fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
	ap_snprintf(buf, sizeof(buf), "%s: could not open(create) scoreboard",
		    ap_server_argv0);
	perror(buf);
	exit(APEXIT_INIT);
    }
    if (ltrunc(fd, (off_t) SCOREBOARD_SIZE, SEEK_SET) == -1) {
	ap_snprintf(buf, sizeof(buf), "%s: could not ltrunc scoreboard",
		    ap_server_argv0);
	perror(buf);
	shm_unlink(ap_scoreboard_fname);
	exit(APEXIT_INIT);
    }
    if ((m = (caddr_t) mmap((caddr_t) 0,
			    (size_t) SCOREBOARD_SIZE, PROT_READ | PROT_WRITE,
			    MAP_SHARED, fd, (off_t) 0)) == (caddr_t) - 1) {
	ap_snprintf(buf, sizeof(buf), "%s: cannot mmap scoreboard",
		    ap_server_argv0);
	perror(buf);
	shm_unlink(ap_scoreboard_fname);
	exit(APEXIT_INIT);
    }
    close(fd);
    ap_register_cleanup(p, NULL, cleanup_shared_mem, ap_null_cleanup);
    ap_scoreboard_image = (scoreboard *) m;
    ap_scoreboard_image->global.running_generation = 0;
}

static void reopen_scoreboard(pool *p)
{
}

#elif defined(USE_MMAP_SCOREBOARD)

static void setup_shared_mem(pool *p)
{
    caddr_t m;

#if defined(MAP_ANON)
/* BSD style */
#ifdef CONVEXOS11
    /*
     * 9-Aug-97 - Jeff Venters (venters@convex.hp.com)
     * ConvexOS maps address space as follows:
     *   0x00000000 - 0x7fffffff : Kernel
     *   0x80000000 - 0xffffffff : User
     * Start mmapped area 1GB above start of text.
     *
     * Also, the length requires a pointer as the actual length is
     * returned (rounded up to a page boundary).
     */
    {
	unsigned len = SCOREBOARD_SIZE;

	m = mmap((caddr_t) 0xC0000000, &len,
		 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, NOFD, 0);
    }
#elif defined(MAP_TMPFILE)
    {
	char mfile[] = "/tmp/apache_shmem_XXXX";
	int fd = mkstemp(mfile);
	if (fd == -1) {
	    perror("open");
	    fprintf(stderr, "%s: Could not open %s\n", ap_server_argv0, mfile);
	    exit(APEXIT_INIT);
	}
	m = mmap((caddr_t) 0, SCOREBOARD_SIZE,
		PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (m == (caddr_t) - 1) {
	    perror("mmap");
	    fprintf(stderr, "%s: Could not mmap %s\n", ap_server_argv0, mfile);
	    exit(APEXIT_INIT);
	}
	close(fd);
	unlink(mfile);
    }
#else
    m = mmap((caddr_t) 0, SCOREBOARD_SIZE,
	     PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
#endif
    if (m == (caddr_t) - 1) {
	perror("mmap");
	fprintf(stderr, "%s: Could not mmap memory\n", ap_server_argv0);
	exit(APEXIT_INIT);
    }
#else
/* Sun style */
    int fd;

    fd = open("/dev/zero", O_RDWR);
    if (fd == -1) {
	perror("open");
	fprintf(stderr, "%s: Could not open /dev/zero\n", ap_server_argv0);
	exit(APEXIT_INIT);
    }
    m = mmap((caddr_t) 0, SCOREBOARD_SIZE,
	     PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (m == (caddr_t) - 1) {
	perror("mmap");
	fprintf(stderr, "%s: Could not mmap /dev/zero\n", ap_server_argv0);
	exit(APEXIT_INIT);
    }
    close(fd);
#endif
    ap_scoreboard_image = (scoreboard *) m;
    ap_scoreboard_image->global.running_generation = 0;
}

static void reopen_scoreboard(pool *p)
{
}

#elif defined(USE_SHMGET_SCOREBOARD)
static key_t shmkey = IPC_PRIVATE;
static int shmid = -1;

static void setup_shared_mem(pool *p)
{
    struct shmid_ds shmbuf;
#ifdef MOVEBREAK
    char *obrk;
#endif

    if ((shmid = shmget(shmkey, SCOREBOARD_SIZE, IPC_CREAT | SHM_R | SHM_W)) == -1) {
#ifdef LINUX
	if (errno == ENOSYS) {
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, server_conf,
			 "Your kernel was built without CONFIG_SYSVIPC\n"
			 "%s: Please consult the Apache FAQ for details",
			 ap_server_argv0);
	}
#endif
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
                     "could not call shmget");
	exit(APEXIT_INIT);
    }

    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf,
		"created shared memory segment #%d", shmid);

#ifdef MOVEBREAK
    /*
     * Some SysV systems place the shared segment WAY too close
     * to the dynamic memory break point (sbrk(0)). This severely
     * limits the use of malloc/sbrk in the program since sbrk will
     * refuse to move past that point.
     *
     * To get around this, we move the break point "way up there",
     * attach the segment and then move break back down. Ugly
     */
    if ((obrk = sbrk(MOVEBREAK)) == (char *) -1) {
	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
	    "sbrk() could not move break");
    }
#endif

#define BADSHMAT	((scoreboard *)(-1))
    if ((ap_scoreboard_image = (scoreboard *) shmat(shmid, 0, 0)) == BADSHMAT) {
	ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf, "shmat error");
	/*
	 * We exit below, after we try to remove the segment
	 */
    }
    /* only worry about permissions if we attached the segment
       and we want/need to change the uid/gid */
    else if (ap_change_shmem_uid) {
	if (shmctl(shmid, IPC_STAT, &shmbuf) != 0) {
	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
		"shmctl() could not stat segment #%d", shmid);
	}
	else {
	    shmbuf.shm_perm.uid = ap_user_id;
	    shmbuf.shm_perm.gid = ap_group_id;
	    if (shmctl(shmid, IPC_SET, &shmbuf) != 0) {
		ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
		    "shmctl() could not set segment #%d", shmid);
	    }
	}
    }
    /*
     * We must avoid leaving segments in the kernel's
     * (small) tables.
     */
    if (shmctl(shmid, IPC_RMID, NULL) != 0) {
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
		"shmctl: IPC_RMID: could not remove shared memory segment #%d",
		shmid);
    }
    if (ap_scoreboard_image == BADSHMAT)	/* now bailout */
	exit(APEXIT_INIT);

#ifdef MOVEBREAK
    if (obrk == (char *) -1)
	return;			/* nothing else to do */
    if (sbrk(-(MOVEBREAK)) == (char *) -1) {
	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
	    "sbrk() could not move break back");
    }
#endif
    ap_scoreboard_image->global.running_generation = 0;
}

static void reopen_scoreboard(pool *p)
{
}

#else
#define SCOREBOARD_FILE
static scoreboard _scoreboard_image;
static int scoreboard_fd = -1;

/* XXX: things are seriously screwed if we ever have to do a partial
 * read or write ... we could get a corrupted scoreboard
 */
static int force_write(int fd, void *buffer, int bufsz)
{
    int rv, orig_sz = bufsz;

    do {
	rv = write(fd, buffer, bufsz);
	if (rv > 0) {
	    buffer = (char *) buffer + rv;
	    bufsz -= rv;
	}
    } while ((rv > 0 && bufsz > 0) || (rv == -1 && errno == EINTR));

    return rv < 0 ? rv : orig_sz - bufsz;
}

static int force_read(int fd, void *buffer, int bufsz)
{
    int rv, orig_sz = bufsz;

    do {
	rv = read(fd, buffer, bufsz);
	if (rv > 0) {
	    buffer = (char *) buffer + rv;
	    bufsz -= rv;
	}
    } while ((rv > 0 && bufsz > 0) || (rv == -1 && errno == EINTR));

    return rv < 0 ? rv : orig_sz - bufsz;
}

static void cleanup_scoreboard_file(void *foo)
{
    unlink(ap_scoreboard_fname);
}

void reopen_scoreboard(pool *p)
{
    if (scoreboard_fd != -1)
	ap_pclosef(p, scoreboard_fd);

#ifdef TPF
    ap_scoreboard_fname = ap_server_root_relative(p, ap_scoreboard_fname);
#endif /* TPF */
    scoreboard_fd = ap_popenf_ex(p, ap_scoreboard_fname, O_CREAT | O_BINARY | O_RDWR, 0666, 1);
    if (scoreboard_fd == -1) {
	perror(ap_scoreboard_fname);
	fprintf(stderr, "Cannot open scoreboard file:\n");
	clean_child_exit(1);
    }
}
#endif

/* Called by parent process */
static void reinit_scoreboard(pool *p)
{
    int running_gen = 0;
    if (ap_scoreboard_image)
	running_gen = ap_scoreboard_image->global.running_generation;

#ifndef SCOREBOARD_FILE
    if (ap_scoreboard_image == NULL) {
	setup_shared_mem(p);
    }
    memset(ap_scoreboard_image, 0, SCOREBOARD_SIZE);
    ap_scoreboard_image->global.running_generation = running_gen;
#else
    ap_scoreboard_image = &_scoreboard_image;
    ap_scoreboard_fname = ap_server_root_relative(p, ap_scoreboard_fname);

    scoreboard_fd = ap_popenf_ex(p, ap_scoreboard_fname, O_CREAT | O_BINARY | O_RDWR, 0644, 1);
    if (scoreboard_fd == -1) {
	perror(ap_scoreboard_fname);
	fprintf(stderr, "Cannot open scoreboard file:\n");
	exit(APEXIT_INIT);
    }
    ap_register_cleanup(p, NULL, cleanup_scoreboard_file, ap_null_cleanup);

    memset((char *) ap_scoreboard_image, 0, sizeof(*ap_scoreboard_image));
    ap_scoreboard_image->global.running_generation = running_gen;
    force_write(scoreboard_fd, ap_scoreboard_image, sizeof(*ap_scoreboard_image));
#endif
}

/* Routines called to deal with the scoreboard image
 * --- note that we do *not* need write locks, since update_child_status
 * only updates a *single* record in place, and only one process writes to
 * a given scoreboard slot at a time (either the child process owning that
 * slot, or the parent, noting that the child has died).
 *
 * As a final note --- setting the score entry to getpid() is always safe,
 * since when the parent is writing an entry, it's only noting SERVER_DEAD
 * anyway.
 */

ap_inline void ap_sync_scoreboard_image(void)
{
#ifdef SCOREBOARD_FILE
    lseek(scoreboard_fd, 0L, 0);
    force_read(scoreboard_fd, ap_scoreboard_image, sizeof(*ap_scoreboard_image));
#endif
}

#endif /* MULTITHREAD */

API_EXPORT(int) ap_exists_scoreboard_image(void)
{
    return (ap_scoreboard_image ? 1 : 0);
}

static ap_inline void put_scoreboard_info(int child_num,
				       short_score *new_score_rec)
{
#ifdef SCOREBOARD_FILE
    lseek(scoreboard_fd, (long) child_num * sizeof(short_score), 0);
    force_write(scoreboard_fd, new_score_rec, sizeof(short_score));
#endif
}

/* a clean exit from the parent with proper cleanup */
#ifdef NETWARE
void clean_shutdown_on_exit(void)
{
    if (!ap_main_finished) {
        AMCSocketCleanup();
        ap_destroy_pool(pcommands);    
        free(ap_loaded_modules);    
        ap_cleanup_method_ptrs();    
        ap_destroy_pool(pglobal);
        ap_cleanup_alloc();
        ap_main_finished = TRUE;
    }
}

void clean_parent_exit(int code) __attribute__((noreturn));
void clean_parent_exit(int code)
#else
static void clean_parent_exit(int code) __attribute__((noreturn));
static void clean_parent_exit(int code)
#endif
{
#ifdef NETWARE
    AMCSocketCleanup();
    ap_destroy_pool(pcommands);    
    free(ap_loaded_modules);    
    ap_cleanup_method_ptrs();    
    ap_destroy_pool(pglobal);
    ap_cleanup_alloc();
    ap_main_finished = TRUE;
#else
    /* Clear the pool - including any registered cleanups */
    ap_destroy_pool(pglobal);
#endif
    exit(code);
}

API_EXPORT(int) ap_update_child_status(int child_num, int status, request_rec *r)
{
    int old_status;
    short_score *ss;

    if (child_num < 0)
	return -1;

    ap_check_signals();

    ap_sync_scoreboard_image();
    ss = &ap_scoreboard_image->servers[child_num];
    old_status = ss->status;
    ss->status = status;
#ifdef NETWARE
    ap_scoreboard_image->parent[child_num].pid = GetThreadID();
#endif

#ifdef OPTIMIZE_TIMEOUTS
    ++ss->cur_vtime;
#endif

    if (ap_extended_status) {
#ifndef OPTIMIZE_TIMEOUTS
	ss->last_used = time(NULL);
#endif
	if (status == SERVER_READY || status == SERVER_DEAD) {
	    /*
	     * Reset individual counters
	     */
	    if (status == SERVER_DEAD) {
		ss->my_access_count = 0L;
		ss->my_bytes_served = 0L;
	    }
	    ss->conn_count = (unsigned short) 0;
	    ss->conn_bytes = (unsigned long) 0;
	}
        else if (status == SERVER_STARTING) {
            /* clean out the start_time so that mod_status will print Req=0 */
            /* Use memset to be independent from the type (struct timeval vs. clock_t) */
            memset (&ss->start_time, '\0', sizeof ss->start_time);
        }
	if (r) {
	    conn_rec *c = r->connection;
	    ap_cpystrn(ss->client, ap_get_remote_host(c, r->per_dir_config,
				  REMOTE_NOLOOKUP), sizeof(ss->client));
	    if (r->the_request == NULL) {
		    ap_cpystrn(ss->request, "NULL", sizeof(ss->request));
	    } else if (r->parsed_uri.password == NULL) {
		    ap_cpystrn(ss->request, r->the_request, sizeof(ss->request));
	    } else {
		/* Don't reveal the password in the server-status view */
		    ap_cpystrn(ss->request, ap_pstrcat(r->pool, r->method, " ",
					       ap_unparse_uri_components(r->pool, &r->parsed_uri, UNP_OMITPASSWORD),
					       r->assbackwards ? NULL : " ", r->protocol, NULL),
				       sizeof(ss->request));
	    }
	    ss->vhostrec =  r->server;
	}
    }
    if (status == SERVER_DEAD) {
        ap_scoreboard_image->parent[child_num].pid = 0;
    }
    else if (status == SERVER_STARTING && r == NULL) {
	/* clean up the slot's vhostrec pointer (maybe re-used)
	 * and mark the slot as belonging to a new generation.
	 */
	ss->vhostrec = NULL;
	ap_scoreboard_image->parent[child_num].generation = ap_my_generation;
#ifdef SCOREBOARD_FILE
	lseek(scoreboard_fd, XtOffsetOf(scoreboard, parent[child_num]), 0);
	force_write(scoreboard_fd, &ap_scoreboard_image->parent[child_num],
	    sizeof(parent_score));
#endif
    }
    put_scoreboard_info(child_num, ss);

    return old_status;
}

static void update_scoreboard_global(void)
{
#ifdef SCOREBOARD_FILE
    lseek(scoreboard_fd,
	  (char *) &ap_scoreboard_image->global -(char *) ap_scoreboard_image, 0);
    force_write(scoreboard_fd, &ap_scoreboard_image->global,
		sizeof ap_scoreboard_image->global);
#endif
}

void ap_time_process_request(int child_num, int status)
{
    short_score *ss;
#if defined(NO_GETTIMEOFDAY) && !defined(NO_TIMES)
    struct tms tms_blk;
#endif

    if (child_num < 0)
	return;

    ap_sync_scoreboard_image();
    ss = &ap_scoreboard_image->servers[child_num];

    if (status == START_PREQUEST) {
#if defined(NO_GETTIMEOFDAY)
#ifndef NO_TIMES
	if ((ss->start_time = times(&tms_blk)) == -1)
#endif /* NO_TIMES */
	    ss->start_time = (clock_t) 0;
#else
	if (gettimeofday(&ss->start_time, (struct timezone *) 0) < 0)
	    ss->start_time.tv_sec =
		ss->start_time.tv_usec = 0L;
#endif
    }
    else if (status == STOP_PREQUEST) {
#if defined(NO_GETTIMEOFDAY)
#ifndef NO_TIMES
	if ((ss->stop_time = times(&tms_blk)) == -1)
#endif
	    ss->stop_time = ss->start_time = (clock_t) 0;
#else
	if (gettimeofday(&ss->stop_time, (struct timezone *) 0) < 0)
	    ss->stop_time.tv_sec =
		ss->stop_time.tv_usec =
		ss->start_time.tv_sec =
		ss->start_time.tv_usec = 0L;
#endif

    }

    put_scoreboard_info(child_num, ss);
}

static void increment_counts(int child_num, request_rec *r)
{
    long int bs = 0;
    short_score *ss;

    ap_sync_scoreboard_image();
    ss = &ap_scoreboard_image->servers[child_num];

    if (r->sent_bodyct)
	ap_bgetopt(r->connection->client, BO_BYTECT, &bs);

#ifndef NO_TIMES
    times(&ss->times);
#endif
    ss->access_count++;
    ss->my_access_count++;
    ss->conn_count++;
    ss->bytes_served += (unsigned long) bs;
    ss->my_bytes_served += (unsigned long) bs;
    ss->conn_bytes += (unsigned long) bs;

    put_scoreboard_info(child_num, ss);
}

static int find_child_by_pid(int pid)
{
    int i;

    for (i = 0; i < max_daemons_limit; ++i)
	if (ap_scoreboard_image->parent[i].pid == pid)
	    return i;

    return -1;
}

static void reclaim_child_processes(int terminate)
{
#ifndef MULTITHREAD
    int i, status;
    long int waittime = 1024 * 16;	/* in usecs */
    struct timeval tv;
    int waitret, tries;
    int not_dead_yet;
    int ret;
#ifndef NO_OTHER_CHILD
    other_child_rec *ocr, *nocr;
#endif

    ap_sync_scoreboard_image();

    for (tries = terminate ? 4 : 1; tries <= 12; ++tries) {
	/* don't want to hold up progress any more than 
	 * necessary, but we need to allow children a few moments to exit.
	 * Set delay with an exponential backoff. NOTE: if we get
 	 * interupted, we'll wait longer than expected...
	 */
	tv.tv_sec = waittime / 1000000;
	tv.tv_usec = waittime % 1000000;
	waittime = waittime * 4;
	do {
	    ret = ap_select(0, NULL, NULL, NULL, &tv);
	} while (ret == -1 && errno == EINTR);

	/* now see who is done */
	not_dead_yet = 0;
	for (i = 0; i < max_daemons_limit; ++i) {
	    int pid = ap_scoreboard_image->parent[i].pid;

	    if (pid == my_pid || pid == 0)
		continue;

            if (!in_pid_table(pid)) {
                ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
                             "Bad pid (%d) in scoreboard slot %d", pid, i);
                continue;
            }
	    waitret = waitpid(pid, &status, WNOHANG);
	    if (waitret == pid || waitret == -1) {
		ap_scoreboard_image->parent[i].pid = 0;
                unset_pid_table(pid);
		continue;
	    }
	    ++not_dead_yet;
	    switch (tries) {
	    case 1:     /*  16ms */
	    case 2:     /*  82ms */
		break;
	    case 3:     /* 344ms */
		/* perhaps it missed the SIGHUP, lets try again */
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING,
			    server_conf,
		    "child process %d did not exit, sending another SIGHUP",
			    pid);
		kill(pid, SIGHUP);
		waittime = 1024 * 16;
		break;
	    case 4:     /*  16ms */
	    case 5:     /*  82ms */
	    case 6:     /* 344ms */
		break;
	    case 7:     /* 1.4sec */
		/* ok, now it's being annoying */
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING,
			    server_conf,
		   "child process %d still did not exit, sending a SIGTERM",
			    pid);
		kill(pid, SIGTERM);
		break;
	    case 8:     /*  6 sec */
		/* die child scum */
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
		   "child process %d still did not exit, sending a SIGKILL",
			    pid);
		kill(pid, SIGKILL);
		waittime = 1024 * 16; /* give them some time to die */
		break;
	    case 9:     /*   6 sec */
	    case 10:    /* 6.1 sec */
	    case 11:    /* 6.4 sec */
		break;
	    case 12:    /* 7.4 sec */
		/* gave it our best shot, but alas...  If this really 
		 * is a child we are trying to kill and it really hasn't
		 * exited, we will likely fail to bind to the port
		 * after the restart.
		 */
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
			    "could not make child process %d exit, "
			    "attempting to continue anyway", pid);
		break;
	    }
	}
#ifdef TPF
        AP_OS_RECLAIM_LOOP_ADJUSTMENTS
#endif
#ifndef NO_OTHER_CHILD
	for (ocr = other_children; ocr; ocr = nocr) {
	    nocr = ocr->next;
	    if (ocr->pid == -1)
		continue;

	    waitret = waitpid(ocr->pid, &status, WNOHANG);
	    if (waitret == ocr->pid) {
		ocr->pid = -1;
		(*ocr->maintenance) (OC_REASON_RESTART, ocr->data, (ap_wait_t)status);
	    }
	    else if (waitret == 0) {
		(*ocr->maintenance) (OC_REASON_RESTART, ocr->data, (ap_wait_t)-1);
		++not_dead_yet;
	    }
	    else if (waitret == -1) {
		/* uh what the heck? they didn't call unregister? */
		ocr->pid = -1;
		(*ocr->maintenance) (OC_REASON_LOST, ocr->data, (ap_wait_t)-1);
	    }
	}
#endif
	if (!not_dead_yet) {
	    /* nothing left to wait for */
	    break;
	}
    }
#endif /* ndef MULTITHREAD */
}


#if defined(NEED_WAITPID)
/*
   Systems without a real waitpid sometimes lose a child's exit while waiting
   for another.  Search through the scoreboard for missing children.
 */
int reap_children(ap_wait_t *status)
{
    int n, pid;

    for (n = 0; n < max_daemons_limit; ++n) {
        ap_sync_scoreboard_image();
        pid = ap_scoreboard_image->parent[n].pid;
        if (ap_scoreboard_image->servers[n].status != SERVER_DEAD) {
            if (in_pid_table(pid)) {
                if (kill(pid, 0) == -1) {
                    ap_update_child_status(n, SERVER_DEAD, NULL);
                    /* just mark it as having a successful exit status */
                    bzero((char *) status, sizeof(ap_wait_t));
                    unset_pid_table(pid);       /* to be safe */
                    return(pid);
                }
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
                            "Bad pid (%d) in scoreboard slot %d", pid, n);
            }
        }
    }
    return 0;
}
#endif

/* Finally, this routine is used by the caretaker process to wait for
 * a while...
 */

#ifndef NETWARE
/* number of calls to wait_or_timeout between writable probes */
#ifndef INTERVAL_OF_WRITABLE_PROBES
#define INTERVAL_OF_WRITABLE_PROBES 10
#endif
static int wait_or_timeout_counter;

static int wait_or_timeout(ap_wait_t *status)
{
#ifdef WIN32
#define MAXWAITOBJ MAXIMUM_WAIT_OBJECTS
    HANDLE h[MAXWAITOBJ];
    int e[MAXWAITOBJ];
    int round, pi, hi, rv, err, pid;
    for (round = 0; round <= (HARD_SERVER_LIMIT - 1) / MAXWAITOBJ + 1; round++) {
	hi = 0;
	for (pi = round * MAXWAITOBJ;
	     (pi < (round + 1) * MAXWAITOBJ) && (pi < HARD_SERVER_LIMIT);
	     pi++) {
	    if (ap_scoreboard_image->servers[pi].status != SERVER_DEAD) {
                e[hi] = pi;
                pid = ap_scoreboard_image->parent[pi].pid;
                if (in_pid_table(pid))
                    h[hi++] = (HANDLE) pid;
                else {
                    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
                                 "Bad pid (%d) in scoreboard slot %d", pid, pi);
                }
	    }

	}
	if (hi > 0) {
	    rv = WaitForMultipleObjects(hi, h, FALSE, 10000);
	    if (rv == -1)
		err = GetLastError();
	    if ((WAIT_OBJECT_0 <= (unsigned int) rv) && ((unsigned int) rv < (WAIT_OBJECT_0 + hi)))
		return (ap_scoreboard_image->parent[e[rv - WAIT_OBJECT_0]].pid);
	    else if ((WAIT_ABANDONED_0 <= (unsigned int) rv) && ((unsigned int) rv < (WAIT_ABANDONED_0 + hi)))
		return (ap_scoreboard_image->parent[e[rv - WAIT_ABANDONED_0]].pid);

	}
    }
    return (-1);
#else /* WIN32 */
    struct timeval tv;
    int ret;

    ++wait_or_timeout_counter;
    if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) {
	wait_or_timeout_counter = 0;
#ifndef NO_OTHER_CHILD
	probe_writable_fds();
#endif
    }
    ret = waitpid(-1, status, WNOHANG);
    if (ret == -1 && errno == EINTR) {
	return -1;
    }
    if (ret > 0) {
	return ret;
    }
#ifdef NEED_WAITPID
    if ((ret = reap_children(status)) > 0) {
	return ret;
    }
#endif
    tv.tv_sec = SCOREBOARD_MAINTENANCE_INTERVAL / 1000000;
    tv.tv_usec = SCOREBOARD_MAINTENANCE_INTERVAL % 1000000;
    ap_select(0, NULL, NULL, NULL, &tv);
    return -1;
#endif /* WIN32 */
}
#endif

#if defined(NSIG)
#define NumSIG NSIG
#elif defined(_NSIG)
#define NumSIG _NSIG
#elif defined(__NSIG)
#define NumSIG __NSIG
#else
#define NumSIG 32   /* for 1998's unixes, this is still a good assumption */
#endif

#ifdef SYS_SIGLIST /* platform has sys_siglist[] */
#define INIT_SIGLIST()  /*nothing*/
#else /* platform has no sys_siglist[], define our own */
#define SYS_SIGLIST ap_sys_siglist
#define INIT_SIGLIST() siglist_init();

const char *ap_sys_siglist[NumSIG];

static void siglist_init(void)
{
    int sig;

    ap_sys_siglist[0] = "Signal 0";
#ifdef SIGHUP
    ap_sys_siglist[SIGHUP] = "Hangup";
#endif
#ifdef SIGINT
    ap_sys_siglist[SIGINT] = "Interrupt";
#endif
#ifdef SIGQUIT
    ap_sys_siglist[SIGQUIT] = "Quit";
#endif
#ifdef SIGILL
    ap_sys_siglist[SIGILL] = "Illegal instruction";
#endif
#ifdef SIGTRAP
    ap_sys_siglist[SIGTRAP] = "Trace/BPT trap";
#endif
#ifdef SIGIOT
    ap_sys_siglist[SIGIOT] = "IOT instruction";
#endif
#ifdef SIGABRT
    ap_sys_siglist[SIGABRT] = "Abort";
#endif
#ifdef SIGEMT
    ap_sys_siglist[SIGEMT] = "Emulator trap";
#endif
#ifdef SIGFPE
    ap_sys_siglist[SIGFPE] = "Arithmetic exception";
#endif
#ifdef SIGKILL
    ap_sys_siglist[SIGKILL] = "Killed";
#endif
#ifdef SIGBUS
    ap_sys_siglist[SIGBUS] = "Bus error";
#endif
#ifdef SIGSEGV
    ap_sys_siglist[SIGSEGV] = "Segmentation fault";
#endif
#ifdef SIGSYS
    ap_sys_siglist[SIGSYS] = "Bad system call";
#endif
#ifdef SIGPIPE
    ap_sys_siglist[SIGPIPE] = "Broken pipe";
#endif
#ifdef SIGALRM
    ap_sys_siglist[SIGALRM] = "Alarm clock";
#endif
#ifdef SIGTERM
    ap_sys_siglist[SIGTERM] = "Terminated";
#endif
#ifdef SIGUSR1
    ap_sys_siglist[SIGUSR1] = "User defined signal 1";
#endif
#ifdef SIGUSR2
    ap_sys_siglist[SIGUSR2] = "User defined signal 2";
#endif
#ifdef SIGCLD
    ap_sys_siglist[SIGCLD] = "Child status change";
#endif
#ifdef SIGCHLD
    ap_sys_siglist[SIGCHLD] = "Child status change";
#endif
#ifdef SIGPWR
    ap_sys_siglist[SIGPWR] = "Power-fail restart";
#endif
#ifdef SIGWINCH
    ap_sys_siglist[SIGWINCH] = "Window changed";
#endif
#ifdef SIGURG
    ap_sys_siglist[SIGURG] = "urgent socket condition";
#endif
#ifdef SIGPOLL
    ap_sys_siglist[SIGPOLL] = "Pollable event occurred";
#endif
#ifdef SIGIO
    ap_sys_siglist[SIGIO] = "socket I/O possible";
#endif
#ifdef SIGSTOP
    ap_sys_siglist[SIGSTOP] = "Stopped (signal)";
#endif
#ifdef SIGTSTP
    ap_sys_siglist[SIGTSTP] = "Stopped";
#endif
#ifdef SIGCONT
    ap_sys_siglist[SIGCONT] = "Continued";
#endif
#ifdef SIGTTIN
    ap_sys_siglist[SIGTTIN] = "Stopped (tty input)";
#endif
#ifdef SIGTTOU
    ap_sys_siglist[SIGTTOU] = "Stopped (tty output)";
#endif
#ifdef SIGVTALRM
    ap_sys_siglist[SIGVTALRM] = "virtual timer expired";
#endif
#ifdef SIGPROF
    ap_sys_siglist[SIGPROF] = "profiling timer expired";
#endif
#ifdef SIGXCPU
    ap_sys_siglist[SIGXCPU] = "exceeded cpu limit";
#endif
#ifdef SIGXFSZ
    ap_sys_siglist[SIGXFSZ] = "exceeded file size limit";
#endif
    for (sig=0; sig < sizeof(ap_sys_siglist)/sizeof(ap_sys_siglist[0]); ++sig)
        if (ap_sys_siglist[sig] == NULL)
            ap_sys_siglist[sig] = "";
}
#endif /* platform has sys_siglist[] */

#ifdef AP_ENABLE_EXCEPTION_HOOK
typedef struct except_hook_t {
    struct except_hook_t *next;
    void (*fn)(ap_exception_info_t *);
} except_hook_t;

static except_hook_t *except_hooks;

static void except_hook_cleanup(void *ignored)
{
    except_hooks = NULL;
}

API_EXPORT(int) ap_add_fatal_exception_hook(void (*fn)(ap_exception_info_t *))
{
    except_hook_t *new;

    ap_assert(pconf);

    if (!ap_exception_hook_enabled) {
        return 1;
    }

    new = ap_palloc(pconf, sizeof(except_hook_t));
    new->next = except_hooks;
    new->fn = fn;
    except_hooks = new;

    return 0;
}

static void run_fatal_exception_hook(int sig)
{
    except_hook_t *cur_hook = except_hooks;
    ap_exception_info_t ei = {0};

    if (ap_exception_hook_enabled &&
        geteuid() != 0) {
        ei.sig = sig;
        ei.pid = getpid();

        while (cur_hook) {
            cur_hook->fn(&ei);
            cur_hook = cur_hook->next;
        }
    }
}
#endif /* AP_ENABLE_EXCEPTION_HOOK */

/* handle all varieties of core dumping signals */
static void sig_coredump(int sig)
{
    chdir(ap_coredump_dir);
    signal(sig, SIG_DFL);
#ifdef AP_ENABLE_EXCEPTION_HOOK
    run_fatal_exception_hook(sig);
#endif
#if !defined(WIN32) && !defined(NETWARE)
    kill(getpid(), sig);
#else
    raise(sig);
#endif
    /* At this point we've got sig blocked, because we're still inside
     * the signal handler.  When we leave the signal handler it will
     * be unblocked, and we'll take the signal... and coredump or whatever
     * is appropriate for this particular Unix.  In addition the parent
     * will see the real signal we received -- whereas if we called
     * abort() here, the parent would only see SIGABRT.
     */
}

/*****************************************************************
 * Connection structures and accounting...
 */

static void just_die(int sig)
{				/* SIGHUP to child process??? */
    /* if alarms are blocked we have to wait to die otherwise we might
     * end up with corruption in alloc.c's internal structures */
#ifdef NETWARE
    get_tsd
#endif
    if (alarms_blocked) {
	exit_after_unblock = 1;
    }
    else {
	clean_child_exit(0);
    }
}

static int volatile usr1_just_die = 1;
static int volatile deferred_die;

static void usr1_handler(int sig)
{
    if (usr1_just_die) {
	just_die(sig);
    }
    deferred_die = 1;
}

/* volatile just in case */
static int volatile shutdown_pending;
static int volatile restart_pending;
static int volatile is_graceful;
API_VAR_EXPORT ap_generation_t volatile ap_my_generation=0;

#ifdef WIN32
/*
 * Signalling Apache on NT.
 *
 * Under Unix, Apache can be told to shutdown or restart by sending various
 * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so
 * we use "events" instead. The parent apache process goes into a loop
 * where it waits forever for a set of events. Two of those events are
 * called
 *
 *    apPID_shutdown
 *    apPID_restart
 *
 * (where PID is the PID of the apache parent process). When one of these
 * is signalled, the Apache parent performs the appropriate action. The events
 * can become signalled through internal Apache methods (e.g. if the child
 * finds a fatal error and needs to kill its parent), via the service
 * control manager (the control thread will signal the shutdown event when
 * requested to stop the Apache service), from the -k Apache command line,
 * or from any external program which finds the Apache PID from the
 * httpd.pid file.
 *
 * The signal_parent() function, below, is used to signal one of these events.
 * It can be called by any child or parent process, since it does not
 * rely on global variables.
 *
 * On entry, type gives the event to signal. 0 means shutdown, 1 means 
 * graceful restart.
 */

static void signal_parent(int type)
{
    HANDLE e;
    char *signal_name;
    extern char signal_shutdown_name[];
    extern char signal_restart_name[];

    /* after updating the shutdown_pending or restart flags, we need
     * to wake up the parent process so it can see the changes. The
     * parent will normally be waiting for either a child process
     * to die, or for a signal on the "spache-signal" event. So set the
     * "apache-signal" event here.
     */

    /* XXX: This is no good, can't we please die in -X mode :-? */
    if (one_process) {
	return;
    }

    switch(type) {
    case 0: signal_name = signal_shutdown_name; break;
    case 1: signal_name = signal_restart_name; break;
    default: return;
    }

    APD2("signal_parent signalling event \"%s\"", signal_name);

    e = OpenEvent(EVENT_ALL_ACCESS, FALSE, signal_name);
    if (!e) {
	/* Um, problem, can't signal the parent, which means we can't
	 * signal ourselves to die. Ignore for now...
	 */
	ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf,
	    "OpenEvent on %s event", signal_name);
	return;
    }
    if (SetEvent(e) == 0) {
	/* Same problem as above */
	ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf,
	    "SetEvent on %s event", signal_name);
	CloseHandle(e);
	return;
    }
    CloseHandle(e);
}
#endif

/*
 * ap_start_shutdown() and ap_start_restart(), below, are a first stab at
 * functions to initiate shutdown or restart without relying on signals. 
 * Previously this was initiated in sig_term() and restart() signal handlers, 
 * but we want to be able to start a shutdown/restart from other sources --
 * e.g. on Win32, from the service manager. Now the service manager can
 * call ap_start_shutdown() or ap_start_restart() as appropiate.  Note that
 * these functions can also be called by the child processes, since global
 * variables are no longer used to pass on the required action to the parent.
 */

API_EXPORT(void) ap_start_shutdown(void)
{
#ifndef WIN32
    if (shutdown_pending == 1) {
	/* Um, is this _probably_ not an error, if the user has
	 * tried to do a shutdown twice quickly, so we won't
	 * worry about reporting it.
	 */
	return;
    }
    shutdown_pending = 1;
#else
    signal_parent(0);	    /* get the parent process to wake up */
#endif
}

/* do a graceful restart if graceful == 1 */
API_EXPORT(void) ap_start_restart(int graceful)
{
#ifndef WIN32
    if (restart_pending == 1) {
	/* Probably not an error - don't bother reporting it */
	return;
    }
    restart_pending = 1;
    is_graceful = graceful;
#else
    signal_parent(1);	    /* get the parent process to wake up */
#endif /* WIN32 */
}

static void sig_term(int sig)
{
    ap_start_shutdown();
}

static void restart(int sig)
{
#ifdef TPF
    signal(sig, restart);
#endif
#if !defined (WIN32) && !defined(NETWARE)
    ap_start_restart(sig == SIGUSR1);
#else
    ap_start_restart(1);
#endif
}

static void set_signals(void)
{
#ifndef NO_USE_SIGACTION
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (!one_process) {
	sa.sa_handler = sig_coredump;
#if defined(SA_ONESHOT)
	sa.sa_flags = SA_ONESHOT;
#elif defined(SA_RESETHAND)
	sa.sa_flags = SA_RESETHAND;
#endif
	if (sigaction(SIGSEGV, &sa, NULL) < 0)
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGSEGV)");
#ifdef SIGBUS
	if (sigaction(SIGBUS, &sa, NULL) < 0)
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGBUS)");
#endif
#ifdef SIGABORT
	if (sigaction(SIGABORT, &sa, NULL) < 0)
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGABORT)");
#endif
#ifdef SIGABRT
	if (sigaction(SIGABRT, &sa, NULL) < 0)
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGABRT)");
#endif
#ifdef SIGILL
	if (sigaction(SIGILL, &sa, NULL) < 0)
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGILL)");
#endif
	sa.sa_flags = 0;
    }
    sa.sa_handler = sig_term;
    if (sigaction(SIGTERM, &sa, NULL) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGTERM)");
#ifdef SIGINT
    if (sigaction(SIGINT, &sa, NULL) < 0)
        ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGINT)");
#endif
#ifdef SIGXCPU
    sa.sa_handler = SIG_DFL;
    if (sigaction(SIGXCPU, &sa, NULL) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGXCPU)");
#endif
#ifdef SIGXFSZ
    sa.sa_handler = SIG_DFL;
    if (sigaction(SIGXFSZ, &sa, NULL) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGXFSZ)");
#endif
#ifdef SIGPIPE
    sa.sa_handler = SIG_IGN;
    if (sigaction(SIGPIPE, &sa, NULL) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGPIPE)");
#endif

    /* we want to ignore HUPs and USR1 while we're busy processing one */
    sigaddset(&sa.sa_mask, SIGHUP);
    sigaddset(&sa.sa_mask, SIGUSR1);
    sa.sa_handler = restart;
    if (sigaction(SIGHUP, &sa, NULL) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGHUP)");
    if (sigaction(SIGUSR1, &sa, NULL) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "sigaction(SIGUSR1)");
#else
    if (!one_process) {
	signal(SIGSEGV, sig_coredump);
#ifdef SIGBUS
	signal(SIGBUS, sig_coredump);
#endif /* SIGBUS */
#ifdef SIGABORT
	signal(SIGABORT, sig_coredump);
#endif /* SIGABORT */
#ifdef SIGABRT
	signal(SIGABRT, sig_coredump);
#endif /* SIGABRT */
#ifdef SIGILL
	signal(SIGILL, sig_coredump);
#endif /* SIGILL */
#ifdef SIGXCPU
	signal(SIGXCPU, SIG_DFL);
#endif /* SIGXCPU */
#ifdef SIGXFSZ
	signal(SIGXFSZ, SIG_DFL);
#endif /* SIGXFSZ */
    }
#ifndef NETWARE
    signal(SIGTERM, sig_term);
#endif
#ifdef SIGHUP
    signal(SIGHUP, restart);
#endif /* SIGHUP */
#ifdef SIGUSR1
    signal(SIGUSR1, restart);
#endif /* SIGUSR1 */
#ifdef SIGPIPE
    signal(SIGPIPE, SIG_IGN);
#endif /* SIGPIPE */

#endif
}


/*****************************************************************
 * Here follows a long bunch of generic server bookkeeping stuff...
 */

static void detach(void)
{
#if !defined(WIN32) && !defined(NETWARE)
    int x;

    chdir("/");
#if !defined(MPE) && !defined(OS2) && !defined(TPF) && !defined(BEOS) && \
    !defined(BONE)
/* Don't detach for MPE because child processes can't survive the death of
   the parent. */
    if (do_detach) {
        if ((x = fork()) > 0)
            exit(0);
        else if (x == -1) {
            perror("fork");
	    fprintf(stderr, "%s: unable to fork new process\n", ap_server_argv0);
	    exit(1);
        }
        RAISE_SIGSTOP(DETACH);
    }
#endif
#ifndef NO_SETSID
    if ((pgrp = setsid()) == -1) {
	perror("setsid");
	fprintf(stderr, "%s: setsid failed\n", ap_server_argv0);
	if (!do_detach) 
	    fprintf(stderr, "setsid() failed probably because you aren't "
		"running under a process management tool like daemontools\n");
	exit(1);
    }
#elif defined(NEXT) || defined(NEWSOS)
    if (setpgrp(0, getpid()) == -1 || (pgrp = getpgrp(0)) == -1) {
	perror("setpgrp");
	fprintf(stderr, "%s: setpgrp or getpgrp failed\n", ap_server_argv0);
	exit(1);
    }
#elif defined(OS2) || defined(TPF)
    /* OS/2 and TPF don't support process group IDs */
    pgrp = getpid();
#elif defined(MPE)
    /* MPE uses negative pid for process group */
    pgrp = -getpid();
#elif defined(CYGWIN)
    /* Cygwin does not take any argument for setpgrp() */
    if ((pgrp = setpgrp()) == -1) {
        perror("setpgrp");
        fprintf(stderr, "%s: setpgrp failed\n", ap_server_argv0);
        exit(1);
    }
#else
    if ((pgrp = setpgrp(getpid(), 0)) == -1) {
	perror("setpgrp");
	fprintf(stderr, "%s: setpgrp failed\n", ap_server_argv0);
	exit(1);
    }
#endif

    /* close out the standard file descriptors */
    if (freopen("/dev/null", "r", stdin) == NULL) {
	fprintf(stderr, "%s: unable to replace stdin with /dev/null: %s\n",
		ap_server_argv0, strerror(errno));
	/* continue anyhow -- note we can't close out descriptor 0 because we
	 * have nothing to replace it with, and if we didn't have a descriptor
	 * 0 the next file would be created with that value ... leading to
	 * havoc.
	 */
    }
    if (freopen("/dev/null", "w", stdout) == NULL) {
	fprintf(stderr, "%s: unable to replace stdout with /dev/null: %s\n",
		ap_server_argv0, strerror(errno));
    }
    /* stderr is a tricky one, we really want it to be the error_log,
     * but we haven't opened that yet.  So leave it alone for now and it'll
     * be reopened moments later.
     */
#endif /* ndef WIN32 */
}

/* Set group privileges.
 *
 * Note that we use the username as set in the config files, rather than
 * the lookup of to uid --- the same uid may have multiple passwd entries,
 * with different sets of groups for each.
 */

static void set_group_privs(void)
{
#if !defined(WIN32) && !defined(NETWARE) && !defined(BEOS) && !defined(BONE)
    if (!geteuid()) {
	char *name;

	/* Get username if passed as a uid */

	if (ap_user_name[0] == '#') {
	    struct passwd *ent;
	    uid_t uid = atoi(&ap_user_name[1]);

	    if ((ent = getpwuid(uid)) == NULL) {
		ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
			 "getpwuid: couldn't determine user name from uid %u, "
			 "you probably need to modify the User directive",
			 (unsigned)uid);
		clean_child_exit(APEXIT_CHILDFATAL);
	    }

	    name = ent->pw_name;
	}
	else
	    name = ap_user_name;

#if !defined(OS2) && !defined(TPF)
	/* OS/2 and TPF don't support groups. */

	/*
	 * Set the GID before initgroups(), since on some platforms
	 * setgid() is known to zap the group list.
	 */
#ifdef MPE
	GETPRIVMODE();
#endif
	if (setgid(ap_group_id) == -1) {
#ifdef MPE
	    GETUSERMODE();
#endif
	    ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
			"setgid: unable to set group id to Group %u",
			(unsigned)ap_group_id);
	    clean_child_exit(APEXIT_CHILDFATAL);
	}
#ifdef MPE
	GETUSERMODE();
#endif

	/* Reset `groups' attributes. */

	if (initgroups(name, ap_group_id) == -1) {
	    ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
			"initgroups: unable to set groups for User %s "
			"and Group %u", name, (unsigned)ap_group_id);
	    clean_child_exit(APEXIT_CHILDFATAL);
	}
#ifdef MULTIPLE_GROUPS
	if (getgroups(NGROUPS_MAX, group_id_list) == -1) {
	    ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
			"getgroups: unable to get group list");
	    clean_child_exit(APEXIT_CHILDFATAL);
	}
#endif /* MULTIPLE_GROUPS */
#endif /* !defined(OS2) && !defined(TPF) */
    }
#endif /* !defined(WIN32) && !defined(NETWARE) && !defined(BEOS) */
}

/* check to see if we have the 'suexec' setuid wrapper installed */
static int init_suexec(void)
{
    int result = 0;

#if !defined(WIN32) && !defined(NETWARE) && !defined(TPF)
    struct stat wrapper;

    if ((stat(SUEXEC_BIN, &wrapper)) != 0) {
	result = 0;
    }
    else if ((wrapper.st_mode & S_ISUID) && (wrapper.st_uid == 0)) {
	result = 1;
    }
#endif /* ndef WIN32 */
    return result;
}

/*****************************************************************
 * Connection structures and accounting...
 */


static conn_rec *new_connection(pool *p, server_rec *server, BUFF *inout,
			     const struct sockaddr_in *remaddr,
			     const struct sockaddr_in *saddr,
			     int child_num)
{
    conn_rec *conn = (conn_rec *) ap_pcalloc(p, sizeof(conn_rec));

    /* Got a connection structure, so initialize what fields we can
     * (the rest are zeroed out by pcalloc).
     */

    conn->child_num = child_num;

    conn->pool = p;
    conn->local_addr = *saddr;
    conn->local_ip = ap_pstrdup(conn->pool,
				inet_ntoa(conn->local_addr.sin_addr));
    conn->server = server; /* just a guess for now */
    ap_update_vhost_given_ip(conn);
    conn->base_server = conn->server;
    conn->client = inout;

    conn->remote_addr = *remaddr;
    conn->remote_ip = ap_pstrdup(conn->pool,
			      inet_ntoa(conn->remote_addr.sin_addr));

    return conn;
}

#if defined(TCP_NODELAY) && !defined(MPE) && !defined(TPF)
static void sock_disable_nagle(int s, struct sockaddr_in *sin_client)
{
    /* The Nagle algorithm says that we should delay sending partial
     * packets in hopes of getting more data.  We don't want to do
     * this; we are not telnet.  There are bad interactions between
     * persistent connections and Nagle's algorithm that have very severe
     * performance penalties.  (Failing to disable Nagle is not much of a
     * problem with simple HTTP.)
     *
     * In spite of these problems, failure here is not a shooting offense.
     */
    int just_say_no = 1;

    if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *) &just_say_no,
		   sizeof(int)) < 0) {
#ifdef NETWARE
        errno = WSAGetLastError();
#endif
        if (sin_client) {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf,
                         "setsockopt: (TCP_NODELAY), client %pA probably "
                         "dropped the connection", &sin_client->sin_addr);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf,
                         "setsockopt: (TCP_NODELAY)");
        }
    }
}

#else
#define sock_disable_nagle(s, c)	/* NOOP */
#endif

static int make_sock(pool *p, const struct sockaddr_in *server)
{
    int s;
    int one = 1;
    char addr[512];

    if (server->sin_addr.s_addr != htonl(INADDR_ANY))
	ap_snprintf(addr, sizeof(addr), "address %s port %d",
		inet_ntoa(server->sin_addr), ntohs(server->sin_port));
    else
	ap_snprintf(addr, sizeof(addr), "port %d", ntohs(server->sin_port));

    /* note that because we're about to slack we don't use psocket */
    ap_block_alarms();
    if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
	    ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
		    "make_sock: failed to get a socket for %s", addr);

	    ap_unblock_alarms();
	    exit(1);
    }

    /* Solaris (probably versions 2.4, 2.5, and 2.5.1 with various levels
     * of tcp patches) has some really weird bugs where if you dup the
     * socket now it breaks things across SIGHUP restarts.  It'll either
     * be unable to bind, or it won't respond.
     */
#if defined (SOLARIS2) && SOLARIS2 < 260
#define WORKAROUND_SOLARIS_BUG
#endif

    /* PR#1282 Unixware 1.x appears to have the same problem as solaris */
#if defined (UW) && UW < 200
#define WORKAROUND_SOLARIS_BUG
#endif

    /* PR#1973 NCR SVR4 systems appear to have the same problem */
#if defined (MPRAS)
#define WORKAROUND_SOLARIS_BUG
#endif

#ifndef WORKAROUND_SOLARIS_BUG
#ifndef BEOS /* this won't work for BeOS sockets!! */
    s = ap_slack(s, AP_SLACK_HIGH);
#endif

    ap_note_cleanups_for_socket_ex(p, s, 1);	/* arrange to close on exec or restart */
#ifdef TPF
    os_note_additional_cleanups(p, s);
#endif /* TPF */
#endif

    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)) < 0) {
#ifndef _OSD_POSIX
	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
		    "make_sock: for %s, setsockopt: (SO_REUSEADDR)", addr);
	closesocket(s);
	ap_unblock_alarms();
	exit(1);
#endif /*_OSD_POSIX*/
    }
    one = 1;
#if defined(SO_KEEPALIVE) && !defined(MPE)
    if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(int)) < 0) {
	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
		    "make_sock: for %s, setsockopt: (SO_KEEPALIVE)", addr);
	closesocket(s);

	ap_unblock_alarms();
	exit(1);
    }
#endif

    sock_disable_nagle(s, NULL);
    sock_enable_linger(s);

    /*
     * To send data over high bandwidth-delay connections at full
     * speed we must force the TCP window to open wide enough to keep the
     * pipe full.  The default window size on many systems
     * is only 4kB.  Cross-country WAN connections of 100ms
     * at 1Mb/s are not impossible for well connected sites.
     * If we assume 100ms cross-country latency,
     * a 4kB buffer limits throughput to 40kB/s.
     *
     * To avoid this problem I've added the SendBufferSize directive
     * to allow the web master to configure send buffer size.
     *
     * The trade-off of larger buffers is that more kernel memory
     * is consumed.  YMMV, know your customers and your network!
     *
     * -John Heidemann <johnh@isi.edu> 25-Oct-96
     *
     * If no size is specified, use the kernel default.
     */
#ifndef BEOS			/* BeOS does not support SO_SNDBUF */
    if (server_conf->send_buffer_size) {
	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
		(char *) &server_conf->send_buffer_size, sizeof(int)) < 0) {
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf,
			"make_sock: failed to set SendBufferSize for %s, "
			"using default", addr);
	    /* not a fatal error */
	}
    }
#endif

#ifdef MPE
/* MPE requires CAP=PM and GETPRIVMODE to bind to ports less than 1024 */
    if (ntohs(server->sin_port) < 1024)
	GETPRIVMODE();
#endif

    if (bind(s, (struct sockaddr *) server, sizeof(struct sockaddr_in)) == -1) {
	ap_log_error(APLOG_MARK, APLOG_CRIT, server_conf,
	    "make_sock: could not bind to %s", addr);
#ifdef MPE
	if (ntohs(server->sin_port) < 1024)
	    GETUSERMODE();
#endif

	closesocket(s);
	ap_unblock_alarms();
	exit(1);
    }
#ifdef MPE
    if (ntohs(server->sin_port) < 1024)
	GETUSERMODE();
#endif

    if (listen(s, ap_listenbacklog) == -1) {
	ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
	    "make_sock: unable to listen for connections on %s", addr);
	closesocket(s);
	ap_unblock_alarms();
	exit(1);
    }

#ifdef SO_ACCEPTFILTER
    if (ap_acceptfilter) {
#ifndef ACCEPT_FILTER_NAME
#define ACCEPT_FILTER_NAME "httpready"
#ifdef __FreeBSD_version
#if __FreeBSD_version < 411000 /* httpready broken before 4.1.1 */
#undef ACCEPT_FILTER_NAME
#define ACCEPT_FILTER_NAME "dataready"
#endif
#endif
#endif /* ! ACCEPT_FILTER_NAME */
	/*
	 * See htdocs/manual/misc/perf-bsd44.html for a discussion of
	 * how to enable this feature and various issues with it.
	 */
	struct accept_filter_arg af = {
	    ACCEPT_FILTER_NAME, ""
	};
	if (setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)) < 0) {
            if (errno == ENOPROTOOPT) {
	    	ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, server_conf,
			 "socket option SO_ACCEPTFILTER unkown on this machine. Continuing.");
	     } else {
	    	ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_INFO, server_conf,
			 "make_sock: for %s, setsockopt: (SO_ACCEPTFILTER)", addr);
	     }
	}
    }
#endif

#ifdef WORKAROUND_SOLARIS_BUG
    s = ap_slack(s, AP_SLACK_HIGH);

    ap_note_cleanups_for_socket_ex(p, s, 1);	/* arrange to close on exec or restart */
#endif
    ap_unblock_alarms();

#ifdef CHECK_FD_SETSIZE
    /* protect various fd_sets */
    if (s >= FD_SETSIZE) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
	    "make_sock: problem listening on %s, filedescriptor (%u) "
	    "larger than FD_SETSIZE (%u) "
	    "found, you probably need to rebuild Apache with a "
	    "larger FD_SETSIZE", addr, s, FD_SETSIZE);
	closesocket(s);
	exit(1);
    }
#endif

    return s;
}


/*
 * During a restart we keep track of the old listeners here, so that we
 * can re-use the sockets.  We have to do this because we won't be able
 * to re-open the sockets ("Address already in use").
 *
 * Unlike the listeners ring, old_listeners is a NULL terminated list.
 *
 * copy_listeners() makes the copy, find_listener() finds an old listener
 * and close_unused_listener() cleans up whatever wasn't used.
 */
static listen_rec *old_listeners;

/* unfortunately copy_listeners may be called before listeners is a ring */
static void copy_listeners(pool *p)
{
    listen_rec *lr;

    ap_assert(old_listeners == NULL);
    if (ap_listeners == NULL) {
	return;
    }
    lr = ap_listeners;
    do {
	listen_rec *nr = malloc(sizeof *nr);

        if (nr == NULL) {
            fprintf(stderr, "Ouch!  malloc failed in copy_listeners()\n");
            exit(1);
        }
	*nr = *lr;
	ap_kill_cleanups_for_socket(p, nr->fd);
	nr->next = old_listeners;
	ap_assert(!nr->used);
	old_listeners = nr;
	lr = lr->next;
    } while (lr && lr != ap_listeners);
}


static int find_listener(listen_rec *lr)
{
    listen_rec *or;

    for (or = old_listeners; or; or = or->next) {
	if (!memcmp(&or->local_addr, &lr->local_addr, sizeof(or->local_addr))) {
	    or->used = 1;
	    return or->fd;
	}
    }
    return -1;
}


static void close_unused_listeners(void)
{
    listen_rec *or, *next;

    for (or = old_listeners; or; or = next) {
	next = or->next;
	if (!or->used)
	    closesocket(or->fd);
	free(or);
    }
    old_listeners = NULL;
}

#ifdef NONBLOCK_WHEN_MULTI_LISTEN
/* retrieved from APR */
static int soblock(int sd)
{
#ifdef NETWARE
    u_long one = 0;

    if (ioctlsocket(sd, FIONBIO, &one) == SOCKET_ERROR) {
        return -1;
    }
#else
#ifndef BEOS
    int fd_flags;
    
    fd_flags = fcntl(sd, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags &= ~O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags &= ~O_NDELAY;
#elif defined(FNDELAY)
    fd_flags &= ~FNDELAY;
#else
#error Teach soblock() how to make a socket blocking on your platform.
#endif
    if (fcntl(sd, F_SETFL, fd_flags) == -1) {
        return errno;
    }
#else
    int on = 0;
    if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0)
        return errno;
#endif /* BEOS */
#endif /* NETWARE */
    return 0;
}

static int sononblock(int sd)
{
#ifdef NETWARE
    u_long one = 1;

    if (ioctlsocket(sd, FIONBIO, &one) == SOCKET_ERROR) {
        return -1;
    }
#else
#ifndef BEOS
    int fd_flags;
    
    fd_flags = fcntl(sd, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags |= O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags |= O_NDELAY;
#elif defined(FNDELAY)
    fd_flags |= FNDELAY;
#else
#error Teach sononblock() how to make a socket non-blocking on your platform.
#endif
    if (fcntl(sd, F_SETFL, fd_flags) == -1) {
        return errno;
    }
#else
    int on = 1;
    if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0)
        return errno;
#endif /* BEOS */
#endif /* NETWARE */
    return 0;
}
#endif /* NONBLOCK_WHEN_MULTI_LISTEN */

/* open sockets, and turn the listeners list into a singly linked ring */
static void setup_listeners(pool *p)
{
    listen_rec *lr;
    int fd;

    listenmaxfd = -1;
    FD_ZERO(&listenfds);
    lr = ap_listeners;
    for (;;) {
	fd = find_listener(lr);
	if (fd < 0) {
	    fd = make_sock(p, &lr->local_addr);
	}
	else {
	    ap_note_cleanups_for_socket_ex(p, fd, 1);
	}
	/* if we get here, (fd >= 0) && (fd < FD_SETSIZE) */
	FD_SET(fd, &listenfds);
	if (fd > listenmaxfd)
	    listenmaxfd = fd;
	lr->fd = fd;
	if (lr->next == NULL)
	    break;
	lr = lr->next;
    }
    /* turn the list into a ring */
    lr->next = ap_listeners;
    head_listener = ap_listeners;
    close_unused_listeners();

#ifdef NONBLOCK_WHEN_MULTI_LISTEN
    if (ap_listeners->next != ap_listeners) {
        lr = ap_listeners;
        do {
            if (sononblock(lr->fd) < 0) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, NULL,
                             "A listening socket could not be made non-blocking.");
                exit(APEXIT_INIT);
            }
            lr = lr->next;
        } while (lr != ap_listeners);
    }
    else {
        /* we could be restarting with a single remaining listening
         * socket, still in non-blocking state from a previous
         * generation which had more listening sockets
         */
        if (soblock(ap_listeners->fd) < 0) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, NULL,
                         "A listening socket could not be made blocking.");
            exit(APEXIT_INIT);
        }
    }
#endif /* NONBLOCK_WHEN_MULTI_LISTEN */
    
#ifdef NO_SERIALIZED_ACCEPT
    /* warn them about the starvation problem if they're using multiple
     * sockets
     */
    if (ap_listeners->next != ap_listeners) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_CRIT, NULL,
		    "You cannot use multiple Listens safely on your system, "
		    "proceeding anyway.  See src/PORTING, search for "
		    "SERIALIZED_ACCEPT.");
    }
#endif
}


/*
 * Find a listener which is ready for accept().  This advances the
 * head_listener global.
 */
static ap_inline listen_rec *find_ready_listener(fd_set * main_fds)
{
    listen_rec *lr;

    lr = head_listener;
    do {
	if (FD_ISSET(lr->fd, main_fds)) {
	    head_listener = lr->next;
	    return (lr);
	}
	lr = lr->next;
    } while (lr != head_listener);
    return NULL;
}


#if defined(WIN32) || defined(NETWARE)
static int s_iInitCount = 0;

static int AMCSocketInitialize(void)
{
    int iVersionRequested;
    WSADATA wsaData;
    int err;

    if (s_iInitCount > 0) {
	s_iInitCount++;
	return (0);
    }
    else if (s_iInitCount < 0)
	return (s_iInitCount);

    /* s_iInitCount == 0. Do the initailization */
    iVersionRequested = MAKEWORD(2, 0);
    err = WSAStartup((WORD) iVersionRequested, &wsaData);
    if (err) {
        printf("WSAStartup failed with error %d\n", err);
	s_iInitCount = -1;
	return (s_iInitCount);
    }

    if (LOBYTE(wsaData.wVersion) != 2 ||
	HIBYTE(wsaData.wVersion) != 0) {
        printf("Apache requires Winsock 2. Please see the Apache FAQ for more information.\n");
	s_iInitCount = -2;
	WSACleanup();
	return (s_iInitCount);
    }
    s_iInitCount++;
    return (s_iInitCount);
}


static void AMCSocketCleanup(void)
{
    if (--s_iInitCount == 0)
	WSACleanup();
    return;
}
#endif

static void show_compile_settings(void)
{
    printf("Server version: %s\n", ap_get_server_version());
    printf("Server built:   %s\n", ap_get_server_built());
    printf("Server's Module Magic Number: %u:%u\n",
	   MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
    printf("Server compiled with....\n");
#ifdef TPF
    show_os_specific_compile_settings();
#endif
#ifdef BIG_SECURITY_HOLE
    printf(" -D BIG_SECURITY_HOLE\n");
#endif
#ifdef SECURITY_HOLE_PASS_AUTHORIZATION
    printf(" -D SECURITY_HOLE_PASS_AUTHORIZATION\n");
#endif
#ifdef HAVE_MMAP
    printf(" -D HAVE_MMAP\n");
#endif
#ifdef HAVE_SHMGET
    printf(" -D HAVE_SHMGET\n");
#endif
#ifdef USE_MMAP_SCOREBOARD
    printf(" -D USE_MMAP_SCOREBOARD\n");
#endif
#ifdef USE_SHMGET_SCOREBOARD
    printf(" -D USE_SHMGET_SCOREBOARD\n");
#endif
#ifdef USE_OS2_SCOREBOARD
    printf(" -D USE_OS2_SCOREBOARD\n");
#endif
#ifdef USE_POSIX_SCOREBOARD
    printf(" -D USE_POSIX_SCOREBOARD\n");
#endif
#ifdef USE_MMAP_FILES
    printf(" -D USE_MMAP_FILES\n");
#ifdef MMAP_SEGMENT_SIZE
	printf(" -D MMAP_SEGMENT_SIZE=%ld\n",(long)MMAP_SEGMENT_SIZE);
#endif
#endif /*USE_MMAP_FILES*/
#ifdef NO_WRITEV
    printf(" -D NO_WRITEV\n");
#endif
#ifdef NO_LINGCLOSE
    printf(" -D NO_LINGCLOSE\n");
#endif
#ifdef HAVE_FCNTL_SERIALIZED_ACCEPT
    printf(" -D HAVE_FCNTL_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_FLOCK_SERIALIZED_ACCEPT
    printf(" -D HAVE_FLOCK_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_USLOCK_SERIALIZED_ACCEPT
    printf(" -D HAVE_USLOCK_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_SYSVSEM_SERIALIZED_ACCEPT
    printf(" -D HAVE_SYSVSEM_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_PTHREAD_SERIALIZED_ACCEPT
    printf(" -D HAVE_PTHREAD_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_OS2SEM_SERIALIZED_ACCEPT
    printf(" -D HAVE_OS2SEM_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_TPF_CORE_SERIALIZED_ACCEPT
    printf(" -D HAVE_TPF_CORE_SERIALIZED_ACCEPT\n");
#endif
#ifdef HAVE_BEOS_SERIALIZED_ACCEPT
    printf(" -D HAVE_BEOS_SERIALIZED_ACCEPT\n");
#endif  
#ifdef HAVE_NONE_SERIALIZED_ACCEPT
    printf(" -D HAVE_NONE_SERIALIZED_ACCEPT\n");
#endif
#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
    printf(" -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n");
#endif
#ifdef NO_OTHER_CHILD
    printf(" -D NO_OTHER_CHILD\n");
#endif
#ifdef NO_RELIABLE_PIPED_LOGS
    printf(" -D NO_RELIABLE_PIPED_LOGS\n");
#endif
#ifdef BUFFERED_LOGS
    printf(" -D BUFFERED_LOGS\n");
#ifdef PIPE_BUF
	printf(" -D PIPE_BUF=%ld\n",(long)PIPE_BUF);
#endif
#endif
    printf(" -D DYNAMIC_MODULE_LIMIT=%ld\n",(long)DYNAMIC_MODULE_LIMIT);
    printf(" -D HARD_SERVER_LIMIT=%ld\n",(long)HARD_SERVER_LIMIT);
#ifdef MULTITHREAD
    printf(" -D MULTITHREAD\n");
#endif
#ifdef CHARSET_EBCDIC
    printf(" -D CHARSET_EBCDIC\n");
#endif
#ifdef NEED_HASHBANG_EMUL
    printf(" -D NEED_HASHBANG_EMUL\n");
#endif
#ifdef SHARED_CORE
    printf(" -D SHARED_CORE\n");
#endif
#ifdef SO_ACCEPTFILTER
    printf(" -D SO_ACCEPTFILTER\n");
    printf(" -D ACCEPT_FILTER_NAME=\"" ACCEPT_FILTER_NAME "\"\n");
#endif
#ifdef AP_ACCEPTFILTER_OFF
    printf(" -D AP_ACCEPTFILTER_OFF\n");
#endif
#ifdef CYGWIN_WINSOCK 
    printf(" -D CYGWIN_WINSOCK\n"); 
#endif 

/* This list displays the compiled-in default paths: */
#ifdef HTTPD_ROOT
    printf(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n");
#endif
#if defined(SUEXEC_BIN) && !defined(TPF)
    printf(" -D SUEXEC_BIN=\"" SUEXEC_BIN "\"\n");
#endif
#if defined(SHARED_CORE) && defined(SHARED_CORE_DIR)
    printf(" -D SHARED_CORE_DIR=\"" SHARED_CORE_DIR "\"\n");
#endif
#ifdef DEFAULT_PIDLOG
    printf(" -D DEFAULT_PIDLOG=\"" DEFAULT_PIDLOG "\"\n");
#endif
#ifdef DEFAULT_SCOREBOARD
    printf(" -D DEFAULT_SCOREBOARD=\"" DEFAULT_SCOREBOARD "\"\n");
#endif
#ifdef DEFAULT_LOCKFILE
    printf(" -D DEFAULT_LOCKFILE=\"" DEFAULT_LOCKFILE "\"\n");
#endif
#ifdef DEFAULT_ERRORLOG
    printf(" -D DEFAULT_ERRORLOG=\"" DEFAULT_ERRORLOG "\"\n");
#endif
#ifdef TYPES_CONFIG_FILE
    printf(" -D TYPES_CONFIG_FILE=\"" TYPES_CONFIG_FILE "\"\n");
#endif
#ifdef SERVER_CONFIG_FILE
    printf(" -D SERVER_CONFIG_FILE=\"" SERVER_CONFIG_FILE "\"\n");
#endif
#ifdef ACCESS_CONFIG_FILE
    printf(" -D ACCESS_CONFIG_FILE=\"" ACCESS_CONFIG_FILE "\"\n");
#endif
#ifdef RESOURCE_CONFIG_FILE
    printf(" -D RESOURCE_CONFIG_FILE=\"" RESOURCE_CONFIG_FILE "\"\n");
#endif
}


/* Some init code that's common between win32 and unix... well actually
 * some of it is #ifdef'd but was duplicated before anyhow.  This stuff
 * is still a mess.
 */
static void common_init(void)
{
    int i;
    INIT_SIGLIST()
#ifdef AUX3
    (void) set42sig();
#endif

#if defined(WIN32) || defined(NETWARE)
    /* Initialize the stupid sockets */
    AMCSocketInitialize();
#endif /* WIN32 */

    pglobal = ap_init_alloc();
    pconf = ap_make_sub_pool(pglobal);
#ifdef AP_ENABLE_EXCEPTION_HOOK
    ap_register_cleanup(pconf, NULL, except_hook_cleanup, ap_null_cleanup);
#endif
    plog = ap_make_sub_pool(pglobal);
    ptrans = ap_make_sub_pool(pconf);

    ap_util_init();
    ap_util_uri_init();

    pcommands = ap_make_sub_pool(NULL);
    ap_server_pre_read_config  = ap_make_array(pcommands, 1, sizeof(char *));
    ap_server_post_read_config = ap_make_array(pcommands, 1, sizeof(char *));
    ap_server_config_defines   = ap_make_array(pcommands, 1, sizeof(char *));
    /* overkill since static */
    for (i = 0; i < HARD_SERVER_LIMIT; i++) {
        pid_table[i] = 0;
    }
}

#ifndef MULTITHREAD
/*****************************************************************
 * Child process main loop.
 * The following vars are static to avoid getting clobbered by longjmp();
 * they are really private to child_main.
 */

static int srv;
static int csd;
static int dupped_csd;
static int requests_this_child;
static fd_set main_fds;

API_EXPORT(void) ap_child_terminate(request_rec *r)
{
    r->connection->keepalive = 0;
    requests_this_child = ap_max_requests_per_child = 1;
}

static void child_main(int child_num_arg)
{
    NET_SIZE_T clen;
    struct sockaddr sa_server;
    struct sockaddr sa_client;
    listen_rec *lr;

    /* All of initialization is a critical section, we don't care if we're
     * told to HUP or USR1 before we're done initializing.  For example,
     * we could be half way through child_init_modules() when a restart
     * signal arrives, and we'd have no real way to recover gracefully
     * and exit properly.
     *
     * I suppose a module could take forever to initialize, but that would
     * be either a broken module, or a broken configuration (i.e. network
     * problems, file locking problems, whatever). -djg
     */
    ap_block_alarms();

    my_pid = getpid();
    csd = -1;
    dupped_csd = -1;
    my_child_num = child_num_arg;
    requests_this_child = 0;

    /* Get a sub pool for global allocations in this child, so that
     * we can have cleanups occur when the child exits.
     */
    pchild = ap_make_sub_pool(pconf);
    /* associate accept mutex cleanup with a subpool of pchild so we can
     * make sure the mutex is released before calling module code at
     * termination
     */
    pmutex = ap_make_sub_pool(pchild);

    /* needs to be done before we switch UIDs so we have permissions */
    reopen_scoreboard(pchild);
    SAFE_ACCEPT(accept_mutex_child_init(pmutex));

    set_group_privs();
#ifdef MPE
    /* No such thing as root on MPE, so try to switch unconditionally */
    GETPRIVMODE();
    if (setuid(ap_user_id) == -1) {
	GETUSERMODE();
	ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
		    "setuid: unable to change to uid: %d", ap_user_id);
	exit(1);
    }
    GETUSERMODE();
#else
    /* 
     * Only try to switch if we're running as root
     * In case of Cygwin we have the special super-user named SYSTEM
     */
#ifdef CYGWIN
    if (getuid() == SYSTEM_UID && (
#else
    if (!geteuid() && (
#endif
#ifdef _OSD_POSIX
	os_init_job_environment(server_conf, ap_user_name, one_process) != 0 || 
#endif
	setuid(ap_user_id) == -1)) {
	ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
		    "setuid: unable to change to uid: %ld", (long) ap_user_id);
	clean_child_exit(APEXIT_CHILDFATAL);
    }
#endif

#ifdef HAVE_SET_DUMPABLE
    if (ap_coredump_dir_configured) {
        /* user set CoredumpDirectory, so they want to get core dumps
         */
        if (prctl(PR_SET_DUMPABLE, 1)) {
            ap_log_error(APLOG_MARK, APLOG_ALERT, NULL,
                         "set dumpable failed - this child will not coredump"
                         " after software errors");
        }
    }
#endif

    ap_child_init_modules(pchild, server_conf);

    /* done with the initialization critical section */
    ap_unblock_alarms();

    (void) ap_update_child_status(my_child_num, SERVER_READY, (request_rec *) NULL);

    /*
     * Setup the jump buffers so that we can return here after a timeout 
     */
    ap_setjmp(jmpbuffer);
#ifndef OS2
#ifdef SIGURG
    signal(SIGURG, timeout);
#endif
#endif
    signal(SIGALRM, alrm_handler);
#ifdef TPF
    signal(SIGHUP, just_die);
    signal(SIGTERM, just_die);
    signal(SIGUSR1, just_die);
#endif /* TPF */

#ifdef OS2
/* Stop Ctrl-C/Ctrl-Break signals going to child processes */
    {
        unsigned long ulTimes;
        DosSetSignalExceptionFocus(0, &ulTimes);
    }
#endif

    while (1) {
	BUFF *conn_io;
	request_rec *r;

	/* Prepare to receive a SIGUSR1 due to graceful restart so that
	 * we can exit cleanly.  Since we're between connections right
	 * now it's the right time to exit, but we might be blocked in a
	 * system call when the graceful restart request is made. */
	usr1_just_die = 1;
	signal(SIGUSR1, usr1_handler);

	/*
	 * (Re)initialize this child to a pre-connection state.
	 */

	ap_kill_timeout(0);	/* Cancel any outstanding alarms. */
	current_conn = NULL;

	ap_clear_pool(ptrans);

	ap_sync_scoreboard_image();
	if (ap_scoreboard_image->global.running_generation != ap_my_generation) {
	    clean_child_exit(0);
	}

#ifndef WIN32
	if ((ap_max_requests_per_child > 0
	     && requests_this_child++ >= ap_max_requests_per_child)) {
	    clean_child_exit(0);
	}
#else
	++requests_this_child;
#endif

	(void) ap_update_child_status(my_child_num, SERVER_READY, (request_rec *) NULL);

	/*
	 * Wait for an acceptable connection to arrive.
	 */

	/* Lock around "accept", if necessary */
	SAFE_ACCEPT(accept_mutex_on());

	for (;;) {
	    if (ap_listeners->next != ap_listeners) {
		/* more than one socket */
		memcpy(&main_fds, &listenfds, sizeof(fd_set));
		srv = ap_select(listenmaxfd + 1, &main_fds, NULL, NULL, NULL);

		if (srv < 0 && errno != EINTR) {
		    /* Single Unix documents select as returning errnos
		     * EBADF, EINTR, and EINVAL... and in none of those
		     * cases does it make sense to continue.  In fact
		     * on Linux 2.0.x we seem to end up with EFAULT
		     * occasionally, and we'd loop forever due to it.
		     */
		    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, "select: (listen)");
		    clean_child_exit(1);
		}

		if (srv <= 0)
		    continue;

		lr = find_ready_listener(&main_fds);
		if (lr == NULL)
		    continue;
		sd = lr->fd;
	    }
	    else {
		/* only one socket, just pretend we did the other stuff */
		sd = ap_listeners->fd;
	    }

	    /* if we accept() something we don't want to die, so we have to
	     * defer the exit
	     */
	    deferred_die = 0;
	    usr1_just_die = 0;
	    for (;;) {
		clen = sizeof(sa_client);
		csd = ap_accept(sd, &sa_client, &clen);
		if (csd >= 0 || errno != EINTR)
		    break;
		if (deferred_die) {
		    /* we didn't get a socket, and we were told to die */
		    clean_child_exit(0);
		}
	    }

	    if (csd >= 0)
		break;		/* We have a socket ready for reading */
	    else {

		/* Our old behaviour here was to continue after accept()
		 * errors.  But this leads us into lots of troubles
		 * because most of the errors are quite fatal.  For
		 * example, EMFILE can be caused by slow descriptor
		 * leaks (say in a 3rd party module, or libc).  It's
		 * foolish for us to continue after an EMFILE.  We also
		 * seem to tickle kernel bugs on some platforms which
		 * lead to never-ending loops here.  So it seems best
		 * to just exit in most cases.
		 */
                switch (errno) {

#if defined(HPUX11) && defined(ENOBUFS)
                    /* On HPUX 11.x, the 'ENOBUFS, No buffer space available'
                     * error occures because the accept() cannot complete.
                     * You will not see ENOBUFS at 10.20 because the kernel
                     * hides any occurrence from being returned from user space.
                     * ENOBUFS at 11.0 TCP/IP is quite possible, and could
                     * occur intermittently. As a work-around, we are going to
                     * ingnore ENOBUFS.
                     */
                case ENOBUFS:
#endif

#ifdef EPROTO
		    /* EPROTO on certain older kernels really means
		     * ECONNABORTED, so we need to ignore it for them.
		     * See discussion in new-httpd archives nh.9701
		     * search for EPROTO.
		     *
		     * Also see nh.9603, search for EPROTO:
		     * There is potentially a bug in Solaris 2.x x<6,
		     * and other boxes that implement tcp sockets in
		     * userland (i.e. on top of STREAMS).  On these
		     * systems, EPROTO can actually result in a fatal
		     * loop.  See PR#981 for example.  It's hard to
		     * handle both uses of EPROTO.
		     */
                case EPROTO:
#endif
#ifdef ECONNABORTED
                case ECONNABORTED:
#endif
		    /* Linux generates the rest of these, other tcp
		     * stacks (i.e. bsd) tend to hide them behind
		     * getsockopt() interfaces.  They occur when
		     * the net goes sour or the client disconnects
		     * after the three-way handshake has been done
		     * in the kernel but before userland has picked
		     * up the socket.
		     */
#ifdef ECONNRESET
                case ECONNRESET:
#endif
#ifdef ETIMEDOUT
                case ETIMEDOUT:
#endif
#ifdef EHOSTUNREACH
		case EHOSTUNREACH:
#endif
#ifdef ENETUNREACH
		case ENETUNREACH:
#endif
                    /* EAGAIN/EWOULDBLOCK can be returned on BSD-derived
                     * TCP stacks when the connection is aborted before
                     * we call connect, but only because our listener
                     * sockets are non-blocking (NONBLOCK_WHEN_MULTI_LISTEN)
                     */
#ifdef EAGAIN
                case EAGAIN:
#endif
#ifdef EWOULDBLOCK
#if !defined(EAGAIN) || EAGAIN != EWOULDBLOCK
                case EWOULDBLOCK:
#endif
#endif
                    break;
#ifdef ENETDOWN
		case ENETDOWN:
		     /*
		      * When the network layer has been shut down, there
		      * is not much use in simply exiting: the parent
		      * would simply re-create us (and we'd fail again).
		      * Use the CHILDFATAL code to tear the server down.
		      * @@@ Martin's idea for possible improvement:
		      * A different approach would be to define
		      * a new APEXIT_NETDOWN exit code, the reception
		      * of which would make the parent shutdown all
		      * children, then idle-loop until it detected that
		      * the network is up again, and restart the children.
		      * Ben Hyde noted that temporary ENETDOWN situations
		      * occur in mobile IP.
		      */
		    ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
			"accept: giving up.");
		    clean_child_exit(APEXIT_CHILDFATAL);
#endif /*ENETDOWN*/

#ifdef TPF
		case EINACT:
                    ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO,
                                 server_conf, "offload device inactive");
                    clean_child_exit(APEXIT_CHILDFATAL); 
		    break;
		default:
                    if (getppid() != 1) {
                        ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO,
                                     server_conf, "select/accept error (%u)",
                                     errno);
                    }
		    clean_child_exit(APEXIT_CHILDFATAL);
#else
		default:
		    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
				"accept: (client socket)");
		    clean_child_exit(1);
#endif
		}
	    }

	    /* go around again, safe to die */
	    usr1_just_die = 1;
	    if (deferred_die) {
		/* ok maybe not, see ya later */
		clean_child_exit(0);
	    }
	    /* or maybe we missed a signal, you never know on systems
	     * without reliable signals
	     */
	    ap_sync_scoreboard_image();
	    if (ap_scoreboard_image->global.running_generation != ap_my_generation) {
		clean_child_exit(0);
	    }
	}

	SAFE_ACCEPT(accept_mutex_off());	/* unlock after "accept" */

	/* We've got a socket, let's at least process one request off the
	 * socket before we accept a graceful restart request.
	 */
	signal(SIGUSR1, SIG_IGN);

	ap_note_cleanups_for_socket_ex(ptrans, csd, 1);

	/* protect various fd_sets */
#ifdef CHECK_FD_SETSIZE
	if (csd >= FD_SETSIZE) {
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
		"[csd] filedescriptor (%u) larger than FD_SETSIZE (%u) "
		"found, you probably need to rebuild Apache with a "
		"larger FD_SETSIZE", csd, FD_SETSIZE);
	    continue;
	}
#endif

	/*
	 * We now have a connection, so set it up with the appropriate
	 * socket options, file descriptors, and read/write buffers.
	 */

#ifdef NONBLOCK_WHEN_MULTI_LISTEN
        /* This assumes that on this platform the non-blocking setting of
         * a listening socket is inherited.  If that isn't the case,
         * this is wasted effort.
         */
        if (ap_listeners != ap_listeners->next) {
            if (soblock(csd) != 0) {
                ap_log_error(APLOG_MARK, APLOG_CRIT, NULL,
                             "couldn't make socket descriptor (%d) blocking again",
                             csd);
                continue;
            }
        }
#endif /* NONBLOCK_WHEN_MULTI_LISTEN */

	clen = sizeof(sa_server);
	if (getsockname(csd, &sa_server, &clen) < 0) {
	    ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf, 
                         "getsockname, client %pA probably dropped the "
                         "connection", 
                         &((struct sockaddr_in *)&sa_client)->sin_addr);
	    continue;
	}

	sock_disable_nagle(csd, (struct sockaddr_in *)&sa_client);

	(void) ap_update_child_status(my_child_num, SERVER_BUSY_READ,
				   (request_rec *) NULL);

	conn_io = ap_bcreate(ptrans, B_RDWR | B_SOCKET);

#ifdef B_SFIO
	(void) sfdisc(conn_io->sf_in, SF_POPDISC);
	sfdisc(conn_io->sf_in, bsfio_new(conn_io->pool, conn_io));
	sfsetbuf(conn_io->sf_in, NULL, 0);

	(void) sfdisc(conn_io->sf_out, SF_POPDISC);
	sfdisc(conn_io->sf_out, bsfio_new(conn_io->pool, conn_io));
	sfsetbuf(conn_io->sf_out, NULL, 0);
#endif

	dupped_csd = csd;
#if defined(NEED_DUPPED_CSD)
	if ((dupped_csd = dup(csd)) < 0) {
	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
			"dup: couldn't duplicate csd");
	    dupped_csd = csd;	/* Oh well... */
	}
	ap_note_cleanups_for_socket_ex(ptrans, dupped_csd, 1);

	/* protect various fd_sets */
#ifdef CHECK_FD_SETSIZE
	if (dupped_csd >= FD_SETSIZE) {
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
		"[dupped_csd] filedescriptor (%u) larger than FD_SETSIZE (%u) "
		"found, you probably need to rebuild Apache with a "
		"larger FD_SETSIZE", dupped_csd, FD_SETSIZE);
	    continue;
	}
#endif
#endif
	ap_bpushfd(conn_io, csd, dupped_csd);

	current_conn = new_connection(ptrans, server_conf, conn_io,
				          (struct sockaddr_in *) &sa_client,
				          (struct sockaddr_in *) &sa_server,
				          my_child_num);

	/*
	 * Read and process each request found on our connection
	 * until no requests are left or we decide to close.
	 */

	while ((r = ap_read_request(current_conn)) != NULL) {

	    /* read_request_line has already done a
	     * signal (SIGUSR1, SIG_IGN);
	     */

	    (void) ap_update_child_status(my_child_num, SERVER_BUSY_WRITE, r);

	    /* process the request if it was read without error */

	    if (r->status == HTTP_OK)
		ap_process_request(r);

	    if(ap_extended_status)
		increment_counts(my_child_num, r);

#ifdef TPF_HAVE_NSD
            /* Update the TPF Network Services Database message counters */
            tpf_tcpip_message_cnt(NSDB_INPUT_CNT,
                ((struct sockaddr_in *)&sa_server)->sin_port,
                NSDB_TCP_S, 1);

            tpf_tcpip_message_cnt(NSDB_OUTPUT_CNT,
                ((struct sockaddr_in *)&sa_server)->sin_port,
                NSDB_TCP_S, 1);
#endif /* TPF_HAVE_NSD */

	    if (!current_conn->keepalive || current_conn->aborted)
		break;

	    ap_destroy_pool(r->pool);
	    (void) ap_update_child_status(my_child_num, SERVER_BUSY_KEEPALIVE,
				       (request_rec *) NULL);

	    ap_sync_scoreboard_image();
	    if (ap_scoreboard_image->global.running_generation != ap_my_generation) {
		ap_bclose(conn_io);
		clean_child_exit(0);
	    }

	    /* In case we get a graceful restart while we're blocked
	     * waiting for the request.
	     *
	     * XXX: This isn't perfect, we might actually read the
	     * request and then just die without saying anything to
	     * the client.  This can be fixed by using deferred_die
	     * but you have to teach buff.c about it so that it can handle
	     * the EINTR properly.
	     *
	     * In practice though browsers (have to) expect keepalive
	     * connections to close before receiving a response because
	     * of network latencies and server timeouts.
	     */
	    usr1_just_die = 1;
	    signal(SIGUSR1, usr1_handler);
	}

	/*
	 * Close the connection, being careful to send out whatever is still
	 * in our buffers.  If possible, try to avoid a hard close until the
	 * client has ACKed our FIN and/or has stopped sending us data.
	 */

#ifdef NO_LINGCLOSE
	ap_bclose(conn_io);	/* just close it */
#else
	if (r && r->connection
	    && !r->connection->aborted
	    && r->connection->client
	    && (r->connection->client->fd >= 0)) {

	    lingering_close(r);
	}
	else {
	    ap_bsetflag(conn_io, B_EOUT, 1);
	    ap_bclose(conn_io);
	}
#endif
    }
}

#ifdef TPF
static void reset_tpf_listeners(APACHE_TPF_INPUT *input_parms)
{
    int count;
    listen_rec *lr;

    count = 0;
    listenmaxfd = -1;
    FD_ZERO(&listenfds);
    lr = ap_listeners;

    for(;;) {
        lr->fd = input_parms->listeners[count];
        if(lr->fd >= 0) {
            FD_SET(lr->fd, &listenfds);
            if(lr->fd > listenmaxfd)
                listenmaxfd = lr->fd;
        }
        if(lr->next == NULL)
            break;
        lr = lr->next;
        count++;
    }
    lr->next = ap_listeners;
    head_listener = ap_listeners;
    close_unused_listeners();
}

#endif /* TPF */

static int make_child(server_rec *s, int slot, time_t now)
{
    int pid;

    if (slot + 1 > max_daemons_limit) {
	max_daemons_limit = slot + 1;
    }

    if (one_process) {
	signal(SIGHUP, just_die);
	signal(SIGINT, just_die);
#ifdef SIGQUIT
	signal(SIGQUIT, SIG_DFL);
#endif
	signal(SIGTERM, just_die);
	child_main(slot);
    }

    /* avoid starvation */
    head_listener = head_listener->next;

    Explain1("Starting new child in slot %d", slot);
    (void) ap_update_child_status(slot, SERVER_STARTING, (request_rec *) NULL);


#ifdef _OSD_POSIX
    /* BS2000 requires a "special" version of fork() before a setuid() call */
    if ((pid = os_fork(ap_user_name)) == -1) {
#elif defined(TPF)
    if ((pid = os_fork(s, slot)) == -1) {
#else
    if ((pid = fork()) == -1) {
#endif
	ap_log_error(APLOG_MARK, APLOG_ERR, s, "fork: Unable to fork new process");

	/* fork didn't succeed. Fix the scoreboard or else
	 * it will say SERVER_STARTING forever and ever
	 */
	(void) ap_update_child_status(slot, SERVER_DEAD, (request_rec *) NULL);

	/* In case system resources are maxxed out, we don't want
	   Apache running away with the CPU trying to fork over and
	   over and over again. */
	sleep(10);

	return -1;
    }

    if (!pid) {
#ifdef AIX_BIND_PROCESSOR
/* by default AIX binds to a single processor
 * this bit unbinds children which will then bind to another cpu
 */
#include <sys/processor.h>
	int status = bindprocessor(BINDPROCESS, (int)getpid(), 
				   PROCESSOR_CLASS_ANY);
	if (status != OK) {
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, server_conf,
			"processor unbind failed %d", status);
	}
#endif
	RAISE_SIGSTOP(MAKE_CHILD);
	MONCONTROL(1);
	/* Disable the restart signal handlers and enable the just_die stuff.
	 * Note that since restart() just notes that a restart has been
	 * requested there's no race condition here.
	 */
	signal(SIGHUP, just_die);
	signal(SIGUSR1, just_die);
	signal(SIGTERM, just_die);
	child_main(slot);
    }

#ifdef OPTIMIZE_TIMEOUTS
    ap_scoreboard_image->parent[slot].last_rtime = now;
#endif
    ap_scoreboard_image->parent[slot].pid = pid;
    set_pid_table(pid);
#ifdef SCOREBOARD_FILE
    lseek(scoreboard_fd, XtOffsetOf(scoreboard, parent[slot]), 0);
    force_write(scoreboard_fd, &ap_scoreboard_image->parent[slot],
		sizeof(parent_score));
#endif

    return 0;
}


/* start up a bunch of children */
static void startup_children(int number_to_start)
{
    int i;
    time_t now = time(NULL);

    for (i = 0; number_to_start && i < ap_daemons_limit; ++i) {
	if (ap_scoreboard_image->servers[i].status != SERVER_DEAD) {
	    continue;
	}
	if (make_child(server_conf, i, now) < 0) {
	    break;
	}
	--number_to_start;
    }
}


/*
 * idle_spawn_rate is the number of children that will be spawned on the
 * next maintenance cycle if there aren't enough idle servers.  It is
 * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
 * without the need to spawn.
 */
static int idle_spawn_rate = 1;
#ifndef MAX_SPAWN_RATE
#define MAX_SPAWN_RATE	(32)
#endif
static int hold_off_on_exponential_spawning;

/*
 * Define the signal that is used to kill off children if idle_count
 * is greater then ap_daemons_max_free. Usually we will use SIGUSR1
 * to gracefully shutdown, but unfortunatly some OS will need other 
 * signals to ensure that the child process is terminated and the 
 * scoreboard pool is not growing to infinity. Also set the signal we
 * use to kill of childs that exceed timeout. This effect has been
* seen at least on Cygwin 1.x. -- Stipe Tolj <tolj@wapme-systems.de>
 */
#if defined(CYGWIN)
#define SIG_IDLE_KILL SIGKILL
#define SIG_TIMEOUT_KILL SIGUSR2
#else
#define SIG_IDLE_KILL SIGUSR1
#define SIG_TIMEOUT_KILL SIGALRM
#endif

static void perform_idle_server_maintenance(void)
{
    int i;
    int to_kill;
    int idle_count;
    int pid;
    short_score *ss;
    time_t now = time(NULL);
    int free_length;
    int free_slots[MAX_SPAWN_RATE];
    int last_non_dead;
    int total_non_dead;

    /* initialize the free_list */
    free_length = 0;

    to_kill = -1;
    idle_count = 0;
    last_non_dead = -1;
    total_non_dead = 0;

    ap_sync_scoreboard_image();
    for (i = 0; i < ap_daemons_limit; ++i) {
	int status;

	if (i >= max_daemons_limit && free_length == idle_spawn_rate)
	    break;
	ss = &ap_scoreboard_image->servers[i];
	status = ss->status;
	if (status == SERVER_DEAD) {
	    /* try to keep children numbers as low as possible */
	    if (free_length < idle_spawn_rate) {
		free_slots[free_length] = i;
		++free_length;
	    }
	}
	else {
	    /* We consider a starting server as idle because we started it
	     * at least a cycle ago, and if it still hasn't finished starting
	     * then we're just going to swamp things worse by forking more.
	     * So we hopefully won't need to fork more if we count it.
	     * This depends on the ordering of SERVER_READY and SERVER_STARTING.
	     */
	    if (status <= SERVER_READY) {
		++ idle_count;
		/* always kill the highest numbered child if we have to...
		 * no really well thought out reason ... other than observing
		 * the server behaviour under linux where lower numbered children
		 * tend to service more hits (and hence are more likely to have
		 * their data in cpu caches).
		 */
		to_kill = i;
	    }

	    ++total_non_dead;
	    last_non_dead = i;
#ifdef OPTIMIZE_TIMEOUTS
	    if (ss->timeout_len) {
		/* if it's a live server, with a live timeout then
		 * start checking its timeout */
		parent_score *ps = &ap_scoreboard_image->parent[i];
		if (ss->cur_vtime != ps->last_vtime) {
		    /* it has made progress, so update its last_rtime,
		     * last_vtime */
		    ps->last_rtime = now;
		    ps->last_vtime = ss->cur_vtime;
		}
		else if (ps->last_rtime + ss->timeout_len < now) {
		    /* no progress, and the timeout length has been exceeded */
		    ss->timeout_len = 0;
                    pid = ps->pid;
                    if (in_pid_table(pid)) {
                        kill(pid, SIG_TIMEOUT_KILL);
                    }
                    else {
                        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
                            "Bad pid (%d) in scoreboard slot %d", pid, i);
                    }
                }
	    }
#endif
	}
    }
    max_daemons_limit = last_non_dead + 1;
    if (idle_count > ap_daemons_max_free) {
	/* kill off one child... we use SIGUSR1 because that'll cause it to
	 * shut down gracefully, in case it happened to pick up a request
	 * while we were counting. Use the define SIG_IDLE_KILL to reflect
	 * which signal should be used on the specific OS.
	 */
        pid = ap_scoreboard_image->parent[to_kill].pid;
        if (in_pid_table(pid)) {
            kill(pid, SIG_IDLE_KILL);
            idle_spawn_rate = 1;
#ifdef TPF
            ap_update_child_status(to_kill, SERVER_DEAD, (request_rec *)NULL);
#endif
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
                         "Bad pid (%d) in scoreboard slot %d", pid, to_kill);
        }
    }
    else if (idle_count < ap_daemons_min_free) {
	/* terminate the free list */
	if (free_length == 0) {
	    /* only report this condition once */
	    static int reported = 0;

	    if (!reported) {
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
			    "server reached MaxClients setting, consider"
			    " raising the MaxClients setting");
		reported = 1;
	    }
	    idle_spawn_rate = 1;
	}
	else {
	    if (idle_spawn_rate >= 8) {
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf,
		    "server seems busy, (you may need "
		    "to increase StartServers, or Min/MaxSpareServers), "
		    "spawning %d children, there are %d idle, and "
		    "%d total children", idle_spawn_rate,
		    idle_count, total_non_dead);
	    }
	    for (i = 0; i < free_length; ++i) {
#ifdef TPF
        if(make_child(server_conf, free_slots[i], now) == -1) {
            if(free_length == 1) {
                shutdown_pending = 1;
                ap_log_error(APLOG_MARK, APLOG_EMERG, server_conf,
                "No active child processes: shutting down");
            }
        }
#else
		make_child(server_conf, free_slots[i], now);
#endif /* TPF */
	    }
	    /* the next time around we want to spawn twice as many if this
	     * wasn't good enough, but not if we've just done a graceful
	     */
	    if (hold_off_on_exponential_spawning) {
		--hold_off_on_exponential_spawning;
	    }
	    else if (idle_spawn_rate < MAX_SPAWN_RATE) {
		idle_spawn_rate *= 2;
	    }
	}
    }
    else {
	idle_spawn_rate = 1;
    }
}


static void process_child_status(int pid, ap_wait_t status)
{
    /* Child died... if it died due to a fatal error,
	* we should simply bail out.
	*/
    if ((WIFEXITED(status)) &&
	WEXITSTATUS(status) == APEXIT_CHILDFATAL) {
        /* cleanup pid file -- it is useless after our exiting */
        const char *pidfile = NULL;
#ifdef TPF
        /* safer on TPF to go through normal shutdown process */
	if (!shutdown_pending) {
		ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, server_conf,
			"Child %d returned a Fatal error... \n"
			"Apache is shutting down!", pid);
		shutdown_pending = 1;
	}
        return;
#endif
        pidfile = ap_server_root_relative (pconf, ap_pid_fname);
        if ( pidfile != NULL && unlink(pidfile) == 0)
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO,
                         server_conf,
                         "removed PID file %s (pid=%ld)",
                         pidfile, (long)getpid());
	ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_NOERRNO, server_conf,
			"Child %d returned a Fatal error... \n"
			"Apache is exiting!",
			pid);
	exit(APEXIT_CHILDFATAL);
    }
    if (WIFSIGNALED(status)) {
	switch (WTERMSIG(status)) {
	case SIGTERM:
	case SIGHUP:
	case SIGUSR1:
	case SIGKILL:
	    break;
	default:
#ifdef SYS_SIGLIST
#ifdef WCOREDUMP
	    if (WCOREDUMP(status)) {
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE,
			     server_conf,
			     "child pid %d exit signal %s (%d), "
			     "possible coredump in %s",
			     pid, (WTERMSIG(status) >= NumSIG) ? "" : 
			     SYS_SIGLIST[WTERMSIG(status)], WTERMSIG(status),
			     ap_coredump_dir);
	    }
	    else {
#endif
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE,
			     server_conf,
			     "child pid %d exit signal %s (%d)", pid,
			     SYS_SIGLIST[WTERMSIG(status)], WTERMSIG(status));
#ifdef WCOREDUMP
	    }
#endif
#else
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE,
			 server_conf,
			 "child pid %d exit signal %d",
			 pid, WTERMSIG(status));
#endif
	}
    }
}


/*****************************************************************
 * Executive routines.
 */

#ifndef STANDALONE_MAIN
#define STANDALONE_MAIN standalone_main

static void standalone_main(int argc, char **argv)
{
    int remaining_children_to_start;

#ifdef OS2
    printf("%s \n", ap_get_server_version());
#endif

    ap_standalone = 1;

    is_graceful = 0;

    if (!one_process) {
	detach();
    }
    else {
	MONCONTROL(1);
    }

    my_pid = getpid();

    do {
	copy_listeners(pconf);
	if (!is_graceful) {
	    ap_restart_time = time(NULL);
	}
#ifdef SCOREBOARD_FILE
	else if (scoreboard_fd != -1) {
	    ap_kill_cleanup(pconf, NULL, cleanup_scoreboard_file);
	    ap_kill_cleanups_for_fd(pconf, scoreboard_fd);
	}
#endif
	ap_clear_pool(pconf);
#ifdef AP_ENABLE_EXCEPTION_HOOK
        ap_register_cleanup(pconf, NULL, except_hook_cleanup, ap_null_cleanup);
#endif
	ptrans = ap_make_sub_pool(pconf);

	ap_init_mutex_method(ap_default_mutex_method());

	server_conf = ap_read_config(pconf, ptrans, ap_server_confname);
	setup_listeners(pconf);
	ap_clear_pool(plog);
	ap_open_logs(server_conf, plog);
	ap_log_pid(pconf, ap_pid_fname);
	ap_set_version();	/* create our server_version string */
	ap_init_modules(pconf, server_conf);
	version_locked++;	/* no more changes to server_version */
	SAFE_ACCEPT(accept_mutex_init(pconf));
	if (!is_graceful) {
	    reinit_scoreboard(pconf);
	}
#ifdef SCOREBOARD_FILE
	else {
	    ap_scoreboard_fname = ap_server_root_relative(pconf, ap_scoreboard_fname);
	    ap_note_cleanups_for_fd_ex(pconf, scoreboard_fd, 1); /* close on exec */
	}
#endif

	set_signals();

	if (ap_daemons_max_free < ap_daemons_min_free + 1)	/* Don't thrash... */
	    ap_daemons_max_free = ap_daemons_min_free + 1;

	/* If we're doing a graceful_restart then we're going to see a lot
	 * of children exiting immediately when we get into the main loop
	 * below (because we just sent them SIGUSR1).  This happens pretty
	 * rapidly... and for each one that exits we'll start a new one until
	 * we reach at least daemons_min_free.  But we may be permitted to
	 * start more than that, so we'll just keep track of how many we're
	 * supposed to start up without the 1 second penalty between each fork.
	 */
	remaining_children_to_start = ap_daemons_to_start;
	if (remaining_children_to_start > ap_daemons_limit) {
	    remaining_children_to_start = ap_daemons_limit;
	}
	if (!is_graceful) {
	    startup_children(remaining_children_to_start);
	    remaining_children_to_start = 0;
	}
	else {
	    /* give the system some time to recover before kicking into
	     * exponential mode */
	    hold_off_on_exponential_spawning = 10;
	}

	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
		    "%s configured -- resuming normal operations",
		    ap_get_server_version());
	if (ap_suexec_enabled) {
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
		         "suEXEC mechanism enabled (wrapper: %s)", SUEXEC_BIN);
	}
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf,
		    "Server built: %s", ap_get_server_built());
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
		    "Accept mutex: %s (Default: %s)",
		     amutex->name, ap_default_mutex_method());
	restart_pending = shutdown_pending = 0;

	while (!restart_pending && !shutdown_pending) {
	    int child_slot;
	    ap_wait_t status;
	    int pid = wait_or_timeout(&status);

	    /* XXX: if it takes longer than 1 second for all our children
	     * to start up and get into IDLE state then we may spawn an
	     * extra child
	     */
#ifdef TPF
            if (shutdown_pending += os_check_server(tpf_server_name)) {
                break;
            }
#endif
	    if (pid >= 0) {
		unset_pid_table(pid);
		process_child_status(pid, status);
		/* non-fatal death... note that it's gone in the scoreboard. */
		ap_sync_scoreboard_image();
		child_slot = find_child_by_pid(pid);
		Explain2("Reaping child %d slot %d", pid, child_slot);
		if (child_slot >= 0) {
		    (void) ap_update_child_status(child_slot, SERVER_DEAD,
					       (request_rec *) NULL);
		    if (remaining_children_to_start
			&& child_slot < ap_daemons_limit) {
			/* we're still doing a 1-for-1 replacement of dead
			 * children with new children
			 */
			make_child(server_conf, child_slot, time(NULL));
			--remaining_children_to_start;
		    }
#ifndef NO_OTHER_CHILD
		}
		else if (reap_other_child(pid, status) == 0) {
		    /* handled */
#endif
		}
		else if (is_graceful) {
		    /* Great, we've probably just lost a slot in the
		     * scoreboard.  Somehow we don't know about this
		     * child.
		     */
		    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, server_conf,
				"long lost child came home! (pid %d)", pid);
		}
		/* Don't perform idle maintenance when a child dies,
		 * only do it when there's a timeout.  Remember only a
		 * finite number of children can die, and it's pretty
		 * pathological for a lot to die suddenly.
		 */
		continue;
	    }
	    else if (remaining_children_to_start) {
		/* we hit a 1 second timeout in which none of the previous
		 * generation of children needed to be reaped... so assume
		 * they're all done, and pick up the slack if any is left.
		 */
		startup_children(remaining_children_to_start);
		remaining_children_to_start = 0;
		/* In any event we really shouldn't do the code below because
		 * few of the servers we just started are in the IDLE state
		 * yet, so we'd mistakenly create an extra server.
		 */
		continue;
	    }

	    perform_idle_server_maintenance();
	}

	if (shutdown_pending) {
	    /* Time to gracefully shut down:
	     * Kill child processes, tell them to call child_exit, etc...
	     */
	    if (ap_killpg(pgrp, SIGTERM) < 0) {
		ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "killpg SIGTERM");
	    }
	    reclaim_child_processes(1);		/* Start with SIGTERM */

	    /* cleanup pid file on normal shutdown */
	    {
		const char *pidfile = NULL;
		pidfile = ap_server_root_relative (pconf, ap_pid_fname);
		if ( pidfile != NULL && unlink(pidfile) == 0)
		    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO,
				 server_conf,
				 "removed PID file %s (pid=%ld)",
				 pidfile, (long)getpid());
	    }

	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
			"caught SIGTERM, shutting down");
	    clean_parent_exit(0);
	}

	/* we've been told to restart */
	signal(SIGHUP, SIG_IGN);
	signal(SIGUSR1, SIG_IGN);

	if (one_process) {
	    /* not worth thinking about */
	    clean_parent_exit(0);
	}

	/* advance to the next generation */
	/* XXX: we really need to make sure this new generation number isn't in
	 * use by any of the children.
	 */
	++ap_my_generation;
	ap_scoreboard_image->global.running_generation = ap_my_generation;
	update_scoreboard_global();

	if (is_graceful) {
#ifndef SCOREBOARD_FILE
	    int i;
#endif
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
			"SIGUSR1 received.  Doing graceful restart");

	    /* kill off the idle ones */
	    if (ap_killpg(pgrp, SIGUSR1) < 0) {
		ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "killpg SIGUSR1");
	    }
#ifndef SCOREBOARD_FILE
	    /* This is mostly for debugging... so that we know what is still
	     * gracefully dealing with existing request.  But we can't really
	     * do it if we're in a SCOREBOARD_FILE because it'll cause
	     * corruption too easily.
	     */
	    ap_sync_scoreboard_image();
	    for (i = 0; i < ap_daemons_limit; ++i) {
		if (ap_scoreboard_image->servers[i].status != SERVER_DEAD) {
		    ap_scoreboard_image->servers[i].status = SERVER_GRACEFUL;
		}
	    }
#endif
	}
	else {
	    /* Kill 'em off */
	    if (ap_killpg(pgrp, SIGHUP) < 0) {
		ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "killpg SIGHUP");
	    }
	    reclaim_child_processes(0);		/* Not when just starting up */
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, server_conf,
			"SIGHUP received.  Attempting to restart");
	}
    } while (restart_pending);

    /*add_common_vars(NULL);*/
}				/* standalone_main */
#else
/* prototype */
void STANDALONE_MAIN(int argc, char **argv);
#endif /* STANDALONE_MAIN */

extern char *optarg;
extern int optind;

/* Cygwin 1.x SHARED_CORE support needs REALMAIN to be declared as dllexport,
 * so we can later while SHARED_CORE_BOOTSTRAP is compiled and linked see the 
 * dllimport for it. -- Stipe Tolj <tolj@wapme-systems.de>
 */

#if defined(CYGWIN)
__declspec(dllexport)  
#endif

int REALMAIN(int argc, char *argv[])
{
    int c;
    int sock_in;
    int sock_out;
    char *s;
    
#ifdef SecureWare
    if (set_auth_parameters(argc, argv) < 0)
	perror("set_auth_parameters");
    if (getluid() < 0)
	if (setluid(getuid()) < 0)
	    perror("setluid");
    if (setreuid(0, 0) < 0)
	perror("setreuid");
#endif

#ifdef SOCKS
    SOCKSinit(argv[0]);
#endif

#ifdef TPF
    EBW_AREA input_parms;
    ecbptr()->ebrout = PRIMECRAS;
    input_parms = * (EBW_AREA *)(&(ecbptr()->ebw000));
#endif

    MONCONTROL(0);

    common_init();
    
    if ((s = strrchr(argv[0], PATHSEPARATOR)) != NULL) {
	ap_server_argv0 = ++s;
    }
    else {
	ap_server_argv0 = argv[0];
    }
    
    ap_cpystrn(ap_server_root, HTTPD_ROOT, sizeof(ap_server_root));
    ap_cpystrn(ap_server_confname, SERVER_CONFIG_FILE, sizeof(ap_server_confname));

    ap_setup_prelinked_modules();

    while ((c = getopt(argc, argv,
				    "D:C:c:xXd:Ff:vVlLR:StTh"
#ifdef DEBUG_SIGSTOP
				    "Z:"
#endif
			)) != -1) {
	char **new;
	switch (c) {
	case 'c':
	    new = (char **)ap_push_array(ap_server_post_read_config);
	    *new = ap_pstrdup(pcommands, optarg);
	    break;
	case 'C':
	    new = (char **)ap_push_array(ap_server_pre_read_config);
	    *new = ap_pstrdup(pcommands, optarg);
	    break;
	case 'D':
	    new = (char **)ap_push_array(ap_server_config_defines);
	    *new = ap_pstrdup(pcommands, optarg);
	    break;
	case 'd':
	    ap_cpystrn(ap_server_root, optarg, sizeof(ap_server_root));
	    break;
	case 'F':
	    do_detach = 0;
	    break;
	case 'f':
	    ap_cpystrn(ap_server_confname, optarg, sizeof(ap_server_confname));
	    break;
	case 'v':
	    ap_set_version();
	    printf("Server version: %s\n", ap_get_server_version());
	    printf("Server built:   %s\n", ap_get_server_built());
	    exit(0);
	case 'V':
	    ap_set_version();
	    show_compile_settings();
	    exit(0);
	case 'l':
	    ap_suexec_enabled = init_suexec();
	    ap_show_modules();
	    exit(0);
	case 'L':
	    ap_show_directives();
	    exit(0);
	case 'X':
	    ++one_process;	/* Weird debugging mode. */
	    break;
#ifdef TPF
	case 'x':
	    os_tpf_child(&input_parms.child);
	    set_signals();
	    break;
#endif
#ifdef DEBUG_SIGSTOP
	case 'Z':
	    raise_sigstop_flags = atoi(optarg);
	    break;
#endif
#ifdef SHARED_CORE
	case 'R':
	    /* just ignore this option here, because it has only
	     * effect when SHARED_CORE is used and then it was
	     * already handled in the Shared Core Bootstrap
	     * program.
	     */
	    break;
#endif
	case 'S':
	    ap_dump_settings = 1;
	    break;
	case 't':
	    ap_configtestonly = 1;
	    ap_docrootcheck = 1;
	    break;
	case 'T':
	    ap_configtestonly = 1;
	    ap_docrootcheck = 0;
	    break;
	case 'h':
	    usage(argv[0]);
	case '?':
	    usage(argv[0]);
	}
    }

    ap_suexec_enabled = init_suexec();
    server_conf = ap_read_config(pconf, ptrans, ap_server_confname);

    if (ap_configtestonly) {
        fprintf(stderr, "Syntax OK\n");
        exit(0);
    }
    if (ap_dump_settings) {
        exit(0);
    }

    child_timeouts = !ap_standalone || one_process;

#ifdef BEOS
    /* make sure we're running in single_process mode - Yuck! */
    one_process = 1;
#endif

#ifndef TPF
    if (ap_standalone) {
	ap_open_logs(server_conf, plog);
	ap_set_version();
	ap_init_modules(pconf, server_conf);
	version_locked++;
	STANDALONE_MAIN(argc, argv);
    }
#else
    if (!tpf_child) {
        memcpy(tpf_server_name, input_parms.parent.servname,
               INETD_SERVNAME_LENGTH);
        tpf_server_name[INETD_SERVNAME_LENGTH] = '\0';
        sprintf(tpf_mutex_key, "%.*x", (int) TPF_MUTEX_KEY_SIZE - 1, getpid());
        tpf_parent_pid = getppid();
        ap_open_logs(server_conf, plog);
        ap_tpf_zinet_checks(ap_standalone, tpf_server_name, server_conf);
        ap_tpf_save_argv(argc, argv);    /* save argv parms for children */
    }
    if (ap_standalone) {
        ap_set_version();
        ap_init_modules(pconf, server_conf);
        version_locked++;
        if(tpf_child) {
           server_conf->error_log = stderr;
#ifdef HAVE_SYSLOG
            /* if ErrorLog is syslog call ap_open_logs from the child since
               syslog isn't redirected to stderr by the Apache parent */
            if (strncasecmp(server_conf->error_fname, "syslog", 6) == 0) {
               ap_open_logs(server_conf, plog);
            }
#endif /* HAVE_SYSLOG */
            copy_listeners(pconf);
            reset_tpf_listeners(&input_parms.child);
#ifdef SCOREBOARD_FILE
            ap_scoreboard_image = &_scoreboard_image;
#else /* must be USE_SHMGET_SCOREBOARD */
            ap_scoreboard_image =
                (scoreboard *)input_parms.child.scoreboard_heap;
#endif
            ap_init_mutex_method(ap_default_mutex_method());
            child_main(input_parms.child.slot);
        }
        else
            STANDALONE_MAIN(argc, argv);
    }
#endif
    else {
	conn_rec *conn;
	request_rec *r;
	struct sockaddr sa_server, sa_client;
	BUFF *cio;
	NET_SIZE_T l;

	ap_set_version();
	/* Yes this is called twice. */
	ap_init_modules(pconf, server_conf);
	version_locked++;
	ap_open_logs(server_conf, plog);
	ap_init_modules(pconf, server_conf);
	set_group_privs();

#ifdef MPE
	/* No such thing as root on MPE, so try to switch unconditionally */
	GETPRIVMODE();
	if (setuid(ap_user_id) == -1) {
	    GETUSERMODE();
	    ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
			"setuid: unable to change to uid: %d", ap_user_id);
	    exit(1);
	}
	GETUSERMODE();
#else
    /* 
     * Only try to switch if we're running as root
     * In case of Cygwin we have the special super-user named SYSTEM
     * with a pre-defined uid.
     */
#ifdef CYGWIN
    if ((getuid() == SYSTEM_UID) && setuid(ap_user_id) == -1) {
#else
	if (!geteuid() && setuid(ap_user_id) == -1) {
#endif
	    ap_log_error(APLOG_MARK, APLOG_ALERT, server_conf,
			"setuid: unable to change to uid: %ld",
			(long) ap_user_id);
	    exit(1);
	}
#endif
	if (ap_setjmp(jmpbuffer)) {
	    exit(0);
	}

#ifdef MPE
/* HP MPE 5.5 inetd only passes the incoming socket as stdin (fd 0), whereas
   HPUX inetd passes the incoming socket as stdin (fd 0) and stdout (fd 1).
   Go figure.  SR 5003355016 has been submitted to request that the existing
   functionality be documented, and then to enhance the functionality to be
   like HPUX. */
    sock_in = fileno(stdin);
    sock_out = fileno(stdin);
#else
    sock_in = fileno(stdin);
    sock_out = fileno(stdout);
#endif

	l = sizeof(sa_client);
	if ((getpeername(sock_in, &sa_client, &l)) < 0) {
/* get peername will fail if the input isn't a socket */
	    perror("getpeername");
	    memset(&sa_client, '\0', sizeof(sa_client));
	}

	l = sizeof(sa_server);
	if (getsockname(sock_in, &sa_server, &l) < 0) {
	    perror("getsockname");
	    fprintf(stderr, "Error getting local address\n");
	    exit(1);
	}
	server_conf->port = ntohs(((struct sockaddr_in *) &sa_server)->sin_port);
	cio = ap_bcreate(ptrans, B_RDWR | B_SOCKET);
        cio->fd = sock_out;
        cio->fd_in = sock_in;
	conn = new_connection(ptrans, server_conf, cio,
			          (struct sockaddr_in *) &sa_client,
			          (struct sockaddr_in *) &sa_server, -1);

	while ((r = ap_read_request(conn)) != NULL) {

	    if (r->status == HTTP_OK)
		ap_process_request(r);

	    if (!conn->keepalive || conn->aborted)
		break;

	    ap_destroy_pool(r->pool);
	}

	ap_bclose(cio);
    }
    exit(0);
}

#else /* ndef MULTITHREAD */


/**********************************************************************
 * Multithreaded implementation
 *
 * This code is fairly specific to Win32.
 *
 * The model used to handle requests is a set of threads. One "main"
 * thread listens for new requests. When something becomes
 * available, it does a select and places the newly available socket
 * onto a list of "jobs" (add_job()). Then any one of a fixed number
 * of "worker" threads takes the top job off the job list with
 * remove_job() and handles that connection to completion. After
 * the connection has finished the thread is free to take another
 * job from the job list.
 *
 * In the code, the "main" thread is running within the worker_main()
 * function. The first thing this function does is create the
 * worker threads, which operate in the child_sub_main() function. The
 * main thread then goes into a loop within worker_main() where they
 * do a select() on the listening sockets. The select times out once
 * per second so that the thread can check for an "exit" signal
 * from the parent process (see below). If this signal is set, the 
 * thread can exit, but only after it has accepted all incoming
 * connections already in the listen queue (since Win32 appears
 * to through away listened but unaccepted connections when a 
 * process dies).
 *
 * Because the main and worker threads exist within a single process
 * they are vulnerable to crashes or memory leaks (crashes can also
 * be caused within modules, of course). There also needs to be a 
 * mechanism to perform restarts and shutdowns. This is done by
 * creating the main & worker threads within a subprocess. A
 * main process (the "parent process") creates one (or more) 
 * processes to do the work, then the parent sits around waiting
 * for the working process to die, in which case it starts a new
 * one. The parent process also handles restarts (by creating
 * a new working process then signalling the previous working process 
 * exit ) and shutdowns (by signalling the working process to exit).
 * The parent process operates within the master_main() function. This
 * process also handles requests from the service manager (NT only).
 *
 * Signalling between the parent and working process uses a Win32
 * event. Each child has a unique name for the event, which is
 * passed to it with the -Z argument when the child is spawned. The
 * parent sets (signals) this event to tell the child to die.
 * At present all children do a graceful die - they finish all
 * current jobs _and_ empty the listen queue before they exit.
 * A non-graceful die would need a second event. The -Z argument in
 * the child is also used to create the shutdown and restart events,
 * since the prefix (apPID) contains the parent process PID.
 *
 * The code below starts with functions at the lowest level -
 * worker threads, and works up to the top level - the main()
 * function of the parent process.
 *
 * The scoreboard (in process memory) contains details of the worker
 * threads (within the active working process). There is no shared
 * "scoreboard" between processes, since only one is ever active
 * at once (or at most, two, when one has been told to shutdown but
 * is processes outstanding requests, and a new one has been started).
 * This is controlled by a "start_mutex" which ensures only one working
 * process is active at once.
 **********************************************************************/

/* The code protected by #ifdef UNGRACEFUL_RESTARTS/#endif sections
 * could implement a sort-of ungraceful restart for Win32. instead of
 * graceful restarts. 
 *
 * However it does not work too well because it does not intercept a
 * connection already in progress (in child_sub_main()). We'd have to
 * get that to poll on the exit event. 
 */

/*
 * Definition of jobs, shared by main and worker threads.
 */

typedef struct joblist_s {
    struct joblist_s *next;
    int sock;
} joblist;

/*
 * Globals common to main and worker threads. This structure is not
 * used by the parent process.
 */

typedef struct globals_s {
#ifdef UNGRACEFUL_RESTART
    HANDLE thread_exit_event;
#else
    int exit_now;
#endif
    semaphore *jobsemaphore;
    joblist *jobhead;
    joblist *jobtail;
    mutex *jobmutex;
    int jobcount;
} globals;

globals allowed_globals =
{0, NULL, NULL, NULL, NULL, 0};

/*
 * add_job()/remove_job() - add or remove an accepted socket from the
 * list of sockets connected to clients. allowed_globals.jobmutex protects
 * against multiple concurrent access to the linked list of jobs.
 */

void add_job(int sock)
{
    joblist *new_job;
    
    ap_assert(allowed_globals.jobmutex);

    /* TODO: If too many jobs in queue, sleep, check for problems */
    ap_acquire_mutex(allowed_globals.jobmutex);
    new_job = (joblist *) malloc(sizeof(joblist));
    if (new_job == NULL) {
	fprintf(stderr, "Ouch!  Out of memory in add_job()!\n");
    }
    new_job->next = NULL;
    new_job->sock = sock;
    if (allowed_globals.jobtail != NULL)
	allowed_globals.jobtail->next = new_job;
    allowed_globals.jobtail = new_job;
    if (!allowed_globals.jobhead)
	allowed_globals.jobhead = new_job;
    allowed_globals.jobcount++;
    release_semaphore(allowed_globals.jobsemaphore);
    ap_release_mutex(allowed_globals.jobmutex);
}

int remove_job(int csd)
{
    static reported = 0;
    static active_threads = 0;
    joblist *job;
    int sock;

    /* Decline decrementing active_threads count on the first call
     * to remove_job.  csd == -1 implies that this is the thread's
     * first call to remove_job.
     */
    if (csd != -1) {
        active_threads--;
    }

#ifdef UNGRACEFUL_RESTART
    HANDLE hObjects[2];
    int rv;

    hObjects[0] = allowed_globals.jobsemaphore;
    hObjects[1] = allowed_globals.thread_exit_event;

    rv = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE);
    ap_assert(rv != WAIT_FAILED);
    if (rv == WAIT_OBJECT_0 + 1) {
	/* thread_exit_now */
	APD1("thread got exit now event");
	return -1;
    }
    /* must be semaphore */
#else
    acquire_semaphore(allowed_globals.jobsemaphore);
#endif
    ap_assert(allowed_globals.jobmutex);

#ifdef UNGRACEFUL_RESTART
    if (!allowed_globals.jobhead) {
#else
    ap_acquire_mutex(allowed_globals.jobmutex);
    if (allowed_globals.exit_now && !allowed_globals.jobhead) {
#endif
	ap_release_mutex(allowed_globals.jobmutex);
	return (-1);
    }

    job = allowed_globals.jobhead;
    ap_assert(job);
    allowed_globals.jobhead = job->next;
    if (allowed_globals.jobhead == NULL)
	allowed_globals.jobtail = NULL;

    ap_release_mutex(allowed_globals.jobmutex);
    sock = job->sock;
    free(job);

    /* If sock == -1 then the thread is about to exit so 
     * don't count it as active.
     */
    if (sock != -1)
        active_threads++;

    if (!reported && (active_threads == ap_threads_per_child)) {
        reported = 1;
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, server_conf,
                     "Server ran out of threads to serve requests. Consider "
                     "raising the ThreadsPerChild setting");
    }
    return (sock);
}

/*
 * child_sub_main() - this is the main loop for the worker threads
 *
 * Each thread runs within this function. They wait within remove_job()
 * for a job to become available, then handle all the requests on that
 * connection until it is closed, then return to remove_job().
 *
 * The worker thread will exit when it removes a job which contains
 * socket number -1. This provides a graceful thread exit, since
 * it will never exit during a connection.
 *
 * This code in this function is basically equivalent to the child_main()
 * from the multi-process (Unix) environment, except that we
 *
 *  - do not call child_init_modules (child init API phase)
 *  - block in remove_job, and when unblocked we have an already
 *    accepted socket, instead of blocking on a mutex or select().
 */

static void child_sub_main(int child_num)
{
    NET_SIZE_T clen;
    struct sockaddr sa_server;
    struct sockaddr sa_client;
    pool *ptrans;
    int requests_this_child = 0;
    int csd = -1;
    int dupped_csd = -1;
    int srv = 0;

#ifdef NETWARE
    TSD* tsd = NULL;

    while(tsd == NULL) {
        tsd = (TSD*) Thread_Data_Area;
        ThreadSwitchWithDelay();
    }
    init_name_space();
#endif
    ap_thread_count++;
    ptrans = ap_make_sub_pool(pconf);

    (void) ap_update_child_status(child_num, SERVER_READY, (request_rec *) NULL);

    /*
     * Setup the jump buffers so that we can return here after a timeout.
     */
#if defined(USE_LONGJMP)
    setjmp(jmpbuffer);
#else
    sigsetjmp(jmpbuffer, 1);
#endif
#if defined(SIGURG)
    signal(SIGURG, timeout);
#endif

#ifdef NETWARE
    tsd = (TSD*) Thread_Data_Area;
#endif

    while (1) {
	BUFF *conn_io;
	request_rec *r;
	
#ifdef NETWARE
        ThreadSwitch();
#endif
	/*
	 * (Re)initialize this child to a pre-connection state.
	 */
	 
	ap_set_callback_and_alarm(NULL, 0); /* Cancel any outstanding alarms */
	timeout_req = NULL;                 /* No request in progress */
	current_conn = NULL;
	ap_clear_pool(ptrans);

	(void) ap_update_child_status(child_num, SERVER_READY,
	                              (request_rec *) NULL);

	/* Get job from the job list. This will block until a job is ready.
	 * If -1 is returned then the main thread wants us to exit.
	 */
	csd = remove_job(csd);
	if (csd == -1)
	    break;		/* time to exit */

	requests_this_child++;

	ap_note_cleanups_for_socket_ex(ptrans, csd, 1);

	/*
	 * We now have a connection, so set it up with the appropriate
	 * socket options, file descriptors, and read/write buffers.
	 */

	clen = sizeof(sa_server);	
	if (getsockname(csd, &sa_server, &clen) < 0) {
	    ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, "getsockname");
	    continue;
	}
	clen = sizeof(sa_client);
	if ((getpeername(csd, &sa_client, &clen)) < 0) {
	    /* get peername will fail if the input isn't a socket */
	    perror("getpeername");
	    memset(&sa_client, '\0', sizeof(sa_client));
	}

	sock_disable_nagle(csd, (struct sockaddr_in *)&sa_client);

	(void) ap_update_child_status(child_num, SERVER_BUSY_READ,
				   (request_rec *) NULL);

	conn_io = ap_bcreate(ptrans, B_RDWR | B_SOCKET);
	dupped_csd = csd;
#if defined(NEED_DUPPED_CSD)
	if ((dupped_csd = dup(csd)) < 0) {
	    ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
			"dup: couldn't duplicate csd");
	    dupped_csd = csd;	/* Oh well... */
	}
	ap_note_cleanups_for_socket_ex(ptrans, dupped_csd, 1);
#endif
	ap_bpushfd(conn_io, csd, dupped_csd);

	current_conn = new_connection(ptrans, server_conf, conn_io,
				          (struct sockaddr_in *) &sa_client,
				          (struct sockaddr_in *) &sa_server,
				          child_num);
				          
	/*
	 * Read and process each request found on our connection
	 * until no requests are left or we decide to close.
	 */
	while ((r = ap_read_request(current_conn)) != NULL) {
            (void) ap_update_child_status(child_num, SERVER_BUSY_WRITE, r);

	    if (r->status == HTTP_OK)
		ap_process_request(r);

	    if (ap_extended_status)
		increment_counts(child_num, r);
	    if (!current_conn->keepalive || current_conn->aborted)
		break;
            /* If the server is shutting down, do not allow anymore requests 
             * to be handled on the keepalive connection. Leave the thread 
             * alive to drain the job queue. This check is particularly 
             * important on the threaded server to allow the process to be 
             * quickly taken down cleanly.
             */
            if (allowed_globals.exit_now)
                break;
	    ap_destroy_pool(r->pool);
	    (void) ap_update_child_status(child_num, SERVER_BUSY_KEEPALIVE,
				       (request_rec *) NULL);

	    ap_sync_scoreboard_image();
	}

	/*
	 * Close the connection, being careful to send out whatever is still
	 * in our buffers.  If possible, try to avoid a hard close until the
	 * client has ACKed our FIN and/or has stopped sending us data.
	 */
	ap_kill_cleanups_for_socket(ptrans, csd);

#ifdef NO_LINGCLOSE
	ap_bclose(conn_io);	/* just close it */
#else
	if (r && r->connection
	    && !r->connection->aborted
	    && r->connection->client
	    && (r->connection->client->fd >= 0)) {

	    lingering_close(r);
	}
	else {
	    ap_bsetflag(conn_io, B_EOUT, 1);
	    ap_bclose(conn_io);
	}
#endif
    }
    ap_destroy_pool(ptrans);
    (void) ap_update_child_status(child_num, SERVER_DEAD, NULL);
    
    ap_thread_count--;
}


#ifdef NETWARE
void child_main(void* child_num_arg)
#else
void child_main(int child_num_arg)
#endif
{
    /*
     * Only reason for this function, is to pass in
     * arguments to child_sub_main() on its stack so
     * that longjump doesn't try to corrupt its local
     * variables and I don't need to make those
     * damn variables static/global
     */
#ifdef NETWARE
    TSD Tsd;
    int *thread_ptr;
    memset(&Tsd, 0, sizeof(TSD));
    thread_ptr = __get_thread_data_area_ptr();
    *thread_ptr = (int) &Tsd;
	child_sub_main((int)child_num_arg);
#else
    child_sub_main(child_num_arg);
#endif
}



void cleanup_thread(thread **handles, int *thread_cnt, int thread_to_clean)
{
    int i;

    free_thread(handles[thread_to_clean]);
    for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++)
	handles[i] = handles[i + 1];
    (*thread_cnt)--;
}
#ifdef WIN32
/*
 * The Win32 call WaitForMultipleObjects will only allow you to wait for 
 * a maximum of MAXIMUM_WAIT_OBJECTS (current 64).  Since the threading 
 * model in the multithreaded version of apache wants to use this call, 
 * we are restricted to a maximum of 64 threads.  This is a simplistic 
 * routine that will increase this size.
 */
static DWORD wait_for_many_objects(DWORD nCount, CONST HANDLE *lpHandles, 
                            DWORD dwSeconds)
{
    time_t tStopTime;
    DWORD dwRet = WAIT_TIMEOUT;
    DWORD dwIndex=0;
    BOOL bFirst = TRUE;
  
    tStopTime = time(NULL) + dwSeconds;
  
    do {
        if (!bFirst)
            Sleep(1000);
        else
            bFirst = FALSE;
          
        for (dwIndex = 0; dwIndex * MAXIMUM_WAIT_OBJECTS < nCount; dwIndex++) {
            dwRet = WaitForMultipleObjects(
                        min(MAXIMUM_WAIT_OBJECTS, 
                            nCount - (dwIndex * MAXIMUM_WAIT_OBJECTS)),
                        lpHandles + (dwIndex * MAXIMUM_WAIT_OBJECTS), 
                        0, 0);
                                           
            if (dwRet != WAIT_TIMEOUT) {                                          
              break;
            }
        }
    } while((time(NULL) < tStopTime) && (dwRet == WAIT_TIMEOUT));
    
    return dwRet;
}
#endif
/*****************************************************************
 * Executive routines.
 */

extern void main_control_server(void *); /* in hellop.c */

event *exit_event;
mutex *start_mutex;

#define MAX_SIGNAL_NAME 30  /* Long enough for apPID_shutdown, where PID is an int */
char signal_name_prefix[MAX_SIGNAL_NAME];
char signal_restart_name[MAX_SIGNAL_NAME]; 
char signal_shutdown_name[MAX_SIGNAL_NAME];

#define MAX_SELECT_ERRORS 100

/*
 * Initialise the signal names, in the global variables signal_name_prefix, 
 * signal_restart_name and signal_shutdown_name.
 */

void setup_signal_names(char *prefix)
{
    ap_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix);    
    ap_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), 
	"%s_shutdown", signal_name_prefix);    
    ap_snprintf(signal_restart_name, sizeof(signal_restart_name), 
	"%s_restart", signal_name_prefix);    

    APD2("signal prefix %s", signal_name_prefix);
}

#ifndef NETWARE
static void setup_inherited_listeners(pool *p)
{
    HANDLE pipe;
    listen_rec *lr;
    int fd;
    WSAPROTOCOL_INFO WSAProtocolInfo;
    DWORD BytesRead;

    /* Setup the listeners */
    listenmaxfd = -1;
    FD_ZERO(&listenfds);

    /* Open the pipe to the parent process to receive the inherited socket
     * data. The sockets have been set to listening in the parent process.
     */
    pipe = GetStdHandle(STD_INPUT_HANDLE);
    for (lr = ap_listeners; lr; lr = lr->next) {
        if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO), 
                      &BytesRead, (LPOVERLAPPED) NULL)) {
            ap_log_error(APLOG_MARK, APLOG_WIN32ERROR|APLOG_CRIT, server_conf,
                         "setup_inherited_listeners: Unable to read socket data from parent");
            signal_parent(0);	/* tell parent to die */
            exit(1);
        }
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf,
                         "BytesRead = %d WSAProtocolInfo = %x20", BytesRead, WSAProtocolInfo);
        fd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
                       &WSAProtocolInfo, 0, 0);
        if (fd == INVALID_SOCKET) {
            ap_log_error(APLOG_MARK, APLOG_WIN32ERROR|APLOG_CRIT, server_conf,
                         "setup_inherited_listeners: WSASocket failed to open the inherited socket.");
            signal_parent(0);	/* tell parent to die */
            exit(1);
        }
        if (fd >= 0) {
            FD_SET(fd, &listenfds);
            if (fd > listenmaxfd)
                listenmaxfd = fd;
        }
        ap_note_cleanups_for_socket_ex(p, fd, 1);
        lr->fd = fd;
        if (lr->next == NULL) {
            /* turn the list into a ring */
            lr->next = ap_listeners;
            break;
        }
    }
    head_listener = ap_listeners;
    close_unused_listeners();
    CloseHandle(pipe);
    return;
}
#endif

/*
 * worker_main() is main loop for the child process. The loop in
 * this function becomes the controlling thread for the actually working
 * threads (which run in a loop in child_sub_main()).
 */
 
#ifdef NETWARE
void worker_main(void)
{
    int nthreads;
    fd_set main_fds;
    int srv;
    int clen;
    int csd;
    struct sockaddr_in sa_client;
    thread **child_handles;
    int rv;
    int i;
    struct timeval tv;
    int my_pid;
    int count_select_errors = 0;
    pool *pchild;
    module **m;    
    listen_rec* lr;
    

    pchild = ap_make_sub_pool(pconf);

    ap_standalone = 1;
    sd = -1;
    nthreads = ap_threads_per_child;
    
    if (nthreads <= 0)
        nthreads = 40;
	    
    my_pid = getpid();

    ++ap_my_generation;

    copy_listeners(pconf);
    ap_restart_time = time(NULL);

    reinit_scoreboard(pconf);
    setup_listeners(pconf);

    if (listenmaxfd == -1) {
        /* Help, no sockets were made, better log something and exit */
        ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, NULL,
                     "No sockets were created for listening");
        
        ap_destroy_pool(pchild);
        cleanup_scoreboard();
        exit(1);
    }
    
    set_signals();

    /* Display listening ports */
    printf("   Listening on port(s):");
    lr = ap_listeners;
    do {
       printf(" %d", ntohs(lr->local_addr.sin_port));
       lr = lr->next;
    } while(lr && lr != ap_listeners);
    
    /* Display dynamic modules loaded */
    printf("\n");    
    for (m = ap_loaded_modules; *m != NULL; m++) {
        if (((module*)*m)->dynamic_load_handle) {
            printf("   Loaded dynamic module %s\n", ap_find_module_name(*m));
        }
    }

    /*
     * - Initialize allowed_globals
     * - Create the thread table
     * - Spawn off threads
     * - Create listen socket set (done above)
     * - loop {
     *       wait for request
     *       create new job
     *   } while (!time to exit)
     * - Close all listeners
     * - Wait for all threads to complete
     * - Exit
     */

    ap_child_init_modules(pconf, server_conf);
    allowed_globals.jobmutex = ap_create_mutex(NULL);
    allowed_globals.jobsemaphore = create_semaphore(0);

    /* spawn off the threads */
    child_handles = (thread *) malloc(nthreads * sizeof(int));
    
    for (i = 0; i < nthreads; i++) {
        child_handles[i] = create_thread((void (*)(void *)) child_main, (void *) i);
    }
    
    if (nthreads > max_daemons_limit) {
        max_daemons_limit = nthreads;
    }

    while (1) {    
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        
        ThreadSwitch();
        
        if (shutdown_pending)
            break;
            
        memcpy(&main_fds, &listenfds, sizeof(fd_set));
        srv = ap_select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv);

        if (srv == 0) {
            count_select_errors = 0;    /* reset count of errors */
            continue;
        }
        else if (srv == SOCKET_ERROR) {
            if (h_errno != WSAEINTR) {
                /* A "real" error occurred, log it and increment the count of
                 * select errors. This count is used to ensure we don't go into
                 * a busy loop of continuous errors.
                 */
                ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, 
                             "select failed with errno %d", h_errno);
                count_select_errors++;
                if (count_select_errors > MAX_SELECT_ERRORS) {
                    ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, server_conf,
                                 "Too many errors in select loop. Child process exiting.");
                    break;
                }
            }
            continue;
        } else {
            listen_rec *lr;
            
            lr = find_ready_listener(&main_fds);
	    
            if (lr != NULL) {
                sd = lr->fd;
            }
        }

        do {
            clen = sizeof(sa_client);
            csd = accept(sd, (struct sockaddr *) &sa_client, &clen);
            
            if (csd == INVALID_SOCKET) {
                csd = -1;
            }
        } while (csd < 0 && h_errno == WSAEINTR);
	
        if (csd == INVALID_SOCKET) {
            if ((h_errno != WSAECONNABORTED) && (h_errno != WSAEWOULDBLOCK)) {
                ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
                             "accept: (client socket) failed with errno = %d",h_errno);
            }
        }
        else {
            u_long one = 0;

            if (soblock(csd) != 0) {
                ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
                             "%d couldn't make socket descriptor (%d) blocking again.", h_errno, csd);
                continue;
            }
            add_job(csd);
        }
    }

    APD2("process PID %d exiting", my_pid);

    /* Get ready to shutdown and exit */
    allowed_globals.exit_now = 1;
    
    for (i = 0; i < nthreads; i++) {
        add_job(-1);
    }

    APD2("process PID %d waiting for worker threads to exit", my_pid);
    while(ap_thread_count)
        ThreadSwitch();
    
    destroy_semaphore(allowed_globals.jobsemaphore);
    ap_destroy_mutex(allowed_globals.jobmutex);
    
    ap_child_exit_modules(pconf, server_conf);
    ap_destroy_pool(pchild);
    free(child_handles);
    cleanup_scoreboard();

    APD2("process PID %d exited", my_pid);
    clean_parent_exit(0);

}
#else
void worker_main(void)
{
    int nthreads;
    fd_set main_fds;
    int srv;
    int clen;
    int csd;
    struct sockaddr_in sa_client;
    int total_jobs = 0;
    thread **child_handles;
    int rv;
    time_t end_time;
    int i;
    struct timeval tv;
    int wait_time = 1;
    int max_jobs_per_exe;
    int max_jobs_after_exit_request;
    HANDLE hObjects[2];

    int count_select_errors = 0;
    pool *pchild;

    pchild = ap_make_sub_pool(pconf);

    ap_standalone = 1;
    sd = -1;
    nthreads = ap_threads_per_child;
    max_jobs_after_exit_request = ap_excess_requests_per_child;
    max_jobs_per_exe = ap_max_requests_per_child;
    if (nthreads <= 0)
	nthreads = 40;
    if (max_jobs_per_exe <= 0)
	max_jobs_per_exe = 0;
    if (max_jobs_after_exit_request <= 0)
	max_jobs_after_exit_request = max_jobs_per_exe / 10;

    if (!one_process)
	detach();

    my_pid = getpid();

    ++ap_my_generation;

    copy_listeners(pconf);
    ap_restart_time = time(NULL);

    reinit_scoreboard(pconf);
    
    /*
     * Wait until we have permission to start accepting connections.
     * start_mutex is used to ensure that only one child ever
     * goes into the listen/accept loop at once. Also wait on exit_event,
     * in case we (this child) is told to die before we get a chance to
     * serve any requests.
     */
    hObjects[0] = (HANDLE)start_mutex;
    hObjects[1] = (HANDLE)exit_event;
    rv = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE);
    if (rv == WAIT_FAILED) {
	ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_WIN32ERROR, server_conf,
                     "Waiting for start_mutex or exit_event -- process will exit");

	ap_destroy_pool(pchild);
	cleanup_scoreboard();
	exit(1);
    }
    if (rv == WAIT_OBJECT_0 + 1) {
	/* exit event signalled - exit now */
	ap_destroy_pool(pchild);
	cleanup_scoreboard();
	exit(0);
    }
    /* start_mutex obtained, continue into the select() loop */
    if (one_process) {
        setup_listeners(pconf);
    } else {
        /* Get listeners from the parent process */
        setup_inherited_listeners(pconf);
    }

    if (listenmaxfd == -1) {
	/* Help, no sockets were made, better log something and exit */
	ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, NULL,
		    "No sockets were created for listening");

	signal_parent(0);	/* tell parent to die */

	ap_destroy_pool(pchild);
	cleanup_scoreboard();
	exit(1);
    }
    set_signals();

    /*
     * - Initialize allowed_globals
     * - Create the thread table
     * - Spawn off threads
     * - Create listen socket set (done above)
     * - loop {
     *       wait for request
     *       create new job
     *   } while (!time to exit)
     * - Close all listeners
     * - Wait for all threads to complete
     * - Exit
     */

    ap_child_init_modules(pconf, server_conf);

    allowed_globals.jobsemaphore = create_semaphore(0);
    allowed_globals.jobmutex = ap_create_mutex(NULL);

    /* spawn off the threads */
    child_handles = (thread *) alloca(nthreads * sizeof(int));
    for (i = 0; i < nthreads; i++) {
	child_handles[i] = create_thread((void (*)(void *)) child_main, (void *) i);
        if (child_handles[i] == 0) {
            ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, 
                         "create_thread rc = %d", errno);
        }
    }
    if (nthreads > max_daemons_limit) {
	max_daemons_limit = nthreads;
    }

    while (1) {
        if (max_jobs_per_exe && (total_jobs > max_jobs_per_exe)) {
            /* Reached MaxRequestsPerChild. Stop accepting new connections
             * and signal the parent to start a new child process.
             */
            ap_start_restart(1);
            break;
	}
        /* Always check for the exit event being signaled.
         */
        rv = WaitForSingleObject(exit_event, 0);
        ap_assert((rv == WAIT_TIMEOUT) || (rv == WAIT_OBJECT_0));
        if (rv == WAIT_OBJECT_0) {
            APD1("child: exit event signalled, exiting");
            break;
        }

	tv.tv_sec = wait_time;
	tv.tv_usec = 0;
	memcpy(&main_fds, &listenfds, sizeof(fd_set));

	srv = ap_select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv);

        if (srv == 0) {
            count_select_errors = 0;    /* reset count of errors */            
            continue;
        }
	else if (srv == SOCKET_ERROR) {
            if (h_errno != WSAEINTR) {
                /* A "real" error occurred, log it and increment the count of
                 * select errors. This count is used to ensure we don't go into
                 * a busy loop of continuous errors.
                 */
                ap_log_error(APLOG_MARK, APLOG_WARNING, server_conf, 
                             "select failed with errno %d", h_errno);
                count_select_errors++;
                if (count_select_errors > MAX_SELECT_ERRORS) {
                    ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, server_conf,
                                 "Too many errors in select loop. Child process exiting.");
                    break;
                }
            }
            continue;
	} else {
	    listen_rec *lr;

	    lr = find_ready_listener(&main_fds);
	    if (lr != NULL) {
		sd = lr->fd;
	    }
	}
	do {
	    clen = sizeof(sa_client);
	    csd = accept(sd, (struct sockaddr *) &sa_client, &clen);
	    if (csd == INVALID_SOCKET) {
		csd = -1;
	    }
	} while (csd < 0 && h_errno == WSAEINTR);

	if (csd < 0) {
	    if (h_errno != WSAECONNABORTED) {
		ap_log_error(APLOG_MARK, APLOG_ERR, server_conf,
                             "accept: (client socket) failed with errno = %d",h_errno);
            }
	}
	else {
	    add_job(csd);
	    total_jobs++;
	}
    }

    APD2("process PID %d exiting", my_pid);

    /* Get ready to shutdown and exit */
    allowed_globals.exit_now = 1;
    ap_release_mutex(start_mutex);

#ifdef UNGRACEFUL_RESTART
    SetEvent(allowed_globals.thread_exit_event);
#else
    for (i = 0; i < nthreads; i++) {
	add_job(-1);
    }
#endif

    APD2("process PID %d waiting for worker threads to exit", my_pid);
    /* Wait for all your children */
    end_time = time(NULL) + 180;
    while (nthreads) {
        rv = wait_for_many_objects(nthreads, child_handles, 
                                   end_time - time(NULL));
	if (rv != WAIT_TIMEOUT) {
	    rv = rv - WAIT_OBJECT_0;
	    ap_assert((rv >= 0) && (rv < nthreads));
	    cleanup_thread(child_handles, &nthreads, rv);
	    continue;
	}
	break;
    }

    APD2("process PID %d killing remaining worker threads", my_pid);
    for (i = 0; i < nthreads; i++) {
	kill_thread(child_handles[i]);
	free_thread(child_handles[i]);
    }
#ifdef UNGRACEFUL_RESTART
    ap_assert(CloseHandle(allowed_globals.thread_exit_event));
#endif
    destroy_semaphore(allowed_globals.jobsemaphore);
    ap_destroy_mutex(allowed_globals.jobmutex);

    ap_child_exit_modules(pconf, server_conf);
    ap_destroy_pool(pchild);

    cleanup_scoreboard();

    APD2("process PID %d exited", my_pid);
    clean_parent_exit(0);
}				/* standalone_main */

/*
 * Spawn a child Apache process. The child process has the command line arguments from
 * argc and argv[], plus a -Z argument giving the name of an event. The child should
 * open and poll or wait on this event. When it is signalled, the child should die.
 * prefix is a prefix string for the event name.
 * 
 * The child_num argument on entry contains a serial number for this child (used to create
 * a unique event name). On exit, this number will have been incremented by one, ready
 * for the next call. 
 *
 * On exit, the value pointed to be *ev will contain the event created
 * to signal the new child process.
 *
 * The return value is the handle to the child process if successful, else -1. If -1 is
 * returned the error will already have been logged by ap_log_error().
 */

/**********************************************************************
 * master_main - this is the parent (main) process. We create a
 * child process to do the work, then sit around waiting for either
 * the child to exit, or a restart or exit signal. If the child dies,
 * we just respawn a new one. If we have a shutdown or graceful restart,
 * tell the child to die when it is ready. If it is a non-graceful
 * restart, force the child to die immediately.
 **********************************************************************/

#define MAX_PROCESSES 50 /* must be < MAX_WAIT_OBJECTS-1 */

static void cleanup_process(HANDLE *handles, HANDLE *events, int position, int *processes)
{
    int i;
    int handle = 0;

    CloseHandle(handles[position]);
    CloseHandle(events[position]);

    handle = (int)handles[position];

    for (i = position; i < (*processes)-1; i++) {
	handles[i] = handles[i + 1];
	events[i] = events[i + 1];
    }
    (*processes)--;

    APD4("cleanup_processes: removed child in slot %d handle %d, max=%d", position, handle, *processes);
}

static int create_process(pool *p, HANDLE *handles, HANDLE *events, 
                          int *processes, int *child_num, char *kill_event_name, int argc, char **argv)
{

    int rv, i;
    HANDLE kill_event;
    char buf[1024];
    char exit_event_name[40]; /* apPID_C# */
    char *pCommand;

    STARTUPINFO si;           /* Filled in prior to call to CreateProcess */
    PROCESS_INFORMATION pi;   /* filled in on call to CreateProces */
    LPWSAPROTOCOL_INFO  lpWSAProtocolInfo;
    listen_rec *lr;
    DWORD BytesWritten;
    HANDLE hPipeRead = NULL;
    HANDLE hPipeWrite = NULL;
    HANDLE hPipeWriteDup;
    HANDLE hNullOutput = NULL;
    HANDLE hShareError = NULL;
    HANDLE hCurrentProcess;
    SECURITY_ATTRIBUTES sa = {0};  

    sa.nLength = sizeof(sa);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;

    /* Build the command line. Should look something like this:
     * C:/apache/bin/apache.exe -Z exit_event -f ap_server_confname 
     * First, get the path to the executable...
     */
    rv = GetModuleFileName(NULL, buf, sizeof(buf));
    if (rv == sizeof(buf)) {
        ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                     "Parent: Path to Apache process too long");
        return -1;
    } else if (rv == 0) {
        ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                     "Parent: GetModuleFileName() returned NULL for current process.");
        return -1;
    }
    
    /* Create the exit event (apPID_C#). Parent signals this event to tell the
     * child to exit 
     */
    ap_snprintf(exit_event_name, sizeof(exit_event_name), "%s_C%d", kill_event_name, ++(*child_num));
    kill_event = CreateEvent(NULL, TRUE, FALSE, exit_event_name);
    if (!kill_event) {
        ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                     "Parent: Could not create exit event for child process");
        return -1;
    }
    
	/* service children must be created with the -z option,
	 * while console mode (interactive apache) children are created
	 * with the -Z option
	 */
    pCommand = ap_psprintf(p, "\"%s\" -%c %s -f \"%s\"", buf, 
		                   isProcessService() ? 'z' : 'Z',
		                   exit_event_name, ap_server_confname);  

    for (i = 1; i < argc; i++) {
        if ((argv[i][0] == '-') && ((argv[i][1] == 'k') || (argv[i][1] == 'n')))
            ++i;
        else
            pCommand = ap_pstrcat(p, pCommand, " \"", argv[i], "\"", NULL);
    }

    /* Create a pipe to send socket info to the child */
    if (!CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0)) {
        ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                     "Parent: Unable to create pipe to child process.\n");
        return -1;
    }

    /* Open a null handle to soak info from the child */
    hNullOutput = CreateFile("nul", GENERIC_READ | GENERIC_WRITE, 
                             FILE_SHARE_READ | FILE_SHARE_WRITE, 
                             &sa, OPEN_EXISTING, 0, NULL);
    if (hNullOutput == INVALID_HANDLE_VALUE) {
        ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                     "Parent: Unable to create null output pipe for child process.\n");
        return -1;
    }

    /* Child's initial stderr -> our main server error log (or, failing that, stderr) */
    if (server_conf->error_log) {
        hShareError = (HANDLE)_get_osfhandle(fileno(server_conf->error_log));
        if (hShareError == INVALID_HANDLE_VALUE) {
            hShareError = GetStdHandle(STD_ERROR_HANDLE);
        }
    }

    hCurrentProcess = GetCurrentProcess();
    if (DuplicateHandle(hCurrentProcess, hPipeWrite, hCurrentProcess,
                        &hPipeWriteDup, 0, FALSE, DUPLICATE_SAME_ACCESS))
    {
        CloseHandle(hPipeWrite);
        hPipeWrite = hPipeWriteDup;
    }

    /* Give the read in of the pipe (hPipeRead) to the child as stdin. The 
     * parent will write the socket data to the child on this pipe.
     */
    memset(&si, 0, sizeof(si));
    memset(&pi, 0, sizeof(pi));
    si.cb = sizeof(si);
    si.dwFlags     = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    si.hStdInput   = hPipeRead;
    si.hStdOutput  = hNullOutput;
    si.hStdError   = hShareError;

    if (!CreateProcess(NULL, pCommand, NULL, NULL, 
                       TRUE,      /* Inherit handles */
                       0,         /* Creation flags */
                       NULL, NULL,
                       &si, &pi)) {
        ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                     "Parent: Not able to create the child process.");
        /*
         * We must close the handles to the new process and its main thread
         * to prevent handle and memory leaks.
         */ 
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        CloseHandle(hPipeRead);
        CloseHandle(hPipeWrite);        
        CloseHandle(hNullOutput);

        return -1;
    }
    else {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, server_conf,
                     "Parent: Created child process %d", pi.dwProcessId);

        /* Assume the child process lives. Update the process and event tables */
        handles[*processes] = pi.hProcess;
        events[*processes] = kill_event;
        (*processes)++;

        /* We never store the thread's handle, so close it now. */
        CloseHandle(pi.hThread);

        /* Run the chain of open sockets. For each socket, duplicate it 
         * for the target process then send the WSAPROTOCOL_INFO 
         * (returned by dup socket) to the child */
        lr = ap_listeners;
        while (lr != NULL) {
            lpWSAProtocolInfo = ap_pcalloc(p, sizeof(WSAPROTOCOL_INFO));
            ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, server_conf,
                         "Parent: Duplicating socket %d and sending it to child process %d", lr->fd, pi.dwProcessId);
            if (WSADuplicateSocket(lr->fd, 
                                   pi.dwProcessId,
                                   lpWSAProtocolInfo) == SOCKET_ERROR) {
                ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                             "Parent: WSADuplicateSocket failed for socket %d.", lr->fd );
                return -1;
            }

            if (!WriteFile(hPipeWrite, lpWSAProtocolInfo, (DWORD) sizeof(WSAPROTOCOL_INFO),
                           &BytesWritten,
                           (LPOVERLAPPED) NULL)) {
                ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, server_conf,
                             "Parent: Unable to write duplicated socket %d to the child.", lr->fd );
                return -1;
            }

            lr = lr->next;
            if (lr == ap_listeners)
                break;
        }
    }
    CloseHandle(hPipeRead);
    CloseHandle(hPipeWrite);        
    CloseHandle(hNullOutput);

    return 0;
}

/* To share the semaphores with other processes, we need a NULL ACL
 * Code from MS KB Q106387
 */

static PSECURITY_ATTRIBUTES GetNullACL()
{
    PSECURITY_DESCRIPTOR pSD;
    PSECURITY_ATTRIBUTES sa;

    sa  = (PSECURITY_ATTRIBUTES) LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
    pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
					    SECURITY_DESCRIPTOR_MIN_LENGTH);
    if (pSD == NULL || sa == NULL) {
        return NULL;
    }
    /*
     * Win98 returns nonzero on failure; check LastError to make sure.
     */
    SetLastError(0);
    if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)
	|| GetLastError()) {
        LocalFree( pSD );
        LocalFree( sa );
        return NULL;
    }
    if (!SetSecurityDescriptorDacl(pSD, TRUE, (PACL) NULL, FALSE)
	|| GetLastError()) {
        LocalFree( pSD );
        LocalFree( sa );
        return NULL;
    }
    sa->nLength = sizeof(sa);
    sa->lpSecurityDescriptor = pSD;
    sa->bInheritHandle = TRUE;
    return sa;
}


static void CleanNullACL( void *sa ) {
    if( sa ) {
        LocalFree( ((PSECURITY_ATTRIBUTES)sa)->lpSecurityDescriptor);
        LocalFree( sa );
    }
}

int master_main(int argc, char **argv)
{
    /* returns NULL if invalid (Win95?) */
    PSECURITY_ATTRIBUTES sa = GetNullACL();
    int nchild = ap_daemons_to_start;
    int child_num = 0;
    int rv, cld;
    char signal_prefix_string[100];
    int i;
    time_t tmstart;
    HANDLE signal_shutdown_event;	/* used to signal shutdown to parent */
    HANDLE signal_restart_event;	/* used to signal a restart to parent */
    HANDLE process_handles[MAX_PROCESSES];
    HANDLE process_kill_events[MAX_PROCESSES];
    int current_live_processes = 0; /* number of child process we know about */
    int processes_to_create = 0;    /* number of child processes to create */
    pool *pparent = NULL;  /* pool for the parent process. Cleaned on each restart */

    nchild = 1;	    /* only allowed one child process for current generation */
    processes_to_create = nchild;

    is_graceful = 0;

    ap_snprintf(signal_prefix_string, sizeof(signal_prefix_string),
	        "ap%d", getpid());
    setup_signal_names(signal_prefix_string);

    /* Create shutdown event, apPID_shutdown, where PID is the parent 
     * Apache process ID. Shutdown is signaled by 'apache -k shutdown'.
     */
    signal_shutdown_event = CreateEvent(sa, TRUE, FALSE, signal_shutdown_name);
    if (!signal_shutdown_event) {
	ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf,
		    "master_main: Cannot create shutdown event %s", signal_shutdown_name);
        CleanNullACL((void *)sa);
	exit(1);
    }

    /* Create restart event, apPID_restart, where PID is the parent 
     * Apache process ID. Restart is signaled by 'apache -k restart'.
     */
    signal_restart_event = CreateEvent(sa, TRUE, FALSE, signal_restart_name);
    if (!signal_restart_event) {
	CloseHandle(signal_shutdown_event);
	ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf,
		    "master_main: Cannot create restart event %s", signal_restart_name);
        CleanNullACL((void *)sa);
	exit(1);
    }
    CleanNullACL((void *)sa);

    /* Create the start mutex, apPID, where PID is the parent Apache process ID.
     * Ths start mutex is used during a restart to prevent more than one 
     * child process from entering the accept loop at once.
     */
    start_mutex = ap_create_mutex(signal_prefix_string);
    restart_pending = shutdown_pending = 0;

    do { /* restart-pending */
	if (!is_graceful) {
	    ap_restart_time = time(NULL);
	}
        copy_listeners(pconf);
	ap_clear_pool(pconf);
	pparent = ap_make_sub_pool(pconf);

	server_conf = ap_read_config(pconf, pparent, ap_server_confname);
        setup_listeners(pconf);
	ap_clear_pool(plog);
	ap_open_logs(server_conf, plog);
	ap_set_version();
	ap_init_modules(pconf, server_conf);
	version_locked++;
        service_set_status(SERVICE_START_PENDING);
        /* Create child processes */
        while (processes_to_create--) {
            if (create_process(pconf, process_handles, process_kill_events, 
                               &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. 
             * Signal the child to shutdown and start a new child process.
             * The restart event can be signaled by a command line restart or
             * by the child process when it handles MaxRequestPerChild connections.
             */
            int children_to_kill = current_live_processes;
            restart_pending = 1;
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, 
                         "master_main: Restart event signaled. Doing a graceful restart.");
            if (ResetEvent(signal_restart_event) == 0) {
                ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf,
                             "master_main: ResetEvent(signal_restart_event) failed.");
            }
            /* Signal each child process to die */
	    for (i = 0; i < children_to_kill; i++) {
		APD3("master_main: signalling child #%d handle %d to die", i, process_handles[i]);
		if (SetEvent(process_kill_events[i]) == 0)
		    ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf,
                                 "master_main: SetEvent for child process in slot #%d failed", i);
                /* Remove the process (and event) from the process table */
                cleanup_process(process_handles, process_kill_events, i, &current_live_processes);
	    }
	    processes_to_create = 1;
            ++ap_my_generation;
            continue;
        } else {
            /* The child process exited premeturely because of a fatal error condition
             * (eg, seg fault). Cleanup and restart the child process.
             */
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, server_conf, 
                         "master_main: Child processed exited prematurely. Restarting the child process.");
	    ap_assert(cld < current_live_processes);
	    cleanup_process(process_handles, process_kill_events, cld, &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);
    return (0);
}
#endif

/*
 * Send signal to a running Apache. On entry signal should contain
 * either "shutdown" or "restart"
 */

int send_signal(pool *p, char *signal)
{
    char prefix[20];
    FILE *fp;
    int nread;
    char *fname;
    int end;

    fname = ap_server_root_relative (p, ap_pid_fname);

    fp = fopen(fname, "r");
    if (!fp) {
	printf("Cannot read apache PID file %s\n", fname);
        return FALSE;
    }
    prefix[0] = 'a';
    prefix[1] = 'p';

    nread = fread(prefix+2, 1, sizeof(prefix)-3, fp);
    if (nread == 0) {
	fclose(fp);
	printf("PID file %s was empty\n", fname);
        return FALSE;
    }
    fclose(fp);

    /* Terminate the prefix string */
    end = 2 + nread - 1;
    while (end > 0 && (prefix[end] == '\r' || prefix[end] == '\n'))
	end--;
    prefix[end + 1] = '\0';

    setup_signal_names(prefix);

    if (!strcasecmp(signal, "shutdown"))
	ap_start_shutdown();
    else if (!strcasecmp(signal, "restart"))
	ap_start_restart(1);
    else {
	printf("Unknown signal name \"%s\". Use either shutdown or restart.\n",
	    signal);
        return FALSE;
    }
    return TRUE;
}

void post_parse_init()
{
    ap_set_version();
    ap_init_modules(pconf, server_conf);
    ap_suexec_enabled = init_suexec();
    version_locked++;
    ap_open_logs(server_conf, plog);
    set_group_privs();
}


#ifdef NETWARE
extern char *optarg;

void signal_handler(int sig)
{
    switch (sig) {
    case SIGTERM:
        shutdown_pending = 1;

        while(!ap_main_finished)
            ThreadSwitchWithDelay();

        break;
    }
    return;
}
#endif

#if defined(NETWARE)
int apache_main(int argc, char *argv[])
#elif defined(WIN32)
 __declspec(dllexport)
int apache_main(int argc, char *argv[])
#else
int REALMAIN(int argc, char *argv[]) 
#endif
{
    int c;
    int child = 0;
    char *cp;
    char *s;
    int conf_specified = 0;
    
#ifdef WIN32
    jmp_buf reparse_args;
    char *service_name = NULL;
    int install = 0;
    int reparsed = 0;
    int is_child_of_service = 0;
    char *signal_to_send = NULL;

    /* Service application under WinNT the first time through only...
     * service_main immediately resets real_exit_code to zero
     */
    if (real_exit_code && isWindowsNT()) 
    {
        if (((argc == 1) && isProcessService()) 
            || ((argc == 2) && !strcmp(argv[1], "--ntservice")))
        {
            service_main(apache_main, argc, argv);
            /* this was the end of the service control thread... 
             * cleanups already ran when second thread of apache_main
             * terminated, so simply...
             */
            exit(0);
        }
    }

    /* This behavior is voided by setting real_exit_code to 0 */
    atexit(hold_console_open_on_error);
#endif

#ifdef NETWARE
    int currentScreen = GetCurrentScreen();
    /* If top_module is not NULL then APACHEC was not exited cleanly
     * and is in a bad state.  Simply clean up and exit.
     */
    check_clean_load (top_module);
    init_name_space();
    signal(SIGTERM, signal_handler);
    atexit(clean_shutdown_on_exit);
    init_tsd();
#endif

    /* Console application or a child process. */

    if ((s = strrchr(argv[0], PATHSEPARATOR)) != NULL) {
        ap_server_argv0 = ++s;
    }
    else {
        ap_server_argv0 = argv[0];
    }

    common_init();
    ap_setup_prelinked_modules();

    /* initialize ap_server_root to the directory of the executable, in case
     * the user chooses a relative path for the -d serverroot arg a bit later
     */

#ifdef NETWARE
    if(!*ap_server_root) {
        ap_cpystrn(ap_server_root, bslash2slash(remove_filename(argv[0])),
                   sizeof(ap_server_root));
    }
#endif

#ifdef WIN32
    if(!*ap_server_root) {
        if (GetModuleFileName(NULL, ap_server_root, sizeof(ap_server_root))) {
            ap_cpystrn(ap_server_root,
                       ap_os_canonical_filename(pcommands, ap_server_root), 
                       sizeof(ap_server_root));
            if (ap_os_is_path_absolute(ap_server_root) 
                    && strchr(ap_server_root, '/'))
                *strrchr(ap_server_root, '/') = '\0';
            else 
                *ap_server_root = '\0';
        }
    }
#endif

    /* Fallback position if argv[0] wasn't deciphered
     */
    if (!*ap_server_root)
        ap_cpystrn(ap_server_root, HTTPD_ROOT, sizeof(ap_server_root));

    chdir (ap_server_root);

#ifdef WIN32
    /* If this is a service, we will need to fall back here and 
     * reparse the entire options list.
     */
    if (setjmp(reparse_args)) {
        /* Reset and reparse the command line */
        ap_server_pre_read_config  = ap_make_array(pcommands, 1, sizeof(char *));
        ap_server_post_read_config = ap_make_array(pcommands, 1, sizeof(char *));
        ap_server_config_defines   = ap_make_array(pcommands, 1, sizeof(char *));

        /* Reset optreset and optind to allow getopt to work correctly
         * the second time around, and assure we never come back here.
         */
        optreset = 1;
        optind = 1;
        reparsed = 1;
    }

    while ((c = getopt(argc, argv, "D:C:c:Xd:f:vVlLz:Z:wiuStThk:n:W:")) != -1) {
#else /* !WIN32 */
    while ((c = getopt(argc, argv, "D:C:c:Xd:Ff:vVlLesStTh")) != -1) {
#endif
        char **new;
	switch (c) {
	case 'c':
	    new = (char **)ap_push_array(ap_server_post_read_config);
	    *new = ap_pstrdup(pcommands, optarg);
	    break;
	case 'C':
	    new = (char **)ap_push_array(ap_server_pre_read_config);
	    *new = ap_pstrdup(pcommands, optarg);
	    break;
	case 'D':
	    new = (char **)ap_push_array(ap_server_config_defines);
	    *new = ap_pstrdup(pcommands, optarg);
	    break;
#ifdef WIN32
        /* Shortcuts; include the -w option to hold the window open on error.
         * This must not be toggled once we reset real_exit_code to 0!
         */
        case 'w':
            if (real_exit_code)
                real_exit_code = 2;
            break;
	/* service children must be created with the -z option,
	 * while console mode (interactive apache) children are created
	 * with the -Z option
	 */
        case 'z':
            is_child_of_service = 1;
        case 'Z':
            /* Prevent holding open the (nonexistant) console */
            real_exit_code = 0;
	    exit_event = open_event(optarg);
	    APD2("child: opened process event %s", optarg);
	    cp = strchr(optarg, '_');
	    ap_assert(cp);
	    *cp = 0;
	    setup_signal_names(optarg);
	    start_mutex = ap_open_mutex(signal_name_prefix);
	    ap_assert(start_mutex);
	    child = 1;
	    break;
        case 'n':
            service_name = ap_pstrdup(pcommands, optarg);
            break;
	case 'i':
            install = 2;
	    break;
	case 'u':
            install = -1;
	    break;
	case 'k':
            if (!strcasecmp(optarg, "stop"))
                signal_to_send = "shutdown";
            else if (!strcasecmp(optarg, "install"))
                install = 2;
            else if (!strcasecmp(optarg, "config"))
                install = 1;
            else if (!strcasecmp(optarg, "uninstall"))
                install = -1;
            else
                signal_to_send = optarg;
	    break;
        case 'W':
            /* -With a dependent service */
            if (install < 1) {
	        fprintf(stderr, "%s: invalid option: -W %s ignored\n"
                        "\t-W only modifies -k install or -k config\n",
                        argv[0], optarg);
            }
            else if (!isWindowsNT()) {
                fprintf(stderr, "%s: invalid option: -W %s ignored\n"
                        "\t-W is only supported for Windows NT/2000\n",
                        argv[0], optarg);
            }
            break;
#endif /* WIN32 */
#ifdef NETWARE
        case 'e':
            {
                int screenHandle;  

                /* Get a screen handle for the console screen. */
                if ((screenHandle = CreateScreen("System Console", 0)) != NULL)
                {
                    SetAutoScreenDestructionMode(1); 
                    SetCurrentScreen(screenHandle);  /* switch to console screen I/O */
                }
            }
            break;
        case 's':
            if (DestroyScreen(GetCurrentScreen()) == 0)
            {
                int screenHandle;  

                /* Create a screen handle for the console screen, 
                even though the console screen exists. */
                if ((screenHandle = CreateScreen("System Console", 0)) != NULL)
                {
                    SetCurrentScreen(screenHandle);  /* switch to console screen I/O */
                    currentScreen = GetCurrentScreen();
                }
            }
            break;
#endif
	case 'S':
	    ap_dump_settings = 1;
	    break;
	case 'd':
            optarg = ap_os_canonical_filename(pcommands, optarg);
            if (!ap_os_is_path_absolute(optarg)) {
	        optarg = ap_pstrcat(pcommands, ap_server_root, "/", 
                                    optarg, NULL);
            }
            ap_cpystrn(ap_server_root, optarg, sizeof(ap_server_root));
            ap_getparents(ap_server_root);
            ap_no2slash(ap_server_root);
            if (ap_server_root[0] 
                    && ap_server_root[strlen(ap_server_root) - 1] == '/')
                ap_server_root[strlen(ap_server_root) - 1] = '\0';
	    break;
#ifndef WIN32
	case 'F':
	    do_detach = 0;
	    break;
#endif
	case 'f':
            ap_cpystrn(ap_server_confname,
                       ap_os_canonical_filename(pcommands, optarg),
                       sizeof(ap_server_confname));
            conf_specified = 1;
	    break;
	case 'v':
	    ap_set_version();
	    printf("Server version: %s\n", ap_get_server_version());
	    printf("Server built:   %s\n", ap_get_server_built());
#ifdef WIN32
            clean_parent_exit(1);
#else
            clean_parent_exit(0);
#endif

        case 'V':
	    ap_set_version();
	    show_compile_settings();
#ifdef WIN32
            clean_parent_exit(1);
#else
            clean_parent_exit(0);
#endif

	case 'l':
	    ap_show_modules();
#ifdef WIN32
            clean_parent_exit(1);
#else
            clean_parent_exit(0);
#endif

	case 'L':
	    ap_show_directives();
#ifdef WIN32
            clean_parent_exit(1);
#else
            clean_parent_exit(0);
#endif

	case 'X':
	    ++one_process;	/* Weird debugging mode. */
	    break;
	case 't':
	    ap_configtestonly = 1;
	    ap_docrootcheck = 1;
	    break;
	case 'T':
	    ap_configtestonly = 1;
	    ap_docrootcheck = 0;
	    break;
	case 'h':
	    usage(ap_server_argv0);
	case '?':
	    usage(ap_server_argv0);
        }   /* switch */
#ifdef NETWARE
        ThreadSwitch();
#endif
    }       /* while  */

#ifdef WIN32

    if (!service_name && install) {
        service_name = DEFAULTSERVICENAME;
    }

    if (service_name) {
        service_name = get_display_name(service_name);
    }

    if (service_name && isValidService(service_name)) 
    {
        if (install == 2) {
            ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL,
                         "Service \"%s\" is already installed!", service_name);
            clean_parent_exit(1);
        }
        /* Don't proceed if we are configuring, uninstalling 
         * or already merged and reparsed the service args
         */
        if (!install && !reparsed)
        {
            int svcargc;
            char **newargv, **svcargv;
            if (ap_configtestonly)
                fprintf(stderr, "Default command options for service %s:\n", 
                        service_name);
                    
            /* Merge the service's default args */
            if (ap_registry_get_service_args(pcommands, &svcargc, &svcargv, 
                                             service_name) > 0) {
                newargv = (char**)malloc((svcargc + argc + 1) * sizeof(char*));
                newargv[0] = argv[0];  /* The true executable name */
                memcpy(newargv + 1, svcargv, svcargc * sizeof(char*)); 
                memcpy(newargv + 1 + svcargc, argv + 1, 
                       (argc - 1) * sizeof(char*));
                argc += svcargc; /* Add the startup options args */
                argv = newargv;
                argv[argc] = NULL;

                if (ap_configtestonly) {
                    while (svcargc-- > 0) {
                        if ((**svcargv == '-') && strchr("dfDCc", svcargv[0][1])
                            && svcargc) {
                            fprintf(stderr, "    %s %s\n", 
                                    *svcargv, *(svcargv + 1));
                            svcargv += 2; --svcargc;
                        }
                        else
                            fprintf(stderr, "    %s\n", *(svcargv++));
                    }
                }
                /* Run through the command line args all over again */
                longjmp(reparse_args, 1);
            }
            else if (ap_configtestonly)
                fprintf (stderr, "    (none)\n");
        }
    }
    else if (service_name && (install <= 1))
    {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, NULL,
                     "Service \"%s\" is not installed!", service_name);
        clean_parent_exit(1);
    }
#endif

    /* ServerRoot/ServerConfFile are found in this order:
     * (1) serverroot set to Apache.exe's path, or HTTPD_ROOT if unparsable
     * (2) arguments are grabbed for the -n named service, if given
     * (3) the -d argument is taken from the given command line
     * (4) the -d argument is taken from the service's default args
     * (5) the -f argument is taken from the given command line
     * (6) the -f argument is taken from the service's default args
     * (7) if -f is omitted, then initialized to SERVER_CONFIG_FILE
     * (8) if ap_server_confname is not absolute, then merge it to serverroot
     */
    
    if (!conf_specified)
        ap_cpystrn(ap_server_confname, SERVER_CONFIG_FILE, sizeof(ap_server_confname));

    if (!ap_os_is_path_absolute(ap_server_confname))
        ap_cpystrn(ap_server_confname,
                   ap_server_root_relative(pcommands, ap_server_confname),
                   sizeof(ap_server_confname));
    ap_getparents(ap_server_confname);
    ap_no2slash(ap_server_confname);
    
#ifdef WIN32
    /* Read the conf now unless we are uninstalling the service,
     * or shutting down a running service 
     * (but do read the conf for the pidfile if we shutdown the console)
     */
    if ((install >= 0) && (!service_name || !signal_to_send 
                            || strcasecmp(signal_to_send,"shutdown"))) {
        server_conf = ap_read_config(pconf, ptrans, ap_server_confname);
    }

    if (install) {
        if (install > 0) 
            InstallService(pconf, service_name, argc, argv, install == 1);
        else
            RemoveService(service_name);
        clean_parent_exit(0);
    }

    /* All NT signals, and all but the 9x start signal are handled entirely.
     * Die if we failed, are on NT, or are not "start"ing the service
     */
    if (service_name && signal_to_send) {
        if (send_signal_to_service(service_name, signal_to_send, argc, argv))
            clean_parent_exit(0);
        if (isWindowsNT() || strcasecmp(signal_to_send, "start"))
            clean_parent_exit(1);
        /* Still here?  Then we are hanging around to detach the console 
         * and use this process as the Windows 9x service.
         */
    }
#else /* ndef WIN32 */
    server_conf = ap_read_config(pconf, ptrans, ap_server_confname);
#endif

    if (ap_configtestonly) {
        fprintf(stderr, "%s: Syntax OK\n", ap_server_root_relative(pcommands, ap_server_confname));
#ifdef WIN32
        clean_parent_exit(1);
#else
        clean_parent_exit(0);
#endif
    }

    if (ap_dump_settings) {
#ifdef WIN32
        clean_parent_exit(1);
#else
        clean_parent_exit(0);
#endif
    }

#ifdef WIN32
    /* Non-service Signals.  (Ignore -k start for now [with or without -n arg]) */
    if (signal_to_send && strcasecmp(signal_to_send, "start")) {
        send_signal(pconf, signal_to_send);
        clean_parent_exit(0);
    }
#endif

#ifndef NETWARE
    if (!child && !ap_dump_settings) { 
        ap_log_pid(pconf, ap_pid_fname);
    }
#endif

    post_parse_init();

#if defined(OS2)
    printf("%s running...\n", ap_get_server_version());
#elif defined(WIN32)
    if (!child) {
        printf("%s running...\n", ap_get_server_version());
    }
#elif defined(NETWARE)
    if (currentScreen != GetCurrentScreen()) {
        SetCurrentScreen(currentScreen);  /* switch to console screen I/O */
        SetAutoScreenDestructionMode(0); 
    }

    printf("%s running...\n", ap_get_server_version());
#endif

#ifndef NETWARE
    if (one_process && !exit_event)
	exit_event = create_event(0, 0, NULL);
    if (one_process && !start_mutex)
	start_mutex = ap_create_mutex(NULL);
#endif

#ifdef NETWARE
    worker_main();
    destroy_semaphore(allowed_globals.jobsemaphore);

    while((ap_thread_count) || (!shutdown_pending))
        ThreadSwitchWithDelay();
#else 
    /*
     * In the future, the main will spawn off a couple
     * of children and monitor them. As soon as a child
     * exits, it spawns off a new one
     */
    if (child || one_process) {
	if (!exit_event || !start_mutex)
	    exit(-1);
#ifdef WIN32
        if (child)
            ap_start_child_console(is_child_of_service);
        else
            ap_start_console_monitor();
#endif
	worker_main();
	ap_destroy_mutex(start_mutex);
	destroy_event(exit_event);
    } 
#ifdef WIN32
    /* Windows NT service second time around ... we have all the overrides 
     * from the NT SCM, so go to town and return to the SCM when we quit.
     */
    if (isWindowsNT() && isProcessService())
    {
        master_main(argc, argv);
    }
    else if (service_name && signal_to_send && !isWindowsNT()
             && !strcasecmp(signal_to_send, "start")) {
        /* service95_main will call master_main() */
        service95_main(master_main, argc, argv, service_name);
    }
    else 
    {
	/* Let's go fishing for some signals including ctrl+c, ctrl+break,
         * logoff, close and shutdown, while the server is running
	 */
	ap_start_console_monitor();
        master_main(argc, argv);
    }
#else /* ndef WIN32 */
    else 
    {
        master_main(argc, argv);
    }
#endif /* ndef WIN32 */
#endif /* ndef NETWARE */

    clean_parent_exit(0);
    return 0;	/* purely to avoid a warning */
}

#endif /* ndef MULTITHREAD */

#else  /* ndef SHARED_CORE_TIESTATIC */

/*
**  Standalone Tie Program for Shared Core support
**
**  It's purpose is to tie the static libraries and 
**  the shared core library under link-time and  
**  passing execution control to the real main function
**  in the shared core library under run-time.
*/

extern int ap_main(int argc, char *argv[]);

int main(int argc, char *argv[]) 
{
    return ap_main(argc, argv);
}

#endif /* ndef SHARED_CORE_TIESTATIC */
#else  /* ndef SHARED_CORE_BOOTSTRAP */

#if defined(OS2) || defined(CYGWIN)
/* Shared core loader for OS/2 and Cygwin */

#if defined(CYGWIN)
__declspec(dllimport) 
#endif


int ap_main(int argc, char *argv[]); /* Load time linked from cyghttpd.dll */

int main(int argc, char *argv[])
{
    return ap_main(argc, argv);
}

#else

/*
**  Standalone Bootstrap Program for Shared Core support
**
**  It's purpose is to initialise the LD_LIBRARY_PATH
**  environment variable therewith the Unix loader is able
**  to start the Standalone Tie Program (see above)
**  and then replacing itself with this program by
**  immediately passing execution to it.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ap_config.h"
#include "httpd.h"

#if defined(HPUX) || defined(HPUX10) || defined(HPUX11)
#define VARNAME "SHLIB_PATH"
#else
#define VARNAME "LD_LIBRARY_PATH"
#endif

#ifndef SHARED_CORE_DIR 
#define SHARED_CORE_DIR HTTPD_ROOT "/libexec"
#endif

#ifndef SHARED_CORE_EXECUTABLE_PROGRAM
#define SHARED_CORE_EXECUTABLE_PROGRAM "lib" TARGET ".ep"
#endif

extern char *optarg;
extern int   optind;

int main(int argc, char *argv[], char *envp[]) 
{
    char prog[MAX_STRING_LEN];
    char llp_buf[MAX_STRING_LEN];
    char **llp_slot;
    char *llp_existing;
    char *llp_dir;
    char **envpnew;
    int c, i, l;

#ifdef MPE
    /*
     * MPE doesn't currently initialize the envp parameter.  Instead, we must
     * use the global variable environ. 
     */
    envp = environ;
#endif
	
    /* 
     * parse argument line, 
     * but only handle the -L option 
     */
    llp_dir = SHARED_CORE_DIR;
    while ((c = getopt(argc, argv, "D:C:c:Xd:Ff:vVlLR:SZ:tTh")) != -1) {
	switch (c) {
	case 'D':
	case 'C':
	case 'c':
	case 'X':
	case 'd':
	case 'F':
	case 'f':
	case 'v':
	case 'V':
	case 'l':
	case 'L':
	case 'S':
	case 'Z':
	case 't':
	case 'T':
	case 'h':
	case '?':
	    break;
	case 'R':
	    llp_dir = strdup(optarg);
	    break;
	}
    }

#ifdef MPE
    /*
     * MPE doesn't currently initialize the envp parameter.  Instead, we must
     * use the global variable environ. 
     */
    envp = environ;
#endif
	
    /* 
     * create path to SHARED_CORE_EXECUTABLE_PROGRAM
     */
    ap_snprintf(prog, sizeof(prog), "%s/%s", llp_dir, SHARED_CORE_EXECUTABLE_PROGRAM);

    /* 
     * adjust process environment therewith the Unix loader 
     * is able to start the SHARED_CORE_EXECUTABLE_PROGRAM.
     */
    llp_slot = NULL;
    llp_existing = NULL;
    l = strlen(VARNAME);
    for (i = 0; envp[i] != NULL; i++) {
	if (strncmp(envp[i], VARNAME "=", l+1) == 0) {
	    llp_slot = &envp[i];
	    llp_existing = strchr(envp[i], '=') + 1;
	}
    }
    if (llp_slot == NULL) {
	envpnew = (char **)malloc(sizeof(char *)*(i + 2));
	if (envpnew == NULL) {
	    fprintf(stderr, "Ouch!  Out of memory generating envpnew!\n");
	}
	memcpy(envpnew, envp, sizeof(char *)*i);
	envp = envpnew;
	llp_slot = &envp[i++];
	envp[i] = NULL;
    }
    if (llp_existing != NULL)
	 ap_snprintf(llp_buf, sizeof(llp_buf), "%s=%s:%s", VARNAME, llp_dir, llp_existing);
    else
	 ap_snprintf(llp_buf, sizeof(llp_buf), "%s=%s", VARNAME, llp_dir);
    *llp_slot = strdup(llp_buf);

    /* 
     * finally replace our process with 
     * the SHARED_CORE_EXECUTABLE_PROGRAM
     */
    if (execve(prog, argv, envp) == -1) {
	fprintf(stderr, 
		"%s: Unable to exec Shared Core Executable Program `%s'\n",
		argv[0], prog);
	return 1;
    }
    else
	return 0;
}

#endif /* def OS2 */
#endif /* ndef SHARED_CORE_BOOTSTRAP */

#ifndef SHARED_CORE_BOOTSTRAP
#include "httpd.h"
/*
 * Force ap_validate_password() into the image so that modules like
 * mod_auth can use it even if they're dynamically loaded.
 */
void suck_in_ap_validate_password(void);
void suck_in_ap_validate_password(void)
{
    ap_validate_password("a", "b");
}
#endif

/* force Expat to be linked into the server executable */
#if defined(USE_EXPAT) && !defined(SHARED_CORE_BOOTSTRAP)
#include "xmlparse.h"
const XML_LChar *suck_in_expat(void);
const XML_LChar *suck_in_expat(void)
{
    return XML_ErrorString(XML_ERROR_NONE);
}
#endif /* USE_EXPAT */

