/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All rights reserved.
 */

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */

/*
 * STM - State-Threaded Multi-Processing Module for Apache/2.0
 *
 * Mike Abbott
 * Accelerating Apache Project - http://aap.sourceforge.net/
 * State Threads Project - http://state-threads.sourceforge.net/
 *
 * State threads are non-preemptive non-concurrent threads that run on
 * virtual processors which do not share data.  Performance exceeds that
 * of concurrent threads such as pthreads because there is no mutual
 * exclusion locking and no proliferation of kernel execution vehicles
 * from use of blocking I/O system calls.  State threads are extremely
 * lightweight, scheduling entirely in user mode.  Hardware concurrency
 * is available by forking multiple virtual processors.
 * 
 * The state-threaded multi-processing module (STM MPM) for Apache/2.0
 * creates a constant number of virtual processors (VPs) and replaces
 * them when they die.  Each virtual processor manages its own
 * independent set of state threads (STs), the number of which varies
 * with load against the server.  Each state thread listens to, and
 * processes connections from, exactly one listening socket.
 * 
 * When the main Apache server calls STM's ap_mpm_run() the initial
 * process becomes the watchdog, waiting for children (VPs) to die or
 * for a signal requesting shutdown or restart.  (See stm_main().)  This
 * is the master process.  The master process mostly sits blocked in
 * wait().  It does not wake up periodically to perform maintenance such
 * as the scoreboard updates other MPMs do every second.  Passive idling
 * via wait() is friendly to both low-volume and high-volume servers.
 * Low-volume servers benefit from passive idling because they can
 * completely page out Apache when it is idle for long periods and won't
 * have some CPU cache lines always containing Apache data.  High-volume
 * servers benefit because there are no extraneous scheduling events and
 * the CPU caches remain hot with data useful to serving pages quickly.
 * 
 * The master process forks off children which become virtual
 * processors.  Each VP starts a number of state threads and then
 * becomes the primordial state thread which blocks waiting for input
 * from a pipe.  (See stm_vp_main().)  Only the VP itself writes to this
 * pipe, and only to begin graceful shutdown of the VP and all its
 * threads.  This happens when a VP receives SIGHUP from the master
 * process or a VP has processed all of the connections it is allowed
 * and terminates itself (to reclaim resources leaked by faulty
 * modules).  This signal is the only way the master process and the VPs
 * communicate (except for SIGTERM, upon receipt of which the VPs exit
 * immediately because they set its disposition to SIG_DFL).  A pipe is
 * used because using write() on a pipe is guaranteed to be safe inside
 * a signal handler and no ST function -- such as st_cond_signal() -- is
 * safe to use inside a signal handler.
 * 
 * Each state thread loops processing connections from a single
 * listening socket.  (See stm_thread_main().)  (Threads could span
 * multiple sockets but doing so would cloud the code a bit.  There
 * would be no extra system calls since everything boils down to
 * select() in the state thread library anyway.)  Only one ST runs on a
 * VP at a time, and VPs do not share memory (except for the
 * scoreboards, discussed below), so no mutual exclusion locking is
 * necessary on any data, and the entire server is free to use all the
 * static variables and non-reentrant library functions it wants,
 * greatly simplifying programming and debugging and increasing
 * performance.  The current thread on each VP maintains equilibrium on
 * that VP, starting a new thread or terminating itself if the number of
 * spare threads exceeds the lower or upper limit, and also updates
 * counters and the scoreboard for that VP.
 * 
 * Each VP has its own scoreboard.  The scoreboards are memory-mapped
 * disk files (as opposed to anonymous shared memory) so that tools can
 * read scoreboard data without causing Apache to serve a page.  Of
 * course, most of the scoreboard data is also available on the
 * server-status page from mod_status.  VPs have read/write access to
 * all VPs' scoreboards but in practice read/write their own and only
 * read others'.
 * 
 * All I/O operations on sockets must use the state thread library's I/O
 * functions because only those functions prevent blocking of the entire
 * VP process and perform state thread scheduling.  No APR I/O function
 * must ever be used.  The STM relies on the state thread I/O layer (ST
 * IOL) to direct I/O operations to ST I/O functions.
 * 
 * Virtual processors may be bound to particular CPUs to improve cache
 * utilization, and may listen to all or a subset of the listen sockets.
 * Cache utilization is further improved when network interface
 * interrupts are bound to the same CPUs as the VPs listening to those
 * interfaces.  Unfortunately intercepting Listen directives to support
 * this optimization introcudes some really ugly code.
 *
 * See htdocs/manual/stm.html for further documentation.
 */

#include <st.h>

#define CORE_PRIVATE
#include "ap_config.h"
#include "apr_portable.h"
#include "apr_strings.h"
#include "httpd.h"
#include "http_main.h"
#include "http_log.h"
#include "http_config.h"
#include "http_connection.h"
#include "ap_listen.h"
#include "ap_mpm.h"
#include "mpm_status.h"
#include "unixd.h"
#include "stiol.h"

#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/mman.h>

#if defined(IRIX) && _MIPS_SIM != _ABIO32
/* disable warnings about unused formal parameters */
#pragma set woff 1174
#endif

/* STM version identifier included in Server response header. */
static const char stm_version[] = "STM/1.0";

/*
 * Maximum number of listeners (Listen/VPListen directives, aka
 * accept()ing sockets).  The default value is arbitrary but should be
 * kept small.  Feel free to override the default with any value though.
 */
#ifndef STM_LISTENER_LIMIT
#define STM_LISTENER_LIMIT	8
#endif

/*
 * Maximum number of virtual processors.  The default value is arbitrary
 * but should be kept small.  Feel free to override the default with any
 * value though.
 */
#ifndef STM_VP_LIMIT
#define STM_VP_LIMIT		8
#endif

/*
 * Maximum number of state threads per virtual processor (upper bound on
 * MaxThreads).  The default value is arbitrary but should be kept
 * moderate:  not so small as to hinder performance, not so big as to
 * waste memory.  Feel free to override the default with any value
 * though.  The server-status page (see mod_status) reports the maximum
 * number of threads actually used at any time, which can be used to
 * tune this value optimally for your system and workload.
 */
#ifndef STM_ST_LIMIT
#define STM_ST_LIMIT		512
#endif

/*
 * Maximum length in bytes of the key string in the scoreboards'
 * connection status tables, including the string-terminating null.
 * Keys longer than this will be truncated.  The default value is
 * arbitrary but should be kept small.  Feel free to override the
 * default with any value though.  It's probably a good idea to keep
 * STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE one or more times the size
 * of your system's largest cache line.
 */
#ifndef STM_SCORE_KEY_SIZE
#define STM_SCORE_KEY_SIZE	16
#endif

/*
 * Maximum length in bytes of the value string in the scoreboards'
 * connection status tables, including the string-terminating null.
 * Values longer than this will be truncated.  The default value is
 * arbitrary but should be kept moderate.  Feel free to override the
 * default with any value though.  It's probably a good idea to keep
 * STM_SCORE_KEY_SIZE + STM_SCORE_VALUE_SIZE one or more times the size
 * of your system's largest cache line.
 */
#ifndef STM_SCORE_VALUE_SIZE
#define STM_SCORE_VALUE_SIZE	48
#endif

/*
 * Maximum number of key/value pairs in the scoreboards' connection
 * status tables.  When the table fills additional insertions are
 * ignored.  The default value is arbitrary but should be kept small.
 * Feel free to override the default with any value though.
 */
#ifndef STM_SCORE_LIMIT
#define STM_SCORE_LIMIT		16
#endif

/* Debugging aids. */
#ifdef STM_DEBUG
# define STM_ASSERT(x)	{ \
    if (!(x)) { \
	fprintf(stderr, "STM assertion botched: \"%s\", %s line %d, pid %d, thread %p\n", \
	  #x, __FILE__, __LINE__, getpid(), stm_thread_self()); \
	abort(); \
    } \
}
# define STM_TRACE(x)	if (stm.debug.trace) printf x
# define HIDDEN		/* nothing */
#else
# define STM_ASSERT(x)	/* nothing */
# define STM_TRACE(x)	/* nothing */
# define HIDDEN		static
#endif

/* Configured VPListen directives. */
typedef struct stm_config_vp_listen {
    int			num_listeners;	/* number of listeners */
    struct stm_listener	*listeners[STM_LISTENER_LIMIT];	/* ptrs to stm.listeners */
} stm_config_vp_listen;

/*
 * Configuration, defaults, and methods.  All STM configuration settings
 * are kept in this structure.
 */
typedef struct stm_config {
    /*
     * Number of virtual processors.  Must be between 1 and STM_VP_LIMIT
     * inclusive.  Only one or a few are needed per CPU.  The server
     * starts a replacement when a VP dies so the number running is
     * constant.  Making this too small can cause long latency for
     * clients; too big and you waste CPU cycles and memory.
     */
    int num_vps;
#define STM_CMD_NUM_VPS			"NumVPs"

    /*
     * Number of state threads each VP should start initially PER SOCKET
     * -- not per VP.  Must be between 1 and STM_ST_LIMIT inclusive.
     * Should be between 1 and max_threads/num_listeners inclusive.
     * Generally this value should be the same as min_spare_threads but
     * it can be higher to facilitate Apache restarts on high-volume
     * servers.
     */
    int start_threads;
#define STM_CMD_START_THREADS		"StartThreads"

    /*
     * Minimum number of spare state threads PER SOCKET -- not per VP.
     * Spare threads are threads awaiting a connection (as opposed to
     * processing requests on established connections).  Must be between
     * 1 and STM_ST_LIMIT inclusive.  Should be between 1 and
     * max_threads/num_listeners inclusive.  Making this too small can
     * cause long latency for clients; too big and you waste CPU cycles
     * on low-volume servers.
     */
    int min_spare_threads;
#define STM_CMD_MIN_SPARE_THREADS	"MinSpareThreads"

    /*
     * Maximum number of spare state threads PER SOCKET -- not per VP.
     * See min_spare_threads.
     */
    int max_spare_threads;
#define STM_CMD_MAX_SPARE_THREADS	"MaxSpareThreads"

    /*
     * Maximum number of state threads -- spare or busy -- per VP across
     * all sockets.  Must be between 1 and STM_ST_LIMIT inclusive.
     * Making this too small can cause long latency for clients,
     * especially if your site is largely dynamic (e.g. CGI); too big
     * and you waste memory.
     */
    int max_threads;
#define STM_CMD_MAX_THREADS		"MaxThreads"

    /*
     * Size in bytes of each thread's stack.  Must be between the
     * system's page size and INT_MAX inclusive.  Making this too small
     * can cause stack overflow errors; too big and you waste memory.
     */
    int stack_size;
#define STM_CMD_STACK_SIZE		"StackSize"

    /*
     * Number of connections (not requests) each thread should serve
     * before yielding the VP to another thread.  Must be between 0 and
     * INT_MAX inclusive.  Making this too small can waste CPU cycles
     * (by forcing a select() too often); too big (or 0) can cause long
     * latency for clients and also inaccurate time stamps in log files
     * (with USE_ST_TIME).  The value 0 means the number of connections
     * is unlimited.
     */
    int thread_connections;
#define STM_CMD_THREAD_CONNECTIONS	"ThreadConnections"

    /*
     * Number of connections (not requests) each VP should serve before
     * exiting gracefully and allowing the master process to replace it
     * to reclaim resources such as memory or file descriptors leaked by
     * faulty modules.  Must be between 0 and INT_MAX inclusive.  Making
     * this too small can waste CPU cycles; too big (or 0) and leaked
     * resources can interfere with normal system operation.  The value
     * 0 means the number of connections is unlimited and VPs should
     * never voluntarily exit.
     */
    int vp_connections;
#define STM_CMD_VP_CONNECTIONS		"VPConnections"

    /*
     * Binding VPs to CPUs.  If vp_bind is non-null, vp_bind[n] is the
     * CPU number to which VP n should be bound.
     */
    int *vp_bind;
#define	STM_CMD_VP_BIND			"VPBind"

    /*
     * Binding listeners to VPs.  If vp_listen is non-null, vp_listen[n]
     * is a vector of IP addresses to which VP n should listen.
     */
    stm_config_vp_listen *vp_listen;
#define STM_CMD_VP_LISTEN		"VPListen"

    /*
     * Path of the file where Apache writes the PID of the master
     * process.  The file may or may not exist before startup.  Except
     * for the defaults structure this is always an absolute path name.
     */
    const char *pid_file;
#define STM_CMD_PID_FILE		"PidFile"

    /*
     * Flag enabling or disabling connection status in scoreboards (they
     * can be slow to maintain).  Zero means disabled, nonzero means
     * enabled.
     */
    int connection_status;
#define STM_CMD_CONNECTION_STATUS	"ConnectionStatus"

    /*
     * Path of the directory where the STM creates scoreboard files for
     * each VP.  The directory must already exist before Apache startup.
     * The scoreboard files themselves may or may not pre-exist, have
     * names that are just the ordinal of the VP, and have a data format
     * described in the scoreboard section below.  Except for the
     * defaults structure this is always an absolute path name.
     */
    const char *scoreboard_dir;
#define STM_CMD_SCOREBOARD_DIR		"ScoreboardDir"

    /*
     * Path of a directory where Apache will dump core if necessary.
     * The directory should exist before Apache startup.  Except for the
     * defaults structure this is always an absolute path name.  A NULL
     * value means Apache should dump core in whatever directory it
     * happens to be in at the time, generally the server root directory.
     */
    const char *core_dir;
#define STM_CMD_CORE_DIR		"CoreDir"
} stm_config;
HIDDEN const stm_config stm_config_defaults = {
    4,			/* num_vps */
    5,			/* start_threads */
    5,			/* min_spare_threads */
    10,			/* max_spare_threads */
    64,			/* max_threads */
    64 * 1024,		/* stack_size */
    0,			/* thread_connections */
    0,			/* vp_connections */
    NULL,		/* vp_bind */
    NULL,		/* vp_listen */
    "logs/httpd.pid",	/* pid_file */
    1,			/* connection_status */
    "scoreboards",	/* scoreboard_dir */
    NULL		/* core_dir */
};
HIDDEN const char *stm_config_set_num(cmd_parms *, const char *, int *, int,
    int, const char *);
HIDDEN const char *stm_config_set_num_vps(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_start_threads(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_min_spare_threads(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_max_spare_threads(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_max_threads(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_stack_size(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_thread_connections(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_vp_connections(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_vp_bind(cmd_parms *, void *,
  const char *, const char *);
HIDDEN const char *stm_config_set_vp_listen(cmd_parms *, void *,
  const char *, const char *);
HIDDEN const char *stm_config_set_ap_listen(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_pid_file(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_connection_status(cmd_parms *, void *,
  int);
HIDDEN const char *stm_config_set_scoreboard_dir(cmd_parms *, void *,
  const char *);
HIDDEN const char *stm_config_set_core_dir(cmd_parms *, void *,
  const char *);
HIDDEN const command_rec stm_cmds[] = {
    AP_INIT_TAKE1(
      STM_CMD_NUM_VPS,
      stm_config_set_num_vps,
      NULL, RSRC_CONF,
      "Number of virtual processors"
    ),
    AP_INIT_TAKE1(
      STM_CMD_START_THREADS,
      stm_config_set_start_threads,
      NULL, RSRC_CONF,
      "Initial number of state threads per listen socket"
    ),
    AP_INIT_TAKE1(
      STM_CMD_MIN_SPARE_THREADS,
      stm_config_set_min_spare_threads,
      NULL, RSRC_CONF,
      "Minumum number of spare state threads per listen socket"
    ),
    AP_INIT_TAKE1(
      STM_CMD_MAX_SPARE_THREADS,
      stm_config_set_max_spare_threads,
      NULL, RSRC_CONF,
      "Maxumum number of spare state threads per listen socket"
    ),
    AP_INIT_TAKE1(
      STM_CMD_MAX_THREADS,
      stm_config_set_max_threads,
      NULL, RSRC_CONF,
      "Maximum number of state threads per virtual processor"
    ),
    AP_INIT_TAKE1(
      STM_CMD_STACK_SIZE,
      stm_config_set_stack_size,
      NULL, RSRC_CONF,
      "Size in bytes of each thread's stack"
    ),
    AP_INIT_TAKE1(
      STM_CMD_THREAD_CONNECTIONS,
      stm_config_set_thread_connections,
      NULL, RSRC_CONF,
      "Number of connections each thread should serve before yielding"
    ),
    AP_INIT_TAKE1(
      STM_CMD_VP_CONNECTIONS,
      stm_config_set_vp_connections,
      NULL, RSRC_CONF,
      "Number of connections each virtual processor should serve before exiting"
    ),
    AP_INIT_TAKE2(
      STM_CMD_VP_BIND,
      stm_config_set_vp_bind,
      NULL, RSRC_CONF,
      "Bind a virtual processor to a specific CPU"
    ),
    AP_INIT_ITERATE2(
      STM_CMD_VP_LISTEN,
      stm_config_set_vp_listen,
      NULL, RSRC_CONF,
      "List of ip-address:port-numbers to which a virtual processor should listen"
    ),
    AP_INIT_TAKE1(
      STM_CMD_PID_FILE,
      stm_config_set_pid_file,
      NULL, RSRC_CONF,
      "Name of file in which the server records its PID upon startup"
    ),
    AP_INIT_FLAG(
      STM_CMD_CONNECTION_STATUS,
      stm_config_set_connection_status,
      NULL, RSRC_CONF,
      "Enables/disables connection status information in the scoreboards"
    ),
    AP_INIT_TAKE1(
      STM_CMD_SCOREBOARD_DIR,
      stm_config_set_scoreboard_dir,
      NULL, RSRC_CONF,
      "Name of existing directory in which the server maintains scoreboard file(s)"
    ),
    AP_INIT_TAKE1(
      STM_CMD_CORE_DIR,
      stm_config_set_core_dir,
      NULL, RSRC_CONF,
      "Name of existing directory in which the server should dump core if necessary"
    ),
    UNIX_DAEMON_COMMANDS

    /*
     * Redirect Listen directives to our own handler.  This is a gross
     * kludge but so is the existence of, definition of, and MPMs'
     * dependence on the LISTEN_COMMANDS macro.
     */
#define ap_set_listener stm_config_set_ap_listen
    LISTEN_COMMANDS
#undef ap_set_listener

    { NULL }
};
HIDDEN void stm_pre_config(apr_pool_t *, apr_pool_t *, apr_pool_t *);
HIDDEN void stm_post_config(apr_pool_t *, apr_pool_t *, apr_pool_t *,
  server_rec *);
HIDDEN void stm_hooks(void);

/* VPs and STs health indicators. */
typedef enum stm_health {
    STM_HEALTH_DEAD,	/* entity is not running */
    STM_HEALTH_DYING,	/* entity has been asked to terminate */
    STM_HEALTH_ALIVE	/* entity is running */
} stm_health;

/* Master process state. */
typedef enum stm_state {
    STM_STATE_SHUTDOWN,	/* shutdown requested or in progress */
    STM_STATE_RUN,	/* running */
    STM_STATE_GRACEFUL,	/* graceful restart requested or in progress */
    STM_STATE_RESTART	/* restart requested or in progress */
} stm_state;

/* Evaluation of seriousness of child process's death. */
typedef enum stm_grief {
    STM_GRIEF_NORMAL,	/* normal and/or expected */
    STM_GRIEF_FATAL,	/* catastrophic */
    STM_GRIEF_SURPRISE	/* unexpected but not tragic */
} stm_grief;

#ifdef STM_DEBUG
/* Debugging aids. */
typedef struct stm_debug {
    int interactive;	/* when nonzero don't detach */
    int one_process;	/* when nonzero restrict to a single process */
    int trace;		/* when nonzero trace function calls */
} stm_debug;
#endif

/* Datum about a connection.  Key/value type mandated by mod_status. */
typedef struct stm_score {
    char key[STM_SCORE_KEY_SIZE];
    char value[STM_SCORE_VALUE_SIZE];
} stm_score;

/*
 * Per-VP scoreboard and methods.  Fields are explicitly sized because
 * this is also the on-disk file format.  When changing this structure
 * also change the version string so tools can correctly parse the data
 * and also update the stmstat tool.  The version string should be
 * exactly STM_SCOREBOARD_VERSION_LENGTH bytes long excluding the null.
 * Do not change STM_SCOREBOARD_VERSION_LENGTH.
 */
#define STM_SCOREBOARD_VERSION_LENGTH	16
#define STM_SCOREBOARD_VERSION		"STM-MPM-sb-0002\n"
typedef struct stm_scoreboard {
    char	version[STM_SCOREBOARD_VERSION_LENGTH];	/* see above */
    apr_int32_t	st_limit;		/* size of arrays below */
    apr_int32_t	listener_limit;		/* size of arrays below */
    apr_int32_t	score_limit;		/* size of arrays below */
    apr_int32_t	key_size;		/* size of arrays below */
    apr_int32_t	value_size;		/* size of arrays below */
    apr_int32_t	pid;			/* this VP's PID */
    apr_time_t	start_time;		/* server's start time */
    apr_time_t	vp_start_time;		/* this VP's start time */
    apr_int32_t	incarnations;		/* # of different VPs in this slot */
    apr_int32_t	cpu;			/* CPU to which this VP is bound */
    apr_int32_t	thread_starts;		/* # of thread start events */
    apr_int32_t	thread_exits;		/* # of thread exit events */
    apr_int32_t	most_threads;		/* most threads ever used */
    apr_int32_t	historic_vp_connections;/* # of conns, prior incarnations */
    apr_int32_t	vp_connections;		/* # of conns, this incarnation */
    apr_int32_t	thread_connections[STM_ST_LIMIT];	    /* per thread */
    apr_int32_t	listener_connections[STM_LISTENER_LIMIT];   /* per listener */
    char	listeners[STM_LISTENER_LIMIT][24];	/* IP address:port */
    stm_score	scores[STM_ST_LIMIT][STM_SCORE_LIMIT];	/* per-conn data */
} stm_scoreboard;
HIDDEN stm_scoreboard *stm_scoreboard_new(int);
HIDDEN void stm_scoreboard_init(stm_scoreboard *);

/*
 * Listener and methods.  These listeners are wrappers around the more
 * traditional ap_listen_rec so we can accept on an st_netfd_t rather
 * than an apr_socket_t.
 */
typedef apr_uint32_t stm_port;	/* simplify profusion of IP port types */
typedef apr_uint32_t stm_addr;	/* simplify profusion of IP address types */
typedef struct stm_listener {
    int			id;		/* == (int) (this - stm.listeners) */
    const char		*addr_string;	/* inet_ntoa(addr.sin_addr) */
    struct sockaddr_in	addr;		/* IP addr and port in network order */
    int			unique;		/* is addr unique (not INADDR_ANY)? */
    int			share_count;	/* number of VPs using this listener */
    ap_listen_rec	*alr;		/* standard listener */
    st_netfd_t		sd;		/* ST version of alr->sd */
    int			spare_threads;	/* # of spare threads right now */
} stm_listener;
HIDDEN void stm_listener_init(stm_listener *);
HIDDEN stm_listener *stm_listener_add(const char *, stm_addr, stm_port, int *);
HIDDEN int stm_listener_usurp(stm_listener *, ap_listen_rec *);
HIDDEN void stm_listener_revert(stm_listener *);

/*
 * Thread and methods.
 */
typedef struct stm_thread {
    int			id;		/* == (int) (this - vp->threads) */
    stm_health		health;		/* how is this thread doing? */
    st_thread_t		st;		/* ST for this thread */
    struct stm_vp	*vp;		/* VP on which this thread runs */
    stm_listener	*listener;	/* listener on which to accept() */
    int			connected;	/* connected to a client right now? */
    int			connections;	/* conns remaining before yield */
} stm_thread;
HIDDEN void stm_thread_init(stm_thread *, struct stm_vp *);
HIDDEN int stm_thread_start(stm_thread *, stm_listener *);
HIDDEN stm_thread *stm_thread_self(void);
HIDDEN void *stm_thread_main(void *);
HIDDEN void stm_thread_process_connection(const stm_listener *, apr_pool_t *,
  st_netfd_t, const struct sockaddr_in *, long);
HIDDEN void stm_thread_stop(stm_thread *);
HIDDEN int stm_thread_stopped(stm_thread *);

/*
 * Virtual processor and methods.
 */
typedef struct stm_vp {
    int		    id;			/* == (int) (this - stm.vps) */
    pid_t	    pid;		/* PID of VP process */
    stm_health	    health;		/* how is this VP doing? */
    apr_pool_t	    *pool;		/* pool for lifetime of VP */
    int		    stop_pipe[2];	/* for asynchronous-safe termination */
    int		    num_listeners;	/* number of listeners for this VP */
    stm_listener    *listeners[STM_LISTENER_LIMIT]; /* ptrs to stm.listeners */
    int		    connections;	/* conns remaining before suicide */
    int		    max_threads_hit;	/* reported hitting thread ceiling? */
    int		    num_threads;	/* number of non-dead threads */
    stm_thread	    threads[STM_ST_LIMIT];	/* threads */
    stm_thread	    *dead_threads[STM_ST_LIMIT];/* to recycle them quickly */
    stm_scoreboard  *scoreboard;	/* scoreboard in shared memory */
} stm_vp;
HIDDEN int stm_vp_init(stm_vp *);
HIDDEN int stm_vp_listen(stm_vp *);
HIDDEN int stm_vp_start(stm_vp *);
HIDDEN void stm_vp_bind(stm_vp *);
HIDDEN void stm_vp_signals(void);
HIDDEN void stm_vp_main(stm_vp *);
HIDDEN void stm_vp_stop(stm_vp *);
HIDDEN void stm_vp_stop_self(stm_vp *);
HIDDEN void stm_vp_onsig_stop(int);
HIDDEN void stm_vp_onexit(void);
HIDDEN apr_status_t stm_vp_cleanup(void *);

/*
 * Global data and methods.  All STM data is in this one place for easy
 * debugging.
 */
HIDDEN struct stm {
    apr_time_t		start_time;		/* server start time */
    apr_pool_t		*pool;			/* main pool */
    server_rec		*server;		/* for logging */
    stm_state		state;			/* run state */
    int			initialized;		/* stm_init() ever called? */
    int			num_restarts;		/* number of server restarts */
    pid_t		pid;			/* pid of master process */
    stm_config		config;			/* config parameters */
    int			num_listeners;		/* number of listeners */
    stm_listener	listeners[STM_LISTENER_LIMIT];
    int			num_vps;		/* number of VPs */
    stm_vp		vps[STM_VP_LIMIT];	/* table of VP info */
    int			self_key;		/* for stm_thread_self() */
#ifdef STM_DEBUG
    stm_debug		debug;			/* debugging parameters */
#endif
} stm;
HIDDEN int stm_init(apr_pool_t *, server_rec *);
HIDDEN void stm_fini(void);
HIDDEN void stm_signals(void);
HIDDEN void stm_main(void);
HIDDEN void stm_reap(pid_t, int);
HIDDEN stm_grief stm_mourn(pid_t, int);
HIDDEN void stm_doomsday(void);

/* Signal helper routines */
HIDDEN void stm_signal_catch(int, void (*)(int));
HIDDEN void stm_signal_default(int);
HIDDEN void stm_signal_ignore(int);
HIDDEN void stm_signal_core(int);

/* Master process's signal handlers */
HIDDEN void stm_onsig_core(int);
HIDDEN void stm_onsig_restart(int);
HIDDEN void stm_onsig_shutdown(int);

/* Miscellany */
HIDDEN long stm_id_make(int, int);
HIDDEN int stm_id_break(long, int *, int *);
HIDDEN void stm_disable_nagle(st_netfd_t);

/* Exported API routines */
void ap_start_shutdown(void);
void ap_start_restart(int);
int ap_graceful_stop_signalled(void);
void ap_reset_connection_status(long);
void ap_update_connection_status(long, const char *, const char *);
apr_array_header_t *ap_get_status_table(apr_pool_t *);
int ap_mpm_run(apr_pool_t *, apr_pool_t *, server_rec *);

/* Module definition */
module MODULE_VAR_EXPORT mpm_stm_module = {
    MPM20_MODULE_STUFF,
    NULL,			/* run before apache parses argv */
    NULL,			/* create per-directory config structure */
    NULL,			/* merge per-directory config structures */
    NULL,			/* create per-server config structure */
    NULL,			/* merge per-server config structures */
    stm_cmds,			/* command table */
    NULL,			/* handlers */
    stm_hooks			/* register_hooks */
};


/***********************************************************************
 * Configuration
 */

/*
 * Standard MPM pre-config hook:  initializes/resets all configuration
 * parameters to their default values.
 */
HIDDEN void
stm_pre_config(apr_pool_t *conf_pool, apr_pool_t *log_pool,
  apr_pool_t *tmp_pool)
{
    int i;

#ifdef STM_DEBUG
    if (stm.pid == 0) {
	/* set these just once */
	stm.debug.interactive = getenv("STM_INTERACTIVE") != NULL;
	stm.debug.one_process = getenv("ONE_PROCESS") != NULL;
	stm.debug.trace = getenv("STM_TRACE") != NULL;
    }
#endif

    STM_TRACE(("%d: stm_pre_config() #%d\n", getpid(), stm.num_restarts));

    /* it would be bad to reset stm.config accidentally while running */
    STM_ASSERT(stm.state != STM_STATE_RUN);

    /* called twice on normal startup; detach only on second one */
    if (stm.num_restarts++ == 1) {
#ifdef STM_DEBUG
	setlinebuf(stdout);
	if (!stm.debug.interactive && !stm.debug.one_process)
#endif
	    if (apr_detach() != APR_SUCCESS)
		ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP,
		  errno, NULL,
		  "stm_pre_config: could not detach from controlling terminal: apr_detach");

	stm.pid = getpid();
    }

    unixd_pre_config();
    ap_listen_pre_config();

    stm.num_listeners = 0;
    for (i = 0; i < STM_LISTENER_LIMIT; i++)
	stm_listener_init(&stm.listeners[i]);

    if (stm.config.vp_bind) {
	free(stm.config.vp_bind);
	stm.config.vp_bind = NULL;
    }
    if (stm.config.vp_listen) {
	free(stm.config.vp_listen);
	stm.config.vp_listen = NULL;
    }

    stm.config = stm_config_defaults;
    stm.config.pid_file = ap_server_root_relative(conf_pool,
      stm_config_defaults.pid_file);
    stm.config.scoreboard_dir = ap_server_root_relative(conf_pool,
      stm_config_defaults.scoreboard_dir);
    stm.config.core_dir = apr_pstrdup(conf_pool, ap_server_root);
}

/*
 * Standard MPM post-config hook.
 */
HIDDEN void
stm_post_config(apr_pool_t *conf_pool, apr_pool_t *log_pool,
  apr_pool_t *tmp_pool, server_rec *server)
{
    STM_TRACE(("%d: stm_post_config()\n", getpid()));

#ifndef NO_STM_VERSION
    ap_add_version_component(conf_pool, stm_version);
#endif
}

/*
 * Standard module hook registration.
 */
HIDDEN void
stm_hooks(void)
{
    STM_TRACE(("%d: stm_hooks()\n", getpid()));

    INIT_SIGLIST()

    ap_hook_pre_config(stm_pre_config, NULL, NULL, AP_HOOK_MIDDLE);
    ap_hook_post_config(stm_post_config, NULL, NULL, AP_HOOK_MIDDLE);
}

/*
 * Utility function to set an integer configuration parameter if the new
 * value is in bounds:
 *  max_value >= 0	min_value <= atoi(value_string) <= max_value
 *  max_value < 0	min_value <= atoi(value_string)
 * Returns an error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_num(cmd_parms *cmd, const char *value_string, int *paramp,
  int min_value, int max_value, const char *max_name)
{
    const char *err;
    int value;

    STM_TRACE(("%d: stm_config_set_num(cmd->\"%s\", value_string=\"%s\", min_value=%d, max_value=%d, max_name=\"%s\")\n",
      getpid(), cmd->cmd->name, value_string, min_value, max_value, max_name));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL) {
	value = atoi(value_string);
	if (value >= min_value) {
	    if (max_value < 0 || value <= max_value)
		*paramp = value;
	    else {
		ap_log_error(APLOG_MARK,
		  APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, NULL,
		  "value %d for %s is above maximum %d, using maximum"
		  "; re-compile with larger %s if desired",
		  value, cmd->cmd->name, max_value, max_name);
		*paramp = max_value;
	    }
	} else {
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, NULL,
	      "value %d for %s is below minimum %d, using minimum",
	      value, cmd->cmd->name, min_value);
	    *paramp = min_value;
	}
    }

    return err;
}

/*
 * Set the num_vps configuration parameter if the new value is in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_num_vps(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.num_vps,
      1, STM_VP_LIMIT, "STM_VP_LIMIT");
}

/*
 * Set the start_threads configuration parameter if the new value is in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_start_threads(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.start_threads,
      1, STM_ST_LIMIT, "STM_ST_LIMIT");
}

/*
 * Set the min_spare_threads configuration parameter if the new value is
 * in bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_min_spare_threads(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.min_spare_threads,
      1, STM_ST_LIMIT, "STM_ST_LIMIT");
}

/*
 * Set the max_spare_threads configuration parameter if the new value is
 * in bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_max_spare_threads(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.max_spare_threads,
      1, STM_ST_LIMIT, "STM_ST_LIMIT");
}

/*
 * Set the max_threads configuration parameter if the new value is in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_max_threads(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.max_threads,
      1, STM_ST_LIMIT, "STM_ST_LIMIT");
}

/*
 * Set the stack_size configuration parameter if the new value is in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_stack_size(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.stack_size,
      getpagesize(), -1, NULL);
}

/*
 * Set the thread_connections configuration parameter if the new value
 * is in bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_thread_connections(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.thread_connections,
      0, -1, NULL);
}

/*
 * Set the vp_connections configuration parameter if the new value is in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_vp_connections(cmd_parms *cmd, void *dummy, const char *num)
{
    return stm_config_set_num(cmd, num, &stm.config.vp_connections,
      0, -1, NULL);
}

/*
 * Add to the vp_bind configuration parameter if the new values are in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_vp_bind(cmd_parms *cmd, void *dummy, const char *vp_string,
  const char *cpu_string)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_vp_bind(vp_string=\"%s\", cpu_string=\"%s\")\n",
      getpid(), vp_string, cpu_string));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL) {
	int v;
	char *stop;

	v = (int) strtol(vp_string, &stop, 0);
	if (stop != vp_string && *stop == 0) {
	    /*
	     * Compare against STM_VP_LIMIT here because num_vps may not
	     * have been set yet (could appear later in config file).
	     * stm_init() warns about bindings for unused VPs.
	     */
	    if (v >= 0 && v < STM_VP_LIMIT) {
		int c;

		c = (int) strtol(cpu_string, &stop, 0);
		if (stop != cpu_string && *stop == 0 && c >= 0) {
		    if (stm.config.vp_bind == NULL) {
			int i;

			stm.config.vp_bind = (int *) malloc(STM_VP_LIMIT *
			  sizeof *stm.config.vp_bind);
			for (i = 0; i < STM_VP_LIMIT; i++)
			    stm.config.vp_bind[i] = -1;
		    }
		    stm.config.vp_bind[v] = c;
		} else
		    err = "CPU identifier must be a non-negative number";
	    } else
		err = apr_psprintf(cmd->pool,
		  "value %d for %s is out of range, must be between 0 and %d inclusive"
		  "; re-compile with larger STM_VP_LIMIT if desired",
		  v, cmd->cmd->name, STM_VP_LIMIT - 1);
	} else
	    err = "virtual processor identifier must be a number";
    }

    return err;
}

/*
 * Add to the vp_listen configuration parameter if the new values are in
 * bounds.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_vp_listen(cmd_parms *cmd, void *dummy, const char *vp_string,
  const char *listen_string)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_vp_listen(vp_string=\"%s\", listen_string=\"%s\")\n",
      getpid(), vp_string, listen_string));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL && ap_listeners && stm.config.vp_listen == NULL)
	err = "cannot mix Listen and " STM_CMD_VP_LISTEN " directives.";
    if (err == NULL) {
	int v;
	char *stop;

	v = (int) strtol(vp_string, &stop, 0);
	if (stop != vp_string && *stop == 0) {
	    /*
	     * Compare against STM_VP_LIMIT here because num_vps may
	     * not have been set yet (could appear later in config
	     * file).  stm_init() warns about bindings for unused
	     * VPs.
	     */
	    if (v >= 0 && v < STM_VP_LIMIT) {
		char *listen_copy;
		char *port_string;

		listen_copy = apr_pstrdup(cmd->pool, listen_string);
		port_string = strchr(listen_copy, ':');
		if (port_string) {
		    stm_port port;

		    *port_string++ = 0;

		    /* should be strtoul() but can't use it, oh well */
		    port = (stm_port) strtol(port_string, &stop, 0);
		    if (stop != port_string && *stop == 0) {
			int addr_ok;
			struct in_addr addr;

			port = htons(port);

			addr_ok = inet_aton(listen_copy, &addr);
			if (!addr_ok && !strcmp(listen_copy, "*")) {
			    addr.s_addr = htonl(INADDR_ANY);
			    addr_ok = 1;
			}

			if (addr_ok) {
			    stm_config_vp_listen *vlp;
			    int new;
			    stm_listener *lp;

			    if (stm.config.vp_listen == NULL)
				stm.config.vp_listen = (stm_config_vp_listen *)
				  calloc(STM_VP_LIMIT,
				    sizeof *stm.config.vp_listen);

			    vlp = &stm.config.vp_listen[v];
			    new = 0;
			    lp = (vlp->num_listeners < STM_LISTENER_LIMIT) ?
				stm_listener_add(listen_copy, addr.s_addr,
				  port, &new) : NULL;
			    if (lp) {
				vlp->listeners[vlp->num_listeners++] = lp;
				if (new) {
				    /*
				     * Call the regular Listen directive
				     * handler to add a standard
				     * listener to ap_listeners.  This
				     * is a gross kludge but so is the
				     * way Apache manages listeners.
				     */
				    err = ap_set_listener(cmd, dummy,
				      (addr.s_addr == htonl(INADDR_ANY)) ?
				      port_string : listen_string);

				    /*
				     * An error from ap_set_listener()
				     * means our call to it is wrong and
				     * should be fixed.
				     */
				    STM_ASSERT(err == NULL);
				}
			    } else
				err = "too many listeners; re-compile with larger STM_LISTENER_LIMIT if desired";
			} else
			    err = "invalid IP address";
		    } else
			err = "invalid port number";
		} else
		    err = "listen address must be of the form: ip-address:port-number or *:port-number";
	    } else
		err = apr_psprintf(cmd->pool,
		  "value %d for %s is out of range, must be between 0 and %d inclusive"
		  "; re-compile with larger STM_VP_LIMIT if desired",
		  v, cmd->cmd->name, STM_VP_LIMIT - 1);
	} else
	    err = "virtual processor identifier must be a number";
    }

    return err;
}

/*
 * Intercept standard Listen directives, saving information that is not
 * readily available later.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_ap_listen(cmd_parms *cmd, void *dummy,
  const char *listen_string)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_ap_listen(listen_string=\"%s\")\n",
      getpid(), listen_string));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL && stm.config.vp_listen)
	err = "cannot mix Listen and " STM_CMD_VP_LISTEN " directives";
    if (err == NULL) {
	char *ips, *ports;
	stm_port port;
	struct in_addr addr;

	/*
	 * Parse the Listen directive's argument so we can fill in the
	 * address and port number in the stm_listener.  Then call the
	 * regular Listen directive handler since we can't call
	 * alloc_listener() directly.
	 */

	ips = apr_pstrdup(cmd->pool, listen_string);

	/*
	 *****************************************
	 * begin copied code from ap_set_listener()
	 */
	ports = strchr(ips, ':');
	if (ports != NULL) {
	    if (ports == ips) {
		return "Missing IP address";
	    }
	    else if (ports[1] == '\0') {
		return "Address must end in :<port-number>";
	    }
	    *(ports++) = '\0';
	}
	else {
	    ports = ips;
	}

	port = atoi(ports);
	if (!port) {
	    return "Port must be numeric";
	}

	if (ports == ips) { /* no address */
	    ips = APR_ANYADDR;
	}
	else {
	    ips[(ports - ips) - 1] = '\0';
	}
	/*
	 * end copied code from ap_set_listener()
	 *****************************************
	 */

	if (inet_aton(ips, &addr)) {
	    int new;

	    if (stm_listener_add(ips, addr.s_addr, htons(port), &new)) {
		if (new)
		    err = ap_set_listener(cmd, dummy, listen_string);
		/* else ignore it */
	    } else
		err = "too many listeners; re-compile with larger STM_LISTENER_LIMIT if desired";
	} else
	    err = "invalid IP address";
    }

    return err;
}

/*
 * Set the pid_file configuration parameter.  Returns error string, or
 * NULL on success.
 */
HIDDEN const char *
stm_config_set_pid_file(cmd_parms *cmd, void *dummy, const char *path)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_pid_file(path=\"%s\")\n", getpid(), path));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL)
	stm.config.pid_file = ap_server_root_relative(stm.pool, path);

    return err;
}

/*
 * Set the connection_status configuration parameter.  Returns error
 * string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_connection_status(cmd_parms *cmd, void *dummy, int flag)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_connection_status(flag=%d)\n", getpid(),
      flag));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL)
	stm.config.connection_status = flag;

    return err;
}

/*
 * Set the scoreboard_dir configuration parameter if the directory
 * exists.  Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_scoreboard_dir(cmd_parms *cmd, void *dummy, const char *path)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_scoreboard_dir(path=\"%s\")\n", getpid(),
      path));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL) {
	const char *rpath;
	struct stat stbuf;

	rpath = ap_server_root_relative(stm.pool, path);
	if (stat(rpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode))
	    stm.config.scoreboard_dir = rpath;
	else
	    err = apr_psprintf(cmd->pool,
	      "%s \"%s\" does not exist or is not a directory",
	      cmd->cmd->name, rpath);
    }

    return err;
}

/*
 * Set the core_dir configuration parameter if the directory exists.
 * Returns error string, or NULL on success.
 */
HIDDEN const char *
stm_config_set_core_dir(cmd_parms *cmd, void *dummy, const char *path)
{
    const char *err;

    STM_TRACE(("%d: stm_config_set_core_dir(path=\"%s\")\n", getpid(), path));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err == NULL) {
	const char *rpath;
	struct stat stbuf;

	rpath = ap_server_root_relative(stm.pool, path);
	if (stat(rpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode))
	    stm.config.core_dir = rpath;
	else
	    err = apr_psprintf(cmd->pool,
	      "%s \"%s\" does not exist or is not a directory",
	      cmd->cmd->name, rpath);
    }

    return err;
}


/***********************************************************************
 * Scoreboard
 */

/*
 * Create and return a pointer to a new scoreboard in shared memory for
 * VP slot n, or NULL on failure.
 */
HIDDEN stm_scoreboard *
stm_scoreboard_new(int n)
{
    stm_scoreboard *sbp;
    char *path;
    int fd;

    STM_TRACE(("%d: stm_scoreboard_new(n=%d)\n", getpid(), n));

    sbp = NULL;

    path = apr_psprintf(stm.pool, "%s/%d", stm.config.scoreboard_dir, n);
    fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
    if (fd >= 0) {
	stm_scoreboard zsc;

	/* zero-fill the new file to exactly the right length */
	memset(&zsc, 0, sizeof zsc);
	if (write(fd, &zsc, sizeof zsc) == sizeof zsc) {
	    void *vp;

	    vp = mmap(0, sizeof *sbp, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
	      0);
	    if (vp != (void *) -1)
		sbp = (stm_scoreboard *) vp;
	    else
		ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
		  "stm_scoreboard_new: cannot map %ld bytes read-write in \"%s\": mmap",
		  (long) sizeof *sbp, path);
	} else
	    ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	      "stm_scoreboard_new: cannot zero-fill %ld bytes in \"%s\": write",
	      (long) sizeof zsc, path);

	close(fd);
    } else
	ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	  "stm_scoreboard_new: cannot create \"%s\" read-write: open", path);

    return sbp;
}

/*
 * Initialize/reset the given scoreboard.
 */
HIDDEN void
stm_scoreboard_init(stm_scoreboard *sb)
{
    int i, j;

    STM_TRACE(("%d: stm_scoreboard_init(sb=%p)\n", getpid(), sb));

    /*
     * All the values start out as zero but this function is used for
     * both init and reset.  Resetting a field to zero here makes it
     * keep track of only one incarnation of a VP.  Not resetting it
     * makes it keep track since server start.
     */

    memcpy(sb->version, STM_SCOREBOARD_VERSION, sizeof sb->version);
    sb->st_limit = STM_ST_LIMIT;
    sb->listener_limit = STM_LISTENER_LIMIT;
    sb->score_limit = STM_SCORE_LIMIT;
    sb->key_size = STM_SCORE_KEY_SIZE;
    sb->value_size = STM_SCORE_VALUE_SIZE;
    sb->pid = -1;
    sb->start_time = stm.start_time;
    sb->vp_start_time = 0;
    /* leave sb->incarnations alone */
    sb->cpu = -1;
    sb->thread_starts = 0;
    sb->thread_exits = 0;
    /* leave sb->most_threads alone */
    sb->historic_vp_connections += sb->vp_connections;
    sb->vp_connections = 0;
    for (i = 0; i < STM_ST_LIMIT; i++) {
	sb->thread_connections[i] = 0;
	for (j = 0; j < STM_SCORE_LIMIT; j++)
	    sb->scores[i][j].key[0] = 0;
    }
    for (i = 0; i < STM_LISTENER_LIMIT; i++) {
	sb->listener_connections[i] = 0;
	sb->listeners[i][0] = 0;
    }
}


/***********************************************************************
 * Listeners
 */

/*
 * Initialize/reset the given listener.
 */
HIDDEN void
stm_listener_init(stm_listener *listener)
{
    STM_TRACE(("%d: stm_listener_init(listener=%p,#%d)\n", getpid(),
      listener, (int) (listener - stm.listeners)));

    listener->id = (int) (listener - stm.listeners);
    STM_ASSERT(listener->id >= 0 && listener->id < STM_LISTENER_LIMIT);
    listener->addr_string = NULL;
    memset(&listener->addr, 0, sizeof listener->addr);
    listener->unique = 0;
    listener->share_count = 0;
    listener->alr = NULL;
    STM_ASSERT(listener->sd == NULL);	/* init or stm_listener_revert() */
    listener->sd = NULL;
    listener->spare_threads = 0;
}

/*
 * Add the given IP address and port number (both in network byte order)
 * to the list of listeners if not already there.  Sets *new to 0 if
 * already there or to 1 if not.  Returns the new listener, or NULL if
 * the listener table already contains the maximum number
 * (STM_LISTENER_LIMIT) of listeners.
 */
HIDDEN stm_listener *
stm_listener_add(const char *addr_string, stm_addr addr, stm_port port,
  int *new)
{
    stm_listener *lp, *ep;

    STM_TRACE(("%d: stm_listener_add(addr_string=\"%s\", addr=%#x, port=%u)\n",
      getpid(), addr_string, ntohl(addr), ntohs(port)));

    *new = 0;
    for (lp = stm.listeners, ep = lp + stm.num_listeners; lp < ep; lp++)
	if (lp->addr.sin_addr.s_addr == addr && lp->addr.sin_port == port)
	    return lp;
    if (stm.num_listeners < STM_LISTENER_LIMIT) {
	lp->addr_string = apr_pstrdup(stm.pool, addr_string);
	lp->addr.sin_family = AF_INET;
	lp->addr.sin_port = port;
	lp->addr.sin_addr.s_addr = addr;
	lp->unique = addr != htonl(INADDR_ANY);
	stm.num_listeners++;
	*new = 1;
	return lp;
    }

    return NULL;
}

/*
 * Adapt the given ap-listener so that the given stm-listener can be
 * used instead.  Returns nonzero on success, zero on failure.
 */
HIDDEN int
stm_listener_usurp(stm_listener *listener, ap_listen_rec *alr)
{
    int sd;

    STM_TRACE(("%d: stm_listener_usurp(listener=%p,#%d, alr=%p) share_count=%d\n",
      getpid(), listener, listener->id, alr, listener->share_count));

    /* the listener must have a valid address at this point */
    STM_ASSERT(listener->addr_string);

    listener->alr = alr;
    if (apr_get_os_sock(&sd, alr->sd) == APR_SUCCESS) {
	listener->sd = st_netfd_open_socket(sd);
	if (listener->sd != NULL) {
	    /* serialize accepts only on shared listeners */
	    if (listener->share_count <= 1 ||
	      st_netfd_serialize_accept(listener->sd) == 0) {
		stm_disable_nagle(listener->sd);
		return 1;
	    } else
		ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
		  "stm_listener_usurp: st_netfd_serialize_accept");
	} else
	    ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	      "stm_listener_usurp: st_netfd_open_socket");
    } else
	ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	  "stm_listener_usurp: apr_get_os_sock");

    return 0;
}

/*
 * Revert the given listener from STM control.
 */
HIDDEN void
stm_listener_revert(stm_listener *listener)
{
    STM_TRACE(("%d: stm_listener_revert(listener=%p,#%d)\n", getpid(),
      listener, listener->id));

    st_netfd_free(listener->sd);
    listener->sd = NULL;
}


/***********************************************************************
 * Threads
 */

/*
 * Initialize/reset the given thread which is bound to the given VP.
 */
HIDDEN void
stm_thread_init(stm_thread *thread, stm_vp *vp)
{
    STM_TRACE(("%d: stm_thread_init(thread=%p,#%d, vp=%p,#%d)\n", getpid(),
      thread, (int) (thread - vp->threads), vp, vp->id));

    thread->id = (int) (thread - vp->threads);
    STM_ASSERT(thread->id >= 0 && thread->id < STM_ST_LIMIT);
    thread->health = STM_HEALTH_DEAD;
    thread->st = 0;
    thread->vp = vp;
    thread->listener = NULL;
    thread->connected = 0;
    thread->connections = stm.config.thread_connections;
}

/*
 * Attach the given listener to the given thread and start the given
 * thread running.  Returns nonzero on success, zero on failure.
 */
HIDDEN int
stm_thread_start(stm_thread *thread, stm_listener *listener)
{
    STM_TRACE(("%d: stm_thread_start(thread=%p,#%d, listener=%p,#%d)\n",
      getpid(), thread, thread->id, listener, listener->id));

    /* it would be bad to clobber a running thread */
    STM_ASSERT(thread->health == STM_HEALTH_DEAD);

    thread->listener = listener;
    thread->st = st_thread_create(stm_thread_main, thread, 0,
	stm.config.stack_size);
    if (thread->st) {
	/*
	 * Note it is impossible for the new thread to run until this
	 * thread blocks.  No race conditions on startup.
	 */
	thread->health = STM_HEALTH_ALIVE;
	return 1;
    }

    ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
      "stm_thread_start: st_thread_create");
    return 0;
}

/*
 * Return the thread currently running, or NULL if called by a
 * non-thread entity (e.g., the master process or the primordial VP
 * thread).
 */
HIDDEN stm_thread *
stm_thread_self(void)
{
    STM_TRACE(("%d: stm_thread_self()\n", getpid()));

    /*
     * We could walk through all of STs on all of the VPs in stm looking
     * for a match for st_thread_self() but a faster way is to use
     * thread-specific data.  The catch with TSD is that the key must be
     * the same on all VPs, since we don't have a VP reference either.
     * Therefore the key must be allocated before any VP.
     */
    return (stm_thread *) st_thread_getspecific(stm.self_key);
}

/*
 * Thread main function.  Always returns NULL.  arg is a pointer to the
 * thread.
 */
HIDDEN void *
stm_thread_main(void *arg)
{
    stm_thread *thread;
    stm_vp *vp;
    stm_listener *lp;
    long id;
    stm_scoreboard *sbp;
    apr_int32_t *sbtcp, *sblcp;
    apr_pool_t *conn_pool;

    STM_TRACE(("%d: => stm_thread_main(arg=%p)\n", getpid(), arg));

    thread = (stm_thread *) arg;
    st_thread_setspecific(stm.self_key, thread);
    vp = thread->vp;
    lp = thread->listener;
    id = stm_id_make(vp->id, thread->id);
    sbp = vp->scoreboard;
    sbtcp = &sbp->thread_connections[thread->id];
    sblcp = &sbp->listener_connections[lp->id];
    sbp->thread_starts++;

    /*
     * Check health once before entering st_accept() to catch early stop
     * requests (see stm_thread_stop()).  Even if this ST has been
     * st_interrupt()ed st_accept() will return EINTR only if there is
     * no connection pending.  (ST I/O functions check for interrupts
     * only when about to block the thread.)
     */
    if (thread->health == STM_HEALTH_ALIVE &&
      apr_create_pool(&conn_pool, vp->pool) == APR_SUCCESS) {
	do {
	    struct sockaddr_in addr;
	    int addrlen;
	    st_netfd_t nsd;

	    /* get a connection */
	    addrlen = sizeof addr;
	    nsd = st_accept(lp->sd, (struct sockaddr *) &addr, &addrlen, -1);
	    if (nsd == NULL) {
		if (errno == EINTR)
		    break;	/* stm_thread_stop() called; terminate now */
		ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
		  "stm_thread_main: st_accept");
		if (st_sleep(1) == -1 && errno == EINTR)
		    break;
		continue;
	    }

	    /*
	     * I have a connection, so keep stm_thread_stop() from
	     * st_thread_interrupt()ing me now.
	     */
	    thread->connected = 1;

	    /*
	     * This thread is no longer "spare."  Start a replacement
	     * spare thread if necessary and allowed.
	     */
	    if (--lp->spare_threads < stm.config.min_spare_threads) {
		if (vp->num_threads < stm.config.max_threads) {
		    if (stm_thread_start(vp->dead_threads[vp->num_threads],
		      lp)) {
			vp->num_threads++;
			if (sbp->most_threads < vp->num_threads)
			    sbp->most_threads = vp->num_threads;
			lp->spare_threads++;
		    }
		} else if (!vp->max_threads_hit) {
		    vp->max_threads_hit = 1;	/* report once per VP */
		    ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO,
		      0, stm.server, "ran out of threads on VP %d; "
		      "increase " STM_CMD_MAX_THREADS " and/or " STM_CMD_NUM_VPS,
		      vp->pid);
		}
	    }

	    /* process the connection */
	    stm_thread_process_connection(lp, conn_pool, nsd, &addr, id);

	    /*
	     * Connection has been closed.  Clean up and return thread
	     * to spare state.
	     */
	    apr_clear_pool(conn_pool);
	    thread->connected = 0;
	    lp->spare_threads++;

	    /* update counters */
	    sbp->vp_connections++;	/* per VP */
	    (*sbtcp)++;			/* per thread */
	    (*sblcp)++;			/* per listener */

	    if (vp->connections > 0 && --vp->connections == 0) {
		/*
		 * This VP has processed as many connections as it's
		 * allowed.  Terminate the whole VP, including this
		 * thread, immediately and gently.
		 */
		stm_vp_stop_self(vp);
		break;
	    }

	    if (thread->connections > 0 && --thread->connections == 0) {
		/*
		 * This thread has processed as many consecutive
		 * connections as it's allowed.  Yield the VP to another
		 * thread.
		 */
		st_usleep(0);
		thread->connections = stm.config.thread_connections;
	    }

	/* check for stop requests, and for too many spare threads */
	} while (thread->health == STM_HEALTH_ALIVE &&
	  lp->spare_threads <= stm.config.max_spare_threads);

	apr_destroy_pool(conn_pool);
    }

    /* this thread is dying */
    lp->spare_threads--;
    vp->num_threads--;
    vp->dead_threads[vp->num_threads] = thread;
    sbp->thread_exits++;

    /* this thread is dead */
    stm_thread_init(thread, vp);

    STM_TRACE(("%d: <= stm_thread_main(arg=%p)\n", getpid(), arg));

    return NULL;
}

/*
 * Process a connection with a client.  Calls back into the main server
 * to process requests, etc.  On return, sd has been closed.
 */
HIDDEN void
stm_thread_process_connection(const stm_listener *listener,
  apr_pool_t *conn_pool, st_netfd_t sd, const struct sockaddr_in *raddr,
  long id)
{
    struct sockaddr_in laddr;
    const struct sockaddr_in *laddrp;

    STM_TRACE(("%d: stm_thread_process_connection(listener=%p,#%d, conn_pool=%p, sd=%p(%d), raddr=%s:%hu, id=%ld)\n",
      getpid(), listener, listener->id, conn_pool, sd, st_netfd_fileno(sd),
      inet_ntoa(raddr->sin_addr), ntohs(raddr->sin_port), id));

#if defined(IRIX) && IRIX >= 65
    /*
     * TCP_NODELAY is inherited from the accepting socket in Irix 6.5
     * and beyond.
     */
#else
    stm_disable_nagle(sd);
#endif

    if (listener->unique)
	laddrp = &listener->addr;
    else {
	int addrlen;

	addrlen = sizeof laddr;
	if (getsockname(st_netfd_fileno(sd), &laddr, &addrlen) == 0)
	    laddrp = &laddr;
	else {
	    ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
	      "stm_thread_process_connection: getsockname");
	    laddrp = NULL;
	}
    }

    if (laddrp) {
	ap_iol *iolp;

	/* make all I/O on this socket use ST's I/O functions */
	iolp = stiol_attach(sd);
	if (iolp) {
	    BUFF *bp;
	    conn_rec *conn;

	    bp = ap_bcreate(conn_pool, B_RDWR);
	    ap_bpush_iol(bp, iolp);
	    conn = ap_new_connection(conn_pool, stm.server, bp, raddr,
	      laddrp, id);
	    ap_process_connection(conn);
	    ap_lingering_close(conn);
	} else {
	    ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
	      "stm_thread_process_connection: stiol_attach");
	    st_netfd_close(sd);
	}
    } else
	st_netfd_close(sd);
}

/*
 * Ask the given thread to terminate.  Must be called only from within
 * the VP of the given thread.
 */
HIDDEN void
stm_thread_stop(stm_thread *thread)
{
    STM_TRACE(("%d: stm_thread_stop(thread=%p,#%d)\n", getpid(), thread,
      thread->id));
    STM_ASSERT(getpid() == thread->vp->pid);

    if (thread->health == STM_HEALTH_ALIVE) {
	thread->health = STM_HEALTH_DYING;

	/*
	 * break the thread out of st_accept() if necessary, and no
	 * other I/O function
	 */
	if (!thread->connected)
	    st_thread_interrupt(thread->st);
    }
}

/*
 * Return nonzero if the given thread has been terminated or asked to
 * terminate.
 */
HIDDEN int
stm_thread_stopped(stm_thread *thread)
{
    STM_TRACE(("%d: stm_thread_stopped(thread=%p,#%d)\n", getpid(), thread,
      thread->id));

    return thread->health != STM_HEALTH_ALIVE;
}


/***********************************************************************
 * VPs
 */

/*
 * Initialize/reset the given VP.  Returns nonzero on success, zero on
 * failure.
 */
HIDDEN int
stm_vp_init(stm_vp *vp)
{
    int i;

    STM_TRACE(("%d: stm_vp_init(vp=%p,#%d)\n", getpid(), vp,
      (int) (vp - stm.vps)));

    vp->id = (int) (vp - stm.vps);
    STM_ASSERT(vp->id >= 0 && vp->id < STM_VP_LIMIT);
    vp->pid = -1;
    vp->health = STM_HEALTH_DEAD;
    vp->pool = NULL;
    vp->stop_pipe[0] = -1;
    vp->stop_pipe[1] = -1;
    /* leave vp->num_listeners and vp->listeners alone */
    vp->connections = stm.config.vp_connections;
    vp->max_threads_hit = 0;
    vp->num_threads = 0;
    for (i = 0; i < STM_ST_LIMIT; i++) {
	stm_thread_init(&vp->threads[i], vp);
	vp->dead_threads[i] = &vp->threads[i];
    }

    if (vp->scoreboard == NULL) {
	/*
	 * create scoreboard even if !stm.config.connection_status
	 * because we may want them after a restart and we keep other
	 * stats in there
	 */
	vp->scoreboard = stm_scoreboard_new(vp->id);
	if (vp->scoreboard == NULL)
	    return 0;
    }
    stm_scoreboard_init(vp->scoreboard);

    return 1;
}

/*
 * Initialize the listeners for the given VP from the stm.listeners and
 * stm.config.vp_listen tables.  Returns nonzero on success, zero on
 * failure.
 */
HIDDEN int
stm_vp_listen(stm_vp *vp)
{
    int i;

    STM_TRACE(("%d: stm_vp_listen(vp=%p,#%d)\n", getpid(), vp, vp->id));

    if (stm.config.vp_listen
#ifdef STM_DEBUG
      && !stm.debug.one_process
#endif
      ) {
	const stm_config_vp_listen *vlp;

	/*
	 * VPListen directives were used.  Each VP listens only to its
	 * designated listeners.
	 */

	vlp = &stm.config.vp_listen[vp->id];
	for (i = 0; i < vlp->num_listeners; i++) {
	    vp->listeners[i] = vlp->listeners[i];
	    vp->listeners[i]->share_count++;
	}
	vp->num_listeners = i;
	while (i < STM_LISTENER_LIMIT)
	    vp->listeners[i++] = NULL;

	if (vp->num_listeners < 1) {
	    ap_log_error(APLOG_MARK,
	      APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO, 0, stm.server,
	      "no listeners for virtual processor %d", vp->id);
	    return 0;
	}
    } else {
	/*
	 * Listen directives were used, or no Listen or VPListen
	 * directives were used at all.  Each VP listens to all
	 * listeners.
	 */

	vp->num_listeners = stm.num_listeners;
	for (i = 0; i < vp->num_listeners; i++) {
	    vp->listeners[i] = &stm.listeners[i];
	    vp->listeners[i]->share_count++;
	}
	while (i < STM_LISTENER_LIMIT)
	    vp->listeners[i++] = NULL;
    }

    /* this VP must have at least one listener at this point */
    STM_ASSERT(vp->num_listeners > 0);

    return 1;
}

/*
 * Start the given VP running.  Returns nonzero on success, zero on
 * failure.
 */
HIDDEN int
stm_vp_start(stm_vp *vp)
{
    pid_t pid;

    STM_TRACE(("%d: stm_vp_start(vp=%p,#%d)\n", getpid(), vp, vp->id));

    /* it would be bad to clobber a running VP */
    STM_ASSERT(vp->health == STM_HEALTH_DEAD);

#ifdef STM_DEBUG
    if (stm.debug.one_process) {
	vp->pid = getpid();
	stm_vp_bind(vp);
	stm_vp_main(vp);
	/*NOTREACHED*/
	return 0;
    }
#endif

    pid = fork();
    switch (pid) {
    case -1:
	ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	  "stm_vp_start: fork");
	sleep(10);	/* avoid thrashing the system */
	return 0;
    case 0:
	vp->pid = getpid();
	stm_vp_bind(vp);	/* in case child runs first */
	stm_vp_main(vp);
	/*NOTREACHED*/
	exit(APEXIT_CHILDFATAL);
	break;
    default:
	vp->pid = pid;
	stm_vp_bind(vp);	/* in case parent runs first */
	vp->health = STM_HEALTH_ALIVE;
	break;
    }

    return 1;
}

/*
 * Bind the given VP to the CPU to which it should be bound, if any.
 */
#if defined(IRIX) && IRIX >= 50
# include <sys/sysmp.h>
#endif
HIDDEN void
stm_vp_bind(stm_vp *vp)
{
    STM_TRACE(("%d: stm_vp_bind(vp=%p,#%d)\n", getpid(), vp, vp->id));

    /* we need the VP's pid to bind it */
    STM_ASSERT(vp->pid > 0);

    if (stm.config.vp_bind) {
	int cpu;

	cpu = stm.config.vp_bind[vp->id];
	if (cpu >= 0 && vp->scoreboard->cpu != cpu) {
#if defined(IRIX) && IRIX >= 50
	    /* only Irix 5.0 and beyond have sysmp(MP_MUSTRUN_PID) */
	    if (sysmp(MP_MUSTRUN_PID, cpu, vp->pid) != -1)
		vp->scoreboard->cpu = cpu;
	    else
		ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
		  "cannot bind process %d to cpu %d: sysmp(MP_MUSTRUN_PID)",
		  vp->pid, cpu);
#else
	    ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0,
	      stm.server, "don't know how to bind process %d to cpu %d",
	      vp->pid, cpu);
#endif
	}
    }
}

/*
 * Establish signal dispositions for VP processes.
 */
HIDDEN void
stm_vp_signals(void)
{
    STM_TRACE(("%d: stm_vp_signals()\n", getpid()));

    /* adjust signal dispositions this VP inherited from the master process */

    stm_signal_default(SIGTERM);
#ifdef SIGINT
    stm_signal_default(SIGINT);
#endif
    stm_signal_catch(SIGHUP, stm_vp_onsig_stop);
    stm_signal_ignore(SIGWINCH);
}

/*
 * VP main function.  Does not return.
 */
HIDDEN void
stm_vp_main(stm_vp *vp)
{
    st_netfd_t stop_fd;
    int i;

    STM_TRACE(("%d: => stm_vp_main(vp=%p,#%d)\n", getpid(), vp, vp->id));

    /*
     * Set stm.num_vps to our VP index so asynchronous global functions
     * like stm_vp_onsig_stop() and stm_vp_onexit() have a handle on the
     * current VP.
     */
    stm.num_vps = vp->id;

    vp->health = STM_HEALTH_ALIVE;
    vp->scoreboard->pid = vp->pid;
    vp->scoreboard->vp_start_time = apr_now();
    vp->scoreboard->incarnations++;

    if (unixd_setup_child() != 0)
	exit(APEXIT_CHILDINIT);

    /*
     * Create the stop pipe, a safe means for this VP to signal
     * asynchronous events.
     */
    if (pipe(vp->stop_pipe) < 0) {
	ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	  "stm_vp_main: pipe");
	exit(APEXIT_CHILDINIT);
    }
    stop_fd = st_netfd_open(vp->stop_pipe[0]);
    if (stop_fd == NULL) {
	ap_log_error(APLOG_MARK, APLOG_ERR, errno, stm.server,
	  "stm_vp_main: st_netfd_open");
	exit(APEXIT_CHILDINIT);
    }

    /*
     * Create a pool that endures for the life of this VP, run child
     * init hooks on it, and use it to close both ends of the stop_pipe
     * when execing another program.
     */
    apr_create_pool(&vp->pool, stm.pool);
    ap_child_init_hook(vp->pool, stm.server);
    apr_register_cleanup(vp->pool, vp, apr_null_cleanup, stm_vp_cleanup);
    atexit(stm_vp_onexit);

    /* Start the requested number of spare threads per listener. */
    for (i = 0; i < vp->num_listeners && stm.state == STM_STATE_RUN; i++) {
	stm_listener *lp;
	int start_error;

	lp = vp->listeners[i];
	start_error = 0;

	apr_snprintf(vp->scoreboard->listeners[lp->id],
	  sizeof vp->scoreboard->listeners[lp->id], "%s:%u",
	  lp->addr_string, ntohs(lp->addr.sin_port));

	while (lp->spare_threads < stm.config.start_threads &&
	  vp->num_threads < stm.config.max_threads) {
	    if (stm_thread_start(&vp->threads[vp->num_threads], lp)) {
		vp->num_threads++;
		lp->spare_threads++;
	    } else {
		start_error = 1;
		break;
	    }
	}
	if (vp->scoreboard->most_threads < vp->num_threads)
	    vp->scoreboard->most_threads = vp->num_threads;
	if (lp->spare_threads < stm.config.start_threads) {
	    /*
	     * We didn't make all the threads we wanted to.  Either we
	     * exceeded the total number of threads per VP or a thread
	     * failed to start.  Warn, once, about the former condition.
	     */
	    if (!start_error && !vp->max_threads_hit) {
		STM_ASSERT(vp->num_threads >= stm.config.max_threads);
		vp->max_threads_hit = 1;    /* report once per VP */
		ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0,
		  stm.server, "ran out of threads on VP %d; "
		  "increase " STM_CMD_MAX_THREADS,
		  vp->pid);
	    }

	    /* Also warn about too-few threads per listener. */
	    ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0,
	      stm.server,
	      "started only %d out of %d thread(s) listening to %s on virtual processor %d",
	      lp->spare_threads, stm.config.start_threads,
	      vp->scoreboard->listeners[lp->id], vp->pid);
	}
    }

    stm_vp_signals();
    if (stm.state != STM_STATE_RUN) {
	/* caught signal on parent's handler: do the right thing */
	exit(APEXIT_CHILDINIT);
    }

    if (vp->num_threads > 0) {
	char c;

	/*
	 * Block here for data on the pipe.  This st_read() returns only
	 * when the pipe has data on it (see stm_vp_stop_self()) and
	 * that means it's time to shut down all the threads.
	 */
	st_read(stop_fd, &c, sizeof c, -1);
	for (i = 0; i < STM_ST_LIMIT; i++)
	    stm_thread_stop(&vp->threads[i]);
    }

    /* this VP is dying */
    st_netfd_close(stop_fd);
    close(vp->stop_pipe[1]);
    STM_TRACE(("%d: <= stm_vp_main(vp=%p,#%d)\n", getpid(), vp, vp->id));
    st_thread_exit(NULL);
    /*NOTREACHED*/
}

/*
 * Ask the given VP to terminate.  Must be called only by the master
 * process.
 */
HIDDEN void
stm_vp_stop(stm_vp *vp)
{
    STM_TRACE(("%d: stm_vp_stop(vp=%p,#%d)\n", getpid(), vp, vp->id));
    STM_ASSERT(stm.pid == getpid());

    if (vp->health == STM_HEALTH_ALIVE) {
	vp->health = STM_HEALTH_DYING;
	STM_ASSERT(vp->pid > 0);
	kill(vp->pid, SIGHUP);
    }
}

/*
 * Ask the given VP to terminate.  Must be called only from within the
 * given VP.
 */
HIDDEN void
stm_vp_stop_self(stm_vp *vp)
{
    char c;

    STM_TRACE(("%d: stm_vp_stop_self(vp=%p,#%d)\n", getpid(), vp, vp->id));
    STM_ASSERT(vp->pid == getpid());

    if (vp->health == STM_HEALTH_ALIVE) {
	vp->health = STM_HEALTH_DYING;
	write(vp->stop_pipe[1], &c, sizeof c);
    }
}

/*
 * Signal handler for a VP process:  Terminate this VP.
 */
HIDDEN void
stm_vp_onsig_stop(int sig)
{
    STM_TRACE(("%d: stm_vp_onsig_stop(sig=%d)\n", getpid(), sig));

    stm_vp_stop_self(&stm.vps[stm.num_vps]);
}

/*
 * Atexit handler for a VP process:  Destroy the VP's pool.
 */
HIDDEN void
stm_vp_onexit(void)
{
    STM_TRACE(("%d: stm_vp_onexit()\n", getpid()));

    apr_destroy_pool(stm.vps[stm.num_vps].pool);
}

/*
 * Clean up the VP to prepare to exec another program.
 */
HIDDEN apr_status_t
stm_vp_cleanup(void *arg)
{
    stm_vp *vp;

    vp = (stm_vp *) arg;
    STM_TRACE(("%d: stm_vp_cleanup(vp=%p,#%d)\n", getpid(), vp, vp->id));

    close(vp->stop_pipe[0]);
    close(vp->stop_pipe[1]);

    return APR_SUCCESS;
}


/***********************************************************************
 * Main STM
 */

/*
 * Initialize/reset the STM MPM.  Returns nonzero on success, zero on
 * failure.
 */
HIDDEN int
stm_init(apr_pool_t *pool, server_rec *server)
{
    int first;
    int i;
    ap_listen_rec *alp;

    STM_TRACE(("%d: stm_init(pool=%p, server=%p) #%d\n", getpid(), pool,
      server, stm.initialized));

    /* verify all configuration parameters' bounds checks */
    STM_ASSERT(stm.config.num_vps > 0 && stm.config.num_vps <= STM_VP_LIMIT);
    STM_ASSERT(stm.config.start_threads > 0 &&
      stm.config.start_threads <= STM_ST_LIMIT);
    STM_ASSERT(stm.config.min_spare_threads > 0 &&
      stm.config.min_spare_threads <= STM_ST_LIMIT);
    STM_ASSERT(stm.config.max_spare_threads > 0 &&
      stm.config.max_spare_threads <= STM_ST_LIMIT);
    STM_ASSERT(stm.config.max_threads > 0 &&
      stm.config.max_threads <= STM_ST_LIMIT);

    /* first is nonzero to initialize, zero to reset */
    first = stm.initialized++ == 0;

    if (first) {
	int maxfds;

	stm.start_time = apr_now();

	/* initialize the state threads library */
	if (st_init() != 0) {
	    ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server,
	      "cannot initialize state threads library");
	    return 0;
	}

	/*
	 * Are there enough file descriptors for max_threads
	 * simultaneous connections per VP?
	 */
	maxfds = st_getfdlimit() - 10;	/* or so */
	if (stm.config.max_threads > maxfds) {
	    ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, server,
	      STM_CMD_MAX_THREADS " %d exceeds "
	      "number of file descriptors per process %d; "
	      "decrease " STM_CMD_MAX_THREADS " and compensate "
	      "by increasing " STM_CMD_NUM_VPS,
	      stm.config.max_threads, maxfds);
	    return 0;
	}

	st_timecache_set(1);
    }

    /*
     * Initialize/reset each member of stm not already initialized/reset
     * by stm_pre_config() and the config routines.
     */
    stm.pool = pool;
    stm.server = server;
    stm.state = STM_STATE_RUN;
    /*
     * leave alone:
     *	stm.initialized (incremented above)
     *	stm.num_restarts (stm_pre_config())
     *	stm.pid (stm_pre_config())
     *	stm.config (stm_pre_config())
     *	stm.num_listeners (stm_pre_config())
     *	stm.listeners (stm_pre_config())
     */
    stm.num_vps = 0;
    for (i = 0; i < STM_VP_LIMIT; i++)
	if (!stm_vp_init(&stm.vps[i]))
	    return 0;
    if (first && st_key_create(&stm.self_key, NULL) == -1) {
	ap_log_error(APLOG_MARK, APLOG_CRIT, errno, server,
	  "stm_init: st_key_create");
	return 0;
    }
    /* leave stm.debug alone (stm_pre_config()) */

    /* establish AP listeners and convert them to STM listeners */
    if (ap_listeners == NULL) {
	int new;

	/*
	 * There were no Listen or VPListen directives so
	 * ap_setup_listeners() will add a default AP listener (yecch).
	 * Add a matching STM listener.
	 */
	STM_ASSERT(stm.num_listeners == 0);
	stm_listener_add(APR_ANYADDR, htonl(INADDR_ANY), htons(server->port),
	  &new);
	STM_ASSERT(new);
    }
    STM_ASSERT(stm.num_listeners > 0);
    for (i = 0; i < stm.config.num_vps; i++)
	if (!stm_vp_listen(&stm.vps[i]))
	    return 0;
    if (ap_setup_listeners(server) > 0) {
	for (alp = ap_listeners; alp; alp = alp->next) {
	    char *addr_string;
	    struct in_addr addr;
	    stm_port port;
	    stm_listener *lp, *ep;

	    /* find the STM listener for the AP listener */

	    if (apr_get_local_ipaddr(&addr_string, alp->sd) != APR_SUCCESS ||
	      !inet_aton(addr_string, &addr))
		addr.s_addr = htonl(INADDR_ANY);
	    if (apr_get_local_port(&port, alp->sd) != APR_SUCCESS)
		port = server->port;
	    port = htons(port);

	    for (lp = stm.listeners, ep = lp + stm.num_listeners; lp < ep;
	      lp++) {
		if (lp->addr.sin_addr.s_addr == addr.s_addr &&
		  lp->addr.sin_port == port) {
		    if (stm_listener_usurp(lp, alp))
			break;
		    else {
			ap_log_error(APLOG_MARK,
			  APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO,
			  0, server,
			  "cannot initialize listen socket for %s:%u",
			  addr_string, ntohs(port));
			return 0;
		    }
		}
	    }

	    /*
	     * Every AP listener must have an STM listener (and vice
	     * versa, which we check below).
	     */
	    STM_ASSERT(lp < ep);
	}
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_STARTUP | APLOG_NOERRNO,
	  0, server, "no listening sockets available");
        return 0;
    }

    if (stm.config.min_spare_threads > stm.config.max_spare_threads)
	stm.config.min_spare_threads = stm.config.max_spare_threads;

    /*
     * Warn if the number of threads per VP needed to satisfy all the
     * listeners exceeds the maximum.  (Other MPM's measure
     * start/min-spare/max-spare threads per child process but STM
     * measures them per listener so these warnings may catch
     * configuration mistakes caused by the different semantics.)
     */
    for (i = 0; i < stm.config.num_vps; i++) {
	int nl = stm.vps[i].num_listeners;

	if (nl * stm.config.start_threads > stm.config.max_threads)
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server,
	      "too few threads: %d " STM_CMD_START_THREADS
	      " for each of %d listener(s) on virtual processor %d exceeds limit of %d "
	      STM_CMD_MAX_THREADS "; decrease " STM_CMD_START_THREADS
	      " or increase " STM_CMD_MAX_THREADS,
	      stm.config.start_threads, nl, i, stm.config.max_threads);
	if (nl * stm.config.min_spare_threads > stm.config.max_threads)
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server,
	      "too few threads: %d " STM_CMD_MIN_SPARE_THREADS
	      " for each of %d listener(s) on virtual processor %d exceeds limit of %d "
	      STM_CMD_MAX_THREADS "; decrease " STM_CMD_MIN_SPARE_THREADS
	      " or increase " STM_CMD_MAX_THREADS,
	      stm.config.min_spare_threads, nl, i, stm.config.max_threads);
	if (nl * stm.config.max_spare_threads > stm.config.max_threads)
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server,
	      "too few threads: %d " STM_CMD_MAX_SPARE_THREADS
	      " for each of %d listener(s) on virtual processor %d exceeds limit of %d "
	      STM_CMD_MAX_THREADS "; decrease " STM_CMD_MAX_SPARE_THREADS
	      " or increase " STM_CMD_MAX_THREADS,
	      stm.config.max_spare_threads, nl, i, stm.config.max_threads);
    }

    /* Warn about CPU- or listen-binding for unused VPs */
    for (i = stm.config.num_vps; i < STM_VP_LIMIT; i++) {
	if (stm.config.vp_bind && stm.config.vp_bind[i] != -1)
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server,
	      STM_CMD_VP_BIND " directive(s) for unused virtual processor %d ignored: "
	      STM_CMD_NUM_VPS " is %d",
	      i, stm.config.num_vps);
	if (stm.config.vp_listen && stm.config.vp_listen[i].num_listeners > 0)
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server,
	      STM_CMD_VP_LISTEN " directive(s) for unused virtual processor %d ignored: "
	      STM_CMD_NUM_VPS " is %d",
	      i, stm.config.num_vps);
    }

    /*
     * Warn about unused listeners.  May be redundant with above
     * warnings about VPListens for unused VPs but want to make sure the
     * user knows nobody is listening to a particular address.
     */
    for (i = 0; i < stm.num_listeners; i++) {
	STM_ASSERT(stm.listeners[i].alr && stm.listeners[i].sd);
	if (stm.listeners[i].share_count == 0)
	    ap_log_error(APLOG_MARK,
	      APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO, 0, server,
	      "no virtual processors serve %s:%u",
	      stm.listeners[i].addr_string,
	      ntohs(stm.listeners[i].addr.sin_port));
    }

    return 1;
}

/*
 * Finalize the STM in preparation for server restart or shutdown.
 */
HIDDEN void
stm_fini(void)
{
    int i;

    STM_TRACE(("%d: stm_fini()\n", getpid()));

    for (i = 0; i < stm.num_listeners; i++)
	stm_listener_revert(&stm.listeners[i]);
}

/*
 * Establish signal dispositions for the master process (and embryonic
 * VP processes).
 */
HIDDEN void
stm_signals(void)
{
    STM_TRACE(("%d: stm_signals()\n", getpid()));

#ifdef SIGSEGV
    stm_signal_core(SIGSEGV);
#endif
#ifdef SIGBUS
    stm_signal_core(SIGBUS);
#endif
#ifdef SIGABORT
    stm_signal_core(SIGABORT);
#endif
#ifdef SIGABRT
    stm_signal_core(SIGABRT);
#endif
#ifdef SIGILL
    stm_signal_core(SIGILL);
#endif

    stm_signal_catch(SIGTERM, stm_onsig_shutdown);
#ifdef SIGINT
    stm_signal_catch(SIGINT, stm_onsig_shutdown);
#endif
#ifdef SIGXCPU
    stm_signal_default(SIGXCPU);
#endif
#ifdef SIGXFSZ
    stm_signal_default(SIGXFSZ);
#endif
#ifdef SIGPIPE
    stm_signal_ignore(SIGPIPE);
#endif

    stm_signal_catch(SIGHUP, stm_onsig_restart);
    stm_signal_catch(SIGWINCH, stm_onsig_restart);
}

/*
 * Master process main function.  Returns when server restart or
 * shutdown has been requested.
 */
HIDDEN void
stm_main(void)
{
    STM_TRACE(("%d: => stm_main()\n", getpid()));

    stm_signals();

    /* start VPs */
    while (stm.state == STM_STATE_RUN &&
      stm.num_vps < stm.config.num_vps &&
      stm_vp_start(&stm.vps[stm.num_vps]))
	stm.num_vps++;
    if (stm.state == STM_STATE_RUN && stm.num_vps < stm.config.num_vps)
	ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP | APLOG_NOERRNO,
	  0, stm.server, "started only %d out of %d virtual processor(s)",
	  stm.num_vps, stm.config.num_vps);

    /* wait for shutdown or restart, restarting VPs as needed */
    if (stm.num_vps > 0) {
	while (stm.state == STM_STATE_RUN) {
	    pid_t pid;
	    int status;

	    pid = wait(&status);
	    if (pid > 0)
		stm_reap(pid, status);
	}
    } else
	stm.state = STM_STATE_SHUTDOWN;

    STM_TRACE(("%d: <= stm_main(), state %d\n", getpid(), stm.state));
}

/*
 * Reap a child process.  If the process was a VP, start a replacement VP.
 */
HIDDEN void
stm_reap(pid_t pid, int status)
{
    stm_grief grief;

    STM_TRACE(("%d: stm_reap(pid=%d, status=%#x)\n", getpid(), pid, status));

    grief = stm_mourn(pid, status);
    if (grief != STM_GRIEF_FATAL) {
	int i;

	for (i = 0; i < STM_VP_LIMIT; i++)
	    if (stm.vps[i].pid == pid)
		break;
	if (i < STM_VP_LIMIT) {
	    /* this was a VP; finish killing it */
	    stm.num_vps--;
	    stm_vp_init(&stm.vps[i]);

	    /* replace it */
	    if (stm.num_vps < stm.config.num_vps &&
	      stm_vp_start(&stm.vps[i])) {
		stm.num_vps++;
		if (grief != STM_GRIEF_NORMAL)
		    sleep(5);	/* avoid thrashing */
	    }
	}
    } else
	stm.state = STM_STATE_SHUTDOWN;
}

/*
 * Evaluate the seriousness of a child process's death and log a warning if
 * appropriate.  Returns:
 *  STM_GRIEF_NORMAL if the death was expected (e.g., voluntary VP death
 *	after processing its allowed number of connections) or irrelevent
 *	(e.g., not a VP)
 *  STM_GRIEF_FATAL if the death was catastrophic (e.g., VP botched an
 *	assertion) and the server should terminate too
 *  STM_GRIEF_SURPRISE if the death was neither expected nor fatal (e.g.,
 *	VP failed to initialize)
 */
HIDDEN stm_grief
stm_mourn(pid_t pid, int status)
{
    stm_grief grief;

    STM_TRACE(("%d: stm_mourn(pid=%d, status=%#x)\n", getpid(), pid, status));

    grief = STM_GRIEF_NORMAL;
    if (WIFEXITED(status)) {
	int ev = WEXITSTATUS(status);

	switch (ev) {
	case 0:
	    break;
	case APEXIT_CHILDFATAL:
	    ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, stm.server,
	      "child %d returned a fatal error; shutting down", pid);
	    grief = STM_GRIEF_FATAL;
	    break;
	case APEXIT_CHILDINIT:
	    ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, stm.server,
	      "child %d failed to initialize", pid);
	    grief = STM_GRIEF_SURPRISE;
	    break;
	default:
	    ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, stm.server,
	      "child %d exited with unexpected status %d", pid, ev);
	    grief = STM_GRIEF_SURPRISE;
	    break;
	}
    } else if (WIFSIGNALED(status)) {
	int sig = WTERMSIG(status);

	switch (sig) {
#ifdef STM_DEBUG
	case SIGABRT:
	    ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, 0, stm.server,
	      "child %d aborted, possible assertion botch; shutting down",
	      pid);
	    grief = STM_GRIEF_FATAL;
	    break;
#endif
	case SIGTERM:
	case SIGHUP:
	case SIGUSR1:
	case SIGKILL:
	    break;
	default:
	    ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0,
	      stm.server,
	      "child pid %d exit signal %d"
#ifdef SYS_SIGLIST
	      " (%s)"
#endif
	      , pid, sig
#ifdef SYS_SIGLIST
	      , (sig < NumSIG) ? SYS_SIGLIST[sig] : "?"
#endif
	      );
	    if (WCOREDUMP(status))
		ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0,
		  stm.server, "-- possible core dump in %s",
		  stm.config.core_dir);
#ifdef STM_DEBUG
	    grief = STM_GRIEF_FATAL;
#else
	    grief = STM_GRIEF_SURPRISE;
#endif
	    break;
	}
    }

    return grief;
}

/*
 * Wait a limited amount of time for all VPs to terminate.  Kill them if
 * they take too long to terminate themselves.  The caller must have
 * already commenced their termination.
 */
HIDDEN void
stm_doomsday(void)
{
    int tries;
    st_utime_t waittime;

    STM_TRACE(("%d: stm_doomsday()\n", getpid()));

    /* wait increasing intervals but not forever */
    for (tries = 0, waittime = 10000;
      stm.num_vps > 0 && tries <= 5;
      tries++, waittime *= 4) {
	stm_vp *vp;

	st_usleep(waittime);

	/* see who's dead */
	for (vp = stm.vps; vp < &stm.vps[STM_VP_LIMIT]; vp++) {
	    if (vp->health != STM_HEALTH_DEAD) {
		pid_t pid;
		int status;

		STM_ASSERT(vp->pid > 0);
		pid = waitpid(vp->pid, &status, WNOHANG);
		if (pid == vp->pid || pid == -1) {
		    if (pid > 0)
			stm_mourn(pid, status);
		    vp->health = STM_HEALTH_DEAD;
		    stm.num_vps--;
		    /*
		     * don't need to stm_vp_init(vp) because mpm is
		     * exiting and will be reinitialized later if
		     * necessary
		     */
		} else {
		    switch (tries) {
		    case 0:	/* 10 ms since doomsday */
		    case 1:	/* 50 ms since doomsday */
			break;
		    case 2:	/* 210 ms since doomsday */
		    case 3:	/* 850 ms since doomsday */
			ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO,
			  0, stm.server,
			  "waiting for process %d to die, trying SIGTERM",
			  vp->pid);
			kill(vp->pid, SIGTERM);
			break;
		    case 4:	/* 3.4 sec since doomsday */
			ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO,
			  0, stm.server,
			  "waiting for process %d to die, trying SIGKILL",
			  vp->pid);
			kill(vp->pid, SIGKILL);
			break;
		    default:	/* >= 14 sec since doomsday */
			ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO,
			  0, stm.server,
			  "could not make process %d die, giving up",
			  vp->pid);
			break;
		    }
		}
	    }
	}
    }
}


/***********************************************************************
 * Signal helper routines
 */

/*
 * Direct the given signal to the given handler in a platform-neutral
 * way.
 */
HIDDEN void
stm_signal_catch(int sig, void (*handler)(int))
{
#ifdef NO_USE_SIGACTION
    STM_TRACE(("%d: stm_signal_catch(sig=%d, handler=%p)\n", getpid(), sig,
      handler));

    signal(sig, handler);
#else
    struct sigaction sa;

    STM_TRACE(("%d: stm_signal_catch(sig=%d, handler=%p)\n", getpid(), sig,
      handler));

    sa.sa_flags = 0;
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(sig, &sa, NULL) != 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
	  "stm_signal_catch: sigaction(%d)", sig);
#endif
}

/*
 * Set the given signal to its default disposition.
 */
HIDDEN void
stm_signal_default(int sig)
{
    stm_signal_catch(sig, SIG_DFL);
}

/*
 * Set the given signal to be ignored.
 */
HIDDEN void
stm_signal_ignore(int sig)
{
    stm_signal_catch(sig, SIG_IGN);
}

/*
 * Set the given signal to dump core in the designated core dump
 * directory.
 */
HIDDEN void
stm_signal_core(int sig)
{
#ifdef NO_USE_SIGACTION
    STM_TRACE(("%d: stm_signal_core(sig=%d)\n", getpid(), sig));

    signal(sig, stm_onsig_core);
#else
    struct sigaction sa;

    STM_TRACE(("%d: stm_signal_core(sig=%d)\n", getpid(), sig));

# if defined(SA_ONESHOT)
    sa.sa_flags = SA_ONESHOT;
# elif defined(SA_RESETHAND)
    sa.sa_flags = SA_RESETHAND;
# else
    sa.sa_flags = 0;
# endif
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = stm_onsig_core;
    if (sigaction(sig, &sa, NULL) != 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
	  "stm_signal_core: sigaction(%d)", sig);
#endif
}


/***********************************************************************
 * Signal handlers
 */

/*
 * Dump core in the designated core dump directory.
 */
HIDDEN void
stm_onsig_core(int sig)
{
    STM_TRACE(("%d: stm_onsig_core(sig=%d)\n", getpid(), sig));

    if (stm.config.core_dir)
	chdir(stm.config.core_dir);
    stm_signal_default(sig);
    kill(getpid(), sig);
}

/*
 * Begin server restart.  Only SIGWINCH uses graceful restart.
 */
HIDDEN void
stm_onsig_restart(int sig)
{
    STM_TRACE(("%d: stm_onsig_restart(sig=%d)\n", getpid(), sig));

    stm.state = (sig == SIGWINCH) ? STM_STATE_GRACEFUL : STM_STATE_RESTART;
}

/*
 * Begin server shutdown.
 */
HIDDEN void
stm_onsig_shutdown(int sig)
{
    STM_TRACE(("%d: stm_onsig_shutdown(sig=%d)\n", getpid(), sig));

    stm.state = STM_STATE_SHUTDOWN;
}


/***********************************************************************
 * Miscellany
 */

/*
 * Convert the given VP and thread indices to a "connection identifier"
 * for mod_status.
 */
HIDDEN long
stm_id_make(int v, int t)
{
    STM_TRACE(("%d: stm_id_make(v=%d, t=%d)\n", getpid(), v, t));

    STM_ASSERT(v >= 0 && v < STM_VP_LIMIT);
    STM_ASSERT(t >= 0 && t < STM_ST_LIMIT);

    return (long) v * STM_ST_LIMIT + t;
}

/*
 * Convert the given connection identifier into VP and thread indices,
 * if within bounds.  Returns nonzero on success, zero on failure.
 */
HIDDEN int
stm_id_break(long id, int *vp, int *tp)
{
    int v, t;

    STM_TRACE(("%d: stm_id_break(id=%ld)\n", getpid(), id));

    v = id / STM_ST_LIMIT;
    t = id % STM_ST_LIMIT;
    if (v >= 0 && v < STM_VP_LIMIT && t >= 0 && t < STM_ST_LIMIT) {
	*vp = v;
	*tp = t;
	return 1;
    }
    return 0;
}

/*
 * Disable the Nagle algorithm on a TCP socket.
 */
HIDDEN void
stm_disable_nagle(st_netfd_t fd)
{
    int on;

    STM_TRACE(("%d: stm_disable_nagle(fd=%p(%d))\n", getpid(), fd,
      st_netfd_fileno(fd)));

    on = 1;
    if (setsockopt(st_netfd_fileno(fd), IPPROTO_TCP, TCP_NODELAY,
      (char *) &on, sizeof on) < 0)
	ap_log_error(APLOG_MARK, APLOG_WARNING, errno, stm.server,
	  "stm_disable_nagle: setsockopt(%d, TCP_NODELAY)",
	  st_netfd_fileno(fd));
}


/***********************************************************************
 * Exported API routines
 */

/*
 * Begin server shutdown.
 */
void
ap_start_shutdown(void)
{
    STM_TRACE(("%d: ap_start_shutdown() STM\n", getpid()));

    kill(stm.pid, SIGTERM);
}

/*
 * Begin server restart.
 */
void
ap_start_restart(int graceful)
{
    STM_TRACE(("%d: ap_start_restart(graceful=%d) STM\n", getpid(), graceful));

    kill(stm.pid, graceful ? SIGWINCH : SIGHUP);
}

/*
 * Returns nonzero if the loop processing requests on a connection
 * should prematurely terminate.  This is the actual usage which belies
 * the name of this predicate function.
 */
int
ap_graceful_stop_signalled(void)
{
    STM_TRACE(("%d: ap_graceful_stop_signalled() STM\n", getpid()));

    /*
     * This is called from a ST on a VP via ap_process_http_connection()
     * so we must ask our thread if it has been requested to stop.
     * Doesn't matter if the whole server is going down, just that this
     * thread is, so the server should abort the connection ASAP.
     */
    return stm_thread_stopped(stm_thread_self());
}

/*
 * Clear all state about the connection with the given ID.
 */
void
ap_reset_connection_status(long id)
{
    int v, t;

    STM_TRACE(("%d: ap_reset_connection_status(id=%ld) STM\n", getpid(), id));

    if (!stm.config.connection_status)
	return;

    if (stm_id_break(id, &v, &t)) {
	stm_score *sp, *ep;

	/* We should be running on VP v but it's not strictly necessary */

	for (sp = stm.vps[v].scoreboard->scores[t],
	  ep = sp + STM_SCORE_LIMIT; sp < ep; sp++)
	    sp->key[0] = 0;
    } else
	ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0,
	  stm.server, "ap_reset_connection_status (STM): bogus id %ld", id);
}

/*
 * Associate the given key/value state with the connection with the
 * given ID.  Keys and values may be silently truncated if they exceed
 * STM_SCORE_KEY_SIZE or STM_SCORE_VALUE_SIZE respectively.  If state
 * with the given key already exists for this connection the new value
 * replaces the old.
 */
void
ap_update_connection_status(long id, const char *key, const char *value)
{
    int v, t;

    STM_TRACE(("%d: ap_update_connection_status(id=%ld, key=\"%s\", value=\"%s\") STM\n",
      getpid(), id, key, value));

    if (!stm.config.connection_status)
	return;

    if (stm_id_break(id, &v, &t)) {
	stm_score *sp, *ep;

	/* We should be running on VP v but it's not strictly necessary */

	for (sp = stm.vps[v].scoreboard->scores[t],
	  ep = sp + STM_SCORE_LIMIT; sp < ep; sp++) {
	    if (sp->key[0] == 0) {
		apr_cpystrn(sp->key, key, sizeof sp->key);
		apr_cpystrn(sp->value, value, sizeof sp->value);
		break;
	    }
	    if (!strcmp(sp->key, key)) {
		apr_cpystrn(sp->value, value, sizeof sp->value);
		break;
	    }
	}
    } else
	ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, stm.server,
	  "ap_update_connection_status (STM): bogus id %ld", id);
}

/*
 * Return an array of status information.
 */
apr_array_header_t *
ap_get_status_table(apr_pool_t *pool)
{
    apr_array_header_t *ap;
    int asize, tsize, i;
    ap_status_table_row_t *srp;
    int total_connections, most_threads,
      listener_connections[STM_LISTENER_LIMIT];

    STM_TRACE(("%d: ap_get_status_table(pool=%p)\n", getpid(), pool));

    /*
     * Create an array large enough to hold:
     *	- one entry per VP containing VP-specific status
     *	- one entry per thread per VP containing connection-specific status
     *	- one entry containing general status
     */
    asize = 1;
    if (stm.config.connection_status)
	asize += stm.config.max_threads;
    asize *= stm.config.num_vps;
    asize++;
    ap = apr_make_array(pool, asize, sizeof (ap_status_table_row_t));

    /* put the general status table first */
    srp = apr_push_array(ap);

    total_connections = 0;
    most_threads = 0;
    memset(listener_connections, 0, sizeof listener_connections);
    for (i = 0; i < stm.config.num_vps; i++) {
	const stm_vp *vp;
	const stm_scoreboard *sbp;
	ap_status_table_row_t *vrp;
	char timebuf[32];
	int j;

	vp = &stm.vps[i];
	sbp = vp->scoreboard;

	/*
	 * Create tables large enough to hold:
	 *	- nine data about VP-specific status
	 *	- one datum per listener about listener-specific status
	 *	- one datum per thread about connection-specific status
	 */
	tsize = 9 + vp->num_listeners + stm.config.max_threads;

	vrp = apr_push_array(ap);
	vrp->conn_id = -1000 - i;
	vrp->data = apr_make_table(pool, tsize);
	apr_ctime(timebuf, sbp->vp_start_time);
	apr_table_addn(vrp->data, "Process ID",
	  apr_psprintf(pool, "%d", sbp->pid));
	apr_table_addn(vrp->data, "Start time",
	  apr_pstrdup(pool, timebuf));
	apr_table_addn(vrp->data, "Incarnations",
	  apr_psprintf(pool, "%d", sbp->incarnations));
	apr_table_addn(vrp->data, "Bound to CPU",
	  apr_psprintf(pool, "%d", sbp->cpu));
	apr_table_addn(vrp->data, "Thread starts",
	  apr_psprintf(pool, "%d", sbp->thread_starts));
	apr_table_addn(vrp->data, "Thread exits",
	  apr_psprintf(pool, "%d", sbp->thread_exits));
	apr_table_addn(vrp->data, "Most threads used at once",
	  apr_psprintf(pool, "%d", sbp->most_threads));
	apr_table_addn(vrp->data, "Connections served, prior incarnations",
	  apr_psprintf(pool, "%d", sbp->historic_vp_connections));
	apr_table_addn(vrp->data, "Connections served, this incarnation",
	  apr_psprintf(pool, "%d", sbp->vp_connections));

	total_connections += sbp->historic_vp_connections +
	    sbp->vp_connections;
	if (most_threads < sbp->most_threads)
	    most_threads = sbp->most_threads;

	for (j = 0; j < vp->num_listeners; j++) {
	    const stm_listener *lp;
	    int lc;

	    lp = vp->listeners[j];
	    STM_ASSERT(lp->id >= 0 && lp->id < STM_LISTENER_LIMIT);
	    lc = sbp->listener_connections[lp->id];

	    apr_table_addn(vrp->data,
	      apr_psprintf(pool, "Connections served for %s",
		sbp->listeners[lp->id]),
	      apr_psprintf(pool, "%d", lc));
	    listener_connections[lp->id] += lc;
	}

	for (j = 0; j < stm.config.max_threads; j++) {
	    const stm_score *sp;

	    apr_table_addn(vrp->data,
	      apr_psprintf(pool, "Connections served by thread %d", j),
	      apr_psprintf(pool, "%d", sbp->thread_connections[j]));

	    sp = sbp->scores[j];
	    if (stm.config.connection_status && sp->key[0]) {
		const stm_score *lep;
		ap_status_table_row_t *rp;

		for (lep = sp + STM_SCORE_LIMIT; --lep >= sp; )
		    if (lep->key[0])
			break;

		rp = apr_push_array(ap);
		rp->conn_id = stm_id_make(i, j);
		rp->data = apr_make_table(pool, lep - sp + 1);

		while (sp <= lep) {
		    apr_table_add(rp->data, sp->key, sp->value);
		    sp++;
		}
	    }
	}

	/* see below */
	STM_ASSERT(apr_table_elts(vrp->data)->nelts <= tsize);
    }

    /*
     * Create a table large enough to hold:
     *	- two data about general server status
     *	- one datum per listener about listener-specific status
     */
    tsize = 2 + stm.num_listeners;
    srp->conn_id = -1;
    srp->data = apr_make_table(pool, tsize);
    apr_table_addn(srp->data, "Total connections served",
      apr_psprintf(pool, "%d", total_connections));
    apr_table_addn(srp->data, "Most threads used at once",
      apr_psprintf(pool, "%d", most_threads));
    for (i = 0; i < stm.num_listeners; i++)
	apr_table_addn(srp->data,
	  apr_psprintf(pool, "Total connections served for %s:%u",
	    stm.listeners[i].addr_string,
	    ntohs(stm.listeners[i].addr.sin_port)),
	  apr_psprintf(pool, "%d", listener_connections[i]));
    STM_ASSERT(apr_table_elts(srp->data)->nelts <= tsize);   /* see below */

    /*
     * Not really worthy of an abort if botched, because arrays and
     * tables grow automatically, but good to check that we're
     * preallocating enough space, for speed.
     */
    STM_ASSERT(ap->nelts <= asize);

    return ap;
}

/*
 * Run the already-configured STM MPM.  Returns nonzero if the server
 * should exit, zero if it should reconfigure and restart.
 */
int
ap_mpm_run(apr_pool_t *conf_pool, apr_pool_t *log_pool, server_rec *server)
{
    int i;

    STM_TRACE(("%d: => ap_mpm_run(conf_pool=%p, log_pool=%p, server=%p) STM\n",
      getpid(), conf_pool, log_pool, server));

    if (!stm_init(conf_pool, server)) {
	ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_NOERRNO, errno, server,
	  "STM MPM failed to initialize, shutting down");
	return -1;
    }

    ap_log_pid(conf_pool, stm.config.pid_file);

    ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server,
      "%s configured -- resuming normal operations",
      ap_get_server_version());
    ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, server,
      "State Threaded MPM: %d virtual processor(s) with up to %d threads each",
      stm.config.num_vps, stm.config.max_threads);
    ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, server,
      "Server built: %s", ap_get_server_built());

    stm_main();

    STM_ASSERT(stm.state != STM_STATE_RUN);
    switch (stm.state) {
    case STM_STATE_SHUTDOWN:
        ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server,
	  "shutting down by request from signal");
	for (i = 0; i < STM_VP_LIMIT; i++)
	    stm_vp_stop(&stm.vps[i]);
	stm_doomsday();
	if (unlink(stm.config.pid_file) == 0)
	    ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, 0, stm.server,
	      "removed PID file %s (pid=%d)", stm.config.pid_file, getpid());
	break;
    case STM_STATE_RESTART:
        ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server,
	  "restarting by request from signal");
	stm_signal_ignore(SIGTERM);
	if (unixd_killpg(getpgrp(), SIGTERM) == -1)
	    ap_log_error(APLOG_MARK, APLOG_WARNING, errno, server,
	      "ap_mpm_run(STM): killpg");
	stm_doomsday();
	break;
    case STM_STATE_GRACEFUL:
        ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, server,
	  "gracefully restarting by request from signal");
	for (i = 0; i < STM_VP_LIMIT; i++)
	    stm_vp_stop(&stm.vps[i]);
	stm_doomsday();
	break;
    }

    stm_fini();

    STM_TRACE(("%d: <= ap_mpm_run(conf_pool=%p, log_pool=%p, server=%p) STM, shutdown=%d\n",
      getpid(), conf_pool, log_pool, server, stm.state == STM_STATE_SHUTDOWN));
    return stm.state == STM_STATE_SHUTDOWN;
}
