summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorStefan Fritsch <sf@sfritsch.de>2011-12-27 19:42:03 +0100
committerStefan Fritsch <sf@sfritsch.de>2011-12-27 19:42:03 +0100
commit80db94fff6a9620fb469ee911347ed973e3f7735 (patch)
tree35ccde4018b7e6b84103e5e85dc1085ef9e7d6c2 /server
downloadapache2-80db94fff6a9620fb469ee911347ed973e3f7735.tar.gz
Upstream tarball 2.2.3upstream/2.2.3
Diffstat (limited to 'server')
-rw-r--r--server/.indent.pro54
-rw-r--r--server/Makefile.in85
-rw-r--r--server/NWGNUmakefile251
-rw-r--r--server/buildmark.c29
-rw-r--r--server/config.c2168
-rw-r--r--server/config.m415
-rw-r--r--server/connection.c181
-rw-r--r--server/core.c3999
-rw-r--r--server/core_filters.c926
-rw-r--r--server/eoc_bucket.c55
-rw-r--r--server/error_bucket.c74
-rw-r--r--server/gen_test_char.c121
-rw-r--r--server/gen_test_char.dsp94
-rw-r--r--server/listen.c683
-rw-r--r--server/log.c987
-rw-r--r--server/main.c742
-rw-r--r--server/mpm/MPM.NAMING15
-rw-r--r--server/mpm/Makefile.in4
-rw-r--r--server/mpm/beos/Makefile.in5
-rw-r--r--server/mpm/beos/beos.c1207
-rw-r--r--server/mpm/beos/beos.h34
-rw-r--r--server/mpm/beos/config5.m47
-rw-r--r--server/mpm/beos/mpm.h50
-rw-r--r--server/mpm/beos/mpm_default.h84
-rw-r--r--server/mpm/config.m479
-rw-r--r--server/mpm/experimental/event/Makefile.in5
-rw-r--r--server/mpm/experimental/event/config5.m46
-rw-r--r--server/mpm/experimental/event/event.c2456
-rw-r--r--server/mpm/experimental/event/fdqueue.c420
-rw-r--r--server/mpm/experimental/event/fdqueue.h82
-rw-r--r--server/mpm/experimental/event/mpm.h62
-rw-r--r--server/mpm/experimental/event/mpm_default.h79
-rw-r--r--server/mpm/experimental/event/pod.c109
-rw-r--r--server/mpm/experimental/event/pod.h60
-rw-r--r--server/mpm/mpmt_os2/Makefile.in5
-rw-r--r--server/mpm/mpmt_os2/config5.m45
-rw-r--r--server/mpm/mpmt_os2/mpm.h43
-rw-r--r--server/mpm/mpmt_os2/mpm_default.h68
-rw-r--r--server/mpm/mpmt_os2/mpmt_os2.c577
-rw-r--r--server/mpm/mpmt_os2/mpmt_os2_child.c480
-rw-r--r--server/mpm/netware/mpm.h58
-rw-r--r--server/mpm/netware/mpm_default.h122
-rw-r--r--server/mpm/netware/mpm_netware.c1291
-rw-r--r--server/mpm/prefork/Makefile.in5
-rw-r--r--server/mpm/prefork/config.m43
-rw-r--r--server/mpm/prefork/mpm.h62
-rw-r--r--server/mpm/prefork/mpm_default.h74
-rw-r--r--server/mpm/prefork/prefork.c1478
-rw-r--r--server/mpm/winnt/Win9xConHook.c697
-rw-r--r--server/mpm/winnt/Win9xConHook.def10
-rw-r--r--server/mpm/winnt/Win9xConHook.dsp103
-rw-r--r--server/mpm/winnt/Win9xConHook.h66
-rw-r--r--server/mpm/winnt/child.c1157
-rw-r--r--server/mpm/winnt/mpm.h49
-rw-r--r--server/mpm/winnt/mpm_default.h89
-rw-r--r--server/mpm/winnt/mpm_winnt.c1724
-rw-r--r--server/mpm/winnt/mpm_winnt.h130
-rw-r--r--server/mpm/winnt/nt_eventlog.c189
-rw-r--r--server/mpm/winnt/service.c1346
-rw-r--r--server/mpm/worker/Makefile.in5
-rw-r--r--server/mpm/worker/config5.m46
-rw-r--r--server/mpm/worker/fdqueue.c384
-rw-r--r--server/mpm/worker/fdqueue.h73
-rw-r--r--server/mpm/worker/mpm.h62
-rw-r--r--server/mpm/worker/mpm_default.h78
-rw-r--r--server/mpm/worker/pod.c110
-rw-r--r--server/mpm/worker/pod.h59
-rw-r--r--server/mpm/worker/worker.c2262
-rw-r--r--server/mpm_common.c1245
-rw-r--r--server/protocol.c1643
-rw-r--r--server/provider.c165
-rw-r--r--server/request.c1912
-rw-r--r--server/scoreboard.c498
-rw-r--r--server/util.c2149
-rw-r--r--server/util_cfgtree.c47
-rw-r--r--server/util_charset.c42
-rw-r--r--server/util_debug.c128
-rw-r--r--server/util_ebcdic.c112
-rw-r--r--server/util_filter.c622
-rw-r--r--server/util_md5.c172
-rw-r--r--server/util_pcre.c230
-rw-r--r--server/util_script.c721
-rw-r--r--server/util_time.c240
-rw-r--r--server/util_xml.c135
-rw-r--r--server/vhost.c1072
85 files changed, 39231 insertions, 0 deletions
diff --git a/server/.indent.pro b/server/.indent.pro
new file mode 100644
index 00000000..a9fbe9f9
--- /dev/null
+++ b/server/.indent.pro
@@ -0,0 +1,54 @@
+-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1
+-TBUFF
+-TFILE
+-TTRANS
+-TUINT4
+-T_trans
+-Tallow_options_t
+-Tapache_sfio
+-Tarray_header
+-Tbool_int
+-Tbuf_area
+-Tbuff_struct
+-Tbuffy
+-Tcmd_how
+-Tcmd_parms
+-Tcommand_rec
+-Tcommand_struct
+-Tconn_rec
+-Tcore_dir_config
+-Tcore_server_config
+-Tdir_maker_func
+-Tevent
+-Tglobals_s
+-Thandler_func
+-Thandler_rec
+-Tjoblist_s
+-Tlisten_rec
+-Tmerger_func
+-Tmode_t
+-Tmodule
+-Tmodule_struct
+-Tmutex
+-Tn_long
+-Tother_child_rec
+-Toverrides_t
+-Tparent_score
+-Tpid_t
+-Tpiped_log
+-Tpool
+-Trequest_rec
+-Trequire_line
+-Trlim_t
+-Tscoreboard
+-Tsemaphore
+-Tserver_addr_rec
+-Tserver_rec
+-Tserver_rec_chain
+-Tshort_score
+-Ttable
+-Ttable_entry
+-Tthread
+-Tu_wide_int
+-Tvtime_t
+-Twide_int
diff --git a/server/Makefile.in b/server/Makefile.in
new file mode 100644
index 00000000..89498dd4
--- /dev/null
+++ b/server/Makefile.in
@@ -0,0 +1,85 @@
+
+CLEAN_TARGETS = gen_test_char test_char.h \
+ ApacheCoreOS2.def httpd.exp export_files \
+ exports.c export_vars.h
+
+SUBDIRS = mpm
+
+LTLIBRARY_NAME = libmain.la
+LTLIBRARY_SOURCES = \
+ test_char.h \
+ config.c log.c main.c vhost.c util.c \
+ util_script.c util_md5.c util_cfgtree.c util_ebcdic.c util_time.c \
+ connection.c listen.c \
+ mpm_common.c util_charset.c util_debug.c util_xml.c \
+ util_filter.c util_pcre.c exports.c \
+ scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \
+ eoc_bucket.c core_filters.c
+
+TARGETS = delete-exports $(LTLIBRARY_NAME) $(CORE_IMPLIB_FILE) export_vars.h httpd.exp
+
+include $(top_builddir)/build/rules.mk
+include $(top_srcdir)/build/library.mk
+
+gen_test_char_OBJECTS = gen_test_char.lo util_debug.lo
+gen_test_char: $(gen_test_char_OBJECTS)
+ $(LINK) $(EXTRA_LDFLAGS) $(gen_test_char_OBJECTS) $(EXTRA_LIBS)
+
+test_char.h: gen_test_char
+ ./gen_test_char > test_char.h
+
+util.lo: test_char.h
+
+EXPORT_DIRS = $(top_srcdir)/include $(top_srcdir)/os/$(OS_DIR) $(top_srcdir)/modules/http
+EXPORT_DIRS_APR = $(APR_INCLUDEDIR) $(APU_INCLUDEDIR)
+
+# If export_files is a dependency here, but we remove it during this stage,
+# when exports.c is generated, make will not detect that export_files is no
+# longer here and deadlock. So, export_files can't be a dependency of
+# delete-exports.
+delete-exports:
+ @if test -f exports.c; then \
+ if test -f export_files; then \
+ files=`cat export_files`; \
+ headers="`find $$files -newer exports.c`"; \
+ if test -n "$$headers"; then \
+ echo Found newer headers. Will rebuild exports.c.; \
+ echo rm -f exports.c export_files; \
+ rm -f exports.c export_files; \
+ fi; \
+ else \
+ rm -f exports.c; \
+ fi; \
+ fi
+
+export_files:
+ tmp=export_files_unsorted.txt; \
+ rm -f $$tmp && touch $$tmp; \
+ for dir in $(EXPORT_DIRS); do \
+ ls $$dir/*.h >> $$tmp; \
+ done; \
+ for dir in $(EXPORT_DIRS_APR); do \
+ (ls $$dir/ap[ru].h $$dir/ap[ru]_*.h >> $$tmp 2>/dev/null); \
+ done; \
+ sort -u $$tmp > $@; \
+ rm -f $$tmp
+
+exports.c: export_files
+ $(AWK) -f $(top_srcdir)/build/make_exports.awk `cat $?` > $@
+
+export_vars.h: export_files
+ $(AWK) -f $(top_srcdir)/build/make_var_export.awk `cat $?` > $@
+
+# Rule to make def file for OS/2 core dll
+ApacheCoreOS2.def: exports.c export_vars.h $(top_srcdir)/os/$(OS_DIR)/core_header.def
+ cat $(top_srcdir)/os/$(OS_DIR)/core_header.def > $@
+ $(CPP) $< $(ALL_CPPFLAGS) $(ALL_INCLUDES) | grep "ap_hack_" | sed -e 's/^.*[)]\(.*\);$$/ "\1"/' >> $@
+ $(CPP) $(ALL_CPPFLAGS) $(ALL_INCLUDES) export_vars.h | grep "^[a-z]" | sed -e 's/^\(.*\)$$/ "\1"/' >> $@
+
+# Rule to make exp file for AIX DSOs
+httpd.exp: exports.c export_vars.h
+ @echo "#! ." > $@
+ @echo "* This file was AUTOGENERATED at build time." >> $@
+ @echo "* Please do not edit by hand." >> $@
+ $(CPP) $(ALL_CPPFLAGS) $(ALL_INCLUDES) exports.c | grep "ap_hack_" | grep -v apr_ | sed -e 's/^.*[)]\(.*\);$$/\1/' >> $@
+ $(CPP) $(ALL_CPPFLAGS) $(ALL_INCLUDES) export_vars.h | grep -v apr_ | sed -e 's/^\#[^!]*//' | sed -e '/^$$/d' >> $@
diff --git a/server/NWGNUmakefile b/server/NWGNUmakefile
new file mode 100644
index 00000000..f2f6da75
--- /dev/null
+++ b/server/NWGNUmakefile
@@ -0,0 +1,251 @@
+#
+# Declare the sub-directories to be built here
+#
+
+SUBDIRS = \
+ ../build \
+ $(EOLIST)
+
+#
+# Get the 'head' of the build environment. This includes default targets and
+# paths to tools
+#
+
+include $(AP_WORK)\build\NWGNUhead.inc
+
+#
+# build this level's files
+
+#
+# These directories will be at the beginning of the include list, followed by
+# INCDIRS
+#
+XINCDIRS += \
+ $(NWOS) \
+ $(APR)/include \
+ $(AP_WORK)/include \
+ $(APRUTIL)/include \
+ $(EOLIST)
+
+#
+# These flags will come after CFLAGS
+#
+XCFLAGS += \
+ $(EOLIST)
+
+#
+# These defines will come after DEFINES
+#
+XDEFINES += \
+ $(EOLIST)
+
+#
+# These flags will be added to the link.opt file
+#
+XLFLAGS += \
+ $(EOLIST)
+
+#
+# These values will be appended to the correct variables based on the value of
+# RELEASE
+#
+ifeq "$(RELEASE)" "debug"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "noopt"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+ifeq "$(RELEASE)" "release"
+XINCDIRS += \
+ $(EOLIST)
+
+XCFLAGS += \
+ $(EOLIST)
+
+XDEFINES += \
+ $(EOLIST)
+
+XLFLAGS += \
+ $(EOLIST)
+endif
+
+#
+# These are used by the link target if an NLM is being generated
+# This is used by the link 'name' directive to name the nlm. If left blank
+# TARGET_nlm (see below) will be used.
+#
+NLM_NAME = genchars
+
+#
+# This is used by the link '-desc ' directive.
+# If left blank, NLM_NAME will be used.
+#
+NLM_DESCRIPTION = Generate Test Characters
+
+#
+# This is used by the '-threadname' directive. If left blank,
+# NLM_NAME Thread will be used.
+#
+NLM_THREAD_NAME = genchars
+
+#
+# If this is specified, it will override VERSION value in
+# $(AP_WORK)\NWGNUNetWare.rul
+#
+NLM_VERSION = 1,0,0
+
+#
+# If this is specified, it will override the default of 64K
+#
+NLM_STACK_SIZE = 8192
+
+#
+# If this is specified it will be used by the link '-entry' directive
+#
+NLM_ENTRY_SYM =_LibCPrelude
+
+#
+# If this is specified it will be used by the link '-exit' directive
+#
+NLM_EXIT_SYM =_LibCPostlude
+
+#
+# If this is specified it will be used by the link '-check' directive
+#
+NLM_CHECK_SYM =
+
+#
+# If this is specified it will be used by the link '-flags' directive
+#
+NLM_FLAGS = PSEUDOPREEMPTION
+
+#
+# If this is specified it will be linked in with the XDCData option in the def
+# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled
+# by setting APACHE_UNIPROC in the environment
+#
+XDCDATA =
+
+#
+# Declare all target files (you must add your files here)
+#
+
+#
+# If there is an NLM target, put it here
+#
+TARGET_nlm = \
+$(OBJDIR)/genchars.nlm \
+ $(EOLIST)
+
+#
+# If there is an LIB target, put it here
+#
+TARGET_lib = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the NLM target above.
+# Paths must all use the '/' character
+#
+FILES_nlm_objs = \
+ $(OBJDIR)/gen_test_char.o \
+ $(EOLIST)
+
+#
+# These are the LIB files needed to create the NLM target above.
+# These will be added as a library command in the link.opt file.
+#
+FILES_nlm_libs = \
+ libcpre.o \
+ $(EOLIST)
+
+#
+# These are the modules that the above NLM target depends on to load.
+# These will be added as a module command in the link.opt file.
+#
+FILES_nlm_modules = \
+ Libc \
+ $(EOLIST)
+
+#
+# If the nlm has a msg file, put it's path here
+#
+FILE_nlm_msg =
+
+#
+# If the nlm has a hlp file put it's path here
+#
+FILE_nlm_hlp =
+
+#
+# If this is specified, it will override $(NWOS)\copyright.txt.
+#
+FILE_nlm_copyright =
+
+#
+# Any additional imports go here
+#
+FILES_nlm_Ximports = \
+ @libc.imp \
+ $(EOLIST)
+
+#
+# Any symbols exported to here
+#
+FILES_nlm_exports = \
+ $(EOLIST)
+
+#
+# These are the OBJ files needed to create the LIB target above.
+# Paths must all use the '/' character
+#
+FILES_lib_objs = \
+ $(EOLIST)
+
+#
+# implement targets and dependancies (leave this section alone)
+#
+
+libs :: $(OBJDIR) $(TARGET_lib)
+
+nlms :: libs $(TARGET_nlm)
+
+#
+# Updated this target to create necessary directories and copy files to the
+# correct place.
+#
+install :: nlms FORCE
+
+#
+# Any specialized rules here
+#
+
+#
+# Include the 'tail' makefile that has targets that depend on variables defined
+# in this makefile
+#
+
+include $(AP_WORK)\build\NWGNUtail.inc
+
diff --git a/server/buildmark.c b/server/buildmark.c
new file mode 100644
index 00000000..a9cd6844
--- /dev/null
+++ b/server/buildmark.c
@@ -0,0 +1,29 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_config.h"
+#include "httpd.h"
+
+#if defined(__DATE__) && defined(__TIME__)
+static const char server_built[] = __DATE__ " " __TIME__;
+#else
+static const char server_built[] = "unknown";
+#endif
+
+AP_DECLARE(const char *) ap_get_server_built()
+{
+ return server_built;
+}
diff --git a/server/config.c b/server/config.c
new file mode 100644
index 00000000..8eed98b0
--- /dev/null
+++ b/server/config.c
@@ -0,0 +1,2168 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_config.c: once was auxillary functions for reading httpd's config
+ * file and converting filenames into a namespace
+ *
+ * Rob McCool
+ *
+ * Wall-to-wall rewrite for Apache... commands which are part of the
+ * server core can now be found next door in "http_core.c". Now contains
+ * general command loop, and functions which do bookkeeping for the new
+ * Apache config stuff (modules and configuration vectors).
+ *
+ * rst
+ *
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_file_io.h"
+#include "apr_fnmatch.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_core.h"
+#include "http_log.h" /* for errors in parse_htaccess */
+#include "http_request.h" /* for default_handler (see invoke_handler) */
+#include "http_main.h"
+#include "http_vhost.h"
+#include "util_cfgtree.h"
+#include "mpm.h"
+
+
+AP_DECLARE_DATA const char *ap_server_argv0 = NULL;
+
+AP_DECLARE_DATA const char *ap_server_root = NULL;
+
+AP_DECLARE_DATA apr_array_header_t *ap_server_pre_read_config = NULL;
+AP_DECLARE_DATA apr_array_header_t *ap_server_post_read_config = NULL;
+AP_DECLARE_DATA apr_array_header_t *ap_server_config_defines = NULL;
+
+AP_DECLARE_DATA ap_directive_t *ap_conftree = NULL;
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(header_parser)
+ APR_HOOK_LINK(pre_config)
+ APR_HOOK_LINK(post_config)
+ APR_HOOK_LINK(open_logs)
+ APR_HOOK_LINK(child_init)
+ APR_HOOK_LINK(handler)
+ APR_HOOK_LINK(quick_handler)
+ APR_HOOK_LINK(optional_fn_retrieve)
+ APR_HOOK_LINK(test_config)
+)
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int, header_parser,
+ (request_rec *r), (r), OK, DECLINED)
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int, pre_config,
+ (apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp),
+ (pconf, plog, ptemp), OK, DECLINED)
+
+AP_IMPLEMENT_HOOK_VOID(test_config,
+ (apr_pool_t *pconf, server_rec *s),
+ (pconf, s))
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int, post_config,
+ (apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s),
+ (pconf, plog, ptemp, s), OK, DECLINED)
+
+/* During the course of debugging I expanded this macro out, so
+ * rather than remove all the useful information there is in the
+ * following lines, I'm going to leave it here in case anyone
+ * else finds it useful.
+ *
+ * Ben has looked at it and thinks it correct :)
+ *
+AP_DECLARE(int) ap_hook_post_config(ap_HOOK_post_config_t *pf,
+ const char * const *aszPre,
+ const char * const *aszSucc,
+ int nOrder)
+{
+ ap_LINK_post_config_t *pHook;
+
+ if (!_hooks.link_post_config) {
+ _hooks.link_post_config = apr_array_make(apr_hook_global_pool, 1,
+ sizeof(ap_LINK_post_config_t));
+ apr_hook_sort_register("post_config", &_hooks.link_post_config);
+ }
+
+ pHook = apr_array_push(_hooks.link_post_config);
+ pHook->pFunc = pf;
+ pHook->aszPredecessors = aszPre;
+ pHook->aszSuccessors = aszSucc;
+ pHook->nOrder = nOrder;
+ pHook->szName = apr_hook_debug_current;
+
+ if (apr_hook_debug_enabled)
+ apr_hook_debug_show("post_config", aszPre, aszSucc);
+}
+
+AP_DECLARE(apr_array_header_t *) ap_hook_get_post_config(void) {
+ return _hooks.link_post_config;
+}
+
+AP_DECLARE(int) ap_run_post_config(apr_pool_t *pconf,
+ apr_pool_t *plog,
+ apr_pool_t *ptemp,
+ server_rec *s)
+{
+ ap_LINK_post_config_t *pHook;
+ int n;
+
+ if(!_hooks.link_post_config)
+ return;
+
+ pHook = (ap_LINK_post_config_t *)_hooks.link_post_config->elts;
+ for (n = 0; n < _hooks.link_post_config->nelts; ++n)
+ pHook[n].pFunc (pconf, plog, ptemp, s);
+}
+ */
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int, open_logs,
+ (apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s),
+ (pconf, plog, ptemp, s), OK, DECLINED)
+
+AP_IMPLEMENT_HOOK_VOID(child_init,
+ (apr_pool_t *pchild, server_rec *s),
+ (pchild, s))
+
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, handler, (request_rec *r),
+ (r), DECLINED)
+
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, quick_handler, (request_rec *r, int lookup),
+ (r, lookup), DECLINED)
+
+AP_IMPLEMENT_HOOK_VOID(optional_fn_retrieve, (void), ())
+
+/****************************************************************
+ *
+ * We begin with the functions which deal with the linked list
+ * of modules which control just about all of the server operation.
+ */
+
+/* total_modules is the number of modules that have been linked
+ * into the server.
+ */
+static int total_modules = 0;
+
+/* dynamic_modules is the number of modules that have been added
+ * after the pre-loaded ones have been set up. It shouldn't be larger
+ * than DYNAMIC_MODULE_LIMIT.
+ */
+static int dynamic_modules = 0;
+
+AP_DECLARE_DATA module *ap_top_module = NULL;
+AP_DECLARE_DATA module **ap_loaded_modules=NULL;
+
+static apr_hash_t *ap_config_hash = NULL;
+
+typedef int (*handler_func)(request_rec *);
+typedef void *(*dir_maker_func)(apr_pool_t *, char *);
+typedef void *(*merger_func)(apr_pool_t *, void *, void *);
+
+/* maximum nesting level for config directories */
+#ifndef AP_MAX_INCLUDE_DIR_DEPTH
+#define AP_MAX_INCLUDE_DIR_DEPTH (128)
+#endif
+
+/* Dealing with config vectors. These are associated with per-directory,
+ * per-server, and per-request configuration, and have a void* pointer for
+ * each modules. The nature of the structure pointed to is private to the
+ * module in question... the core doesn't (and can't) know. However, there
+ * are defined interfaces which allow it to create instances of its private
+ * per-directory and per-server structures, and to merge the per-directory
+ * structures of a directory and its subdirectory (producing a new one in
+ * which the defaults applying to the base directory have been properly
+ * overridden).
+ */
+
+static ap_conf_vector_t *create_empty_config(apr_pool_t *p)
+{
+ void *conf_vector = apr_pcalloc(p, sizeof(void *) *
+ (total_modules + DYNAMIC_MODULE_LIMIT));
+ return conf_vector;
+}
+
+static ap_conf_vector_t *create_default_per_dir_config(apr_pool_t *p)
+{
+ void **conf_vector = apr_pcalloc(p, sizeof(void *) *
+ (total_modules + DYNAMIC_MODULE_LIMIT));
+ module *modp;
+
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ dir_maker_func df = modp->create_dir_config;
+
+ if (df)
+ conf_vector[modp->module_index] = (*df)(p, NULL);
+ }
+
+ return (ap_conf_vector_t *)conf_vector;
+}
+
+AP_CORE_DECLARE(ap_conf_vector_t *) ap_merge_per_dir_configs(apr_pool_t *p,
+ ap_conf_vector_t *base,
+ ap_conf_vector_t *new_conf)
+{
+ void **conf_vector = apr_palloc(p, sizeof(void *) * total_modules);
+ void **base_vector = (void **)base;
+ void **new_vector = (void **)new_conf;
+ module *modp;
+
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ int i = modp->module_index;
+
+ if (!new_vector[i]) {
+ conf_vector[i] = base_vector[i];
+ }
+ else {
+ merger_func df = modp->merge_dir_config;
+ if (df && base_vector[i]) {
+ conf_vector[i] = (*df)(p, base_vector[i], new_vector[i]);
+ }
+ else
+ conf_vector[i] = new_vector[i];
+ }
+ }
+
+ return (ap_conf_vector_t *)conf_vector;
+}
+
+static ap_conf_vector_t *create_server_config(apr_pool_t *p, server_rec *s)
+{
+ void **conf_vector = apr_pcalloc(p, sizeof(void *) *
+ (total_modules + DYNAMIC_MODULE_LIMIT));
+ module *modp;
+
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ if (modp->create_server_config)
+ conf_vector[modp->module_index] = (*modp->create_server_config)(p, s);
+ }
+
+ return (ap_conf_vector_t *)conf_vector;
+}
+
+static void merge_server_configs(apr_pool_t *p, ap_conf_vector_t *base,
+ ap_conf_vector_t *virt)
+{
+ /* Can reuse the 'virt' vector for the spine of it, since we don't
+ * have to deal with the moral equivalent of .htaccess files here...
+ */
+
+ void **base_vector = (void **)base;
+ void **virt_vector = (void **)virt;
+ module *modp;
+
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ merger_func df = modp->merge_server_config;
+ int i = modp->module_index;
+
+ if (!virt_vector[i])
+ virt_vector[i] = base_vector[i];
+ else if (df)
+ virt_vector[i] = (*df)(p, base_vector[i], virt_vector[i]);
+ }
+}
+
+AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_request_config(apr_pool_t *p)
+{
+ return create_empty_config(p);
+}
+
+AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_conn_config(apr_pool_t *p)
+{
+ return create_empty_config(p);
+}
+
+AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_per_dir_config(apr_pool_t *p)
+{
+ return create_empty_config(p);
+}
+
+static int ap_invoke_filter_init(ap_filter_t *filters)
+{
+ while (filters) {
+ if (filters->frec->filter_init_func) {
+ int result = filters->frec->filter_init_func(filters);
+ if (result != OK) {
+ return result;
+ }
+ }
+ filters = filters->next;
+ }
+ return OK;
+}
+
+AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r)
+{
+ const char *handler;
+ const char *p;
+ int result;
+ const char *old_handler = r->handler;
+
+ /*
+ * The new insert_filter stage makes the most sense here. We only use
+ * it when we are going to run the request, so we must insert filters
+ * if any are available. Since the goal of this phase is to allow all
+ * modules to insert a filter if they want to, this filter returns
+ * void. I just can't see any way that this filter can reasonably
+ * fail, either your modules inserts something or it doesn't. rbb
+ */
+ ap_run_insert_filter(r);
+
+ /* Before continuing, allow each filter that is in the two chains to
+ * run their init function to let them do any magic before we could
+ * start generating data.
+ */
+ result = ap_invoke_filter_init(r->input_filters);
+ if (result != OK) {
+ return result;
+ }
+ result = ap_invoke_filter_init(r->output_filters);
+ if (result != OK) {
+ return result;
+ }
+
+ if (!r->handler) {
+ handler = r->content_type ? r->content_type : ap_default_type(r);
+ if ((p=ap_strchr_c(handler, ';')) != NULL) {
+ char *new_handler = (char *)apr_pmemdup(r->pool, handler,
+ p - handler + 1);
+ char *p2 = new_handler + (p - handler);
+ handler = new_handler;
+
+ /* MIME type arguments */
+ while (p2 > handler && p2[-1] == ' ')
+ --p2; /* strip trailing spaces */
+
+ *p2='\0';
+ }
+
+ r->handler = handler;
+ }
+
+ result = ap_run_handler(r);
+
+ r->handler = old_handler;
+
+ if (result == DECLINED && r->handler && r->filename) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+ "handler \"%s\" not found for: %s", r->handler, r->filename);
+ }
+
+ return result == DECLINED ? HTTP_INTERNAL_SERVER_ERROR : result;
+}
+
+AP_DECLARE(int) ap_method_is_limited(cmd_parms *cmd, const char *method)
+{
+ int methnum;
+
+ methnum = ap_method_number_of(method);
+
+ /*
+ * A method number either hardcoded into apache or
+ * added by a module and registered.
+ */
+ if (methnum != M_INVALID) {
+ return (cmd->limited & (AP_METHOD_BIT << methnum)) ? 1 : 0;
+ }
+
+ return 0; /* not found */
+}
+
+AP_DECLARE(void) ap_register_hooks(module *m, apr_pool_t *p)
+{
+ if (m->register_hooks) {
+ if (getenv("SHOW_HOOKS")) {
+ printf("Registering hooks for %s\n", m->name);
+ apr_hook_debug_enabled = 1;
+ }
+
+ apr_hook_debug_current = m->name;
+ m->register_hooks(p);
+ }
+}
+
+static void ap_add_module_commands(module *m, apr_pool_t *p);
+
+typedef struct ap_mod_list_struct ap_mod_list;
+struct ap_mod_list_struct {
+ struct ap_mod_list_struct *next;
+ module *m;
+ const command_rec *cmd;
+};
+
+static apr_status_t reload_conf_hash(void *baton)
+{
+ ap_config_hash = NULL;
+ return APR_SUCCESS;
+}
+
+static void rebuild_conf_hash(apr_pool_t *p, int add_prelinked)
+{
+ module **m;
+
+ ap_config_hash = apr_hash_make(p);
+
+ apr_pool_cleanup_register(p, NULL, reload_conf_hash,
+ apr_pool_cleanup_null);
+ if (add_prelinked) {
+ for (m = ap_prelinked_modules; *m != NULL; m++) {
+ ap_add_module_commands(*m, p);
+ }
+ }
+}
+
+static void ap_add_module_commands(module *m, apr_pool_t *p)
+{
+ apr_pool_t *tpool;
+ ap_mod_list *mln;
+ const command_rec *cmd;
+ char *dir;
+
+ cmd = m->cmds;
+
+ if (ap_config_hash == NULL) {
+ rebuild_conf_hash(p, 0);
+ }
+
+ tpool = apr_hash_pool_get(ap_config_hash);
+
+ while (cmd && cmd->name) {
+ mln = apr_palloc(tpool, sizeof(ap_mod_list));
+ mln->cmd = cmd;
+ mln->m = m;
+ dir = apr_pstrdup(tpool, cmd->name);
+
+ ap_str_tolower(dir);
+
+ mln->next = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING);
+ apr_hash_set(ap_config_hash, dir, APR_HASH_KEY_STRING, mln);
+ ++cmd;
+ }
+}
+
+
+/* One-time setup for precompiled modules --- NOT to be done on restart */
+
+AP_DECLARE(const char *) ap_add_module(module *m, apr_pool_t *p)
+{
+ /* This could be called from a LoadModule httpd.conf command,
+ * after the file has been linked and the module structure within it
+ * teased out...
+ */
+
+ if (m->version != MODULE_MAGIC_NUMBER_MAJOR) {
+ return apr_psprintf(p, "Module \"%s\" is not compatible with this "
+ "version of Apache (found %d, need %d). Please "
+ "contact the vendor for the correct version.",
+ m->name, m->version, MODULE_MAGIC_NUMBER_MAJOR);
+ }
+
+ if (m->next == NULL) {
+ m->next = ap_top_module;
+ ap_top_module = m;
+ }
+
+ if (m->module_index == -1) {
+ m->module_index = total_modules++;
+ dynamic_modules++;
+
+ if (dynamic_modules > DYNAMIC_MODULE_LIMIT) {
+ return apr_psprintf(p, "Module \"%s\" could not be loaded, "
+ "because the dynamic module limit was "
+ "reached. Please increase "
+ "DYNAMIC_MODULE_LIMIT and recompile.", m->name);
+ }
+ }
+
+ /* Some C compilers put a complete path into __FILE__, but we want
+ * only the filename (e.g. mod_includes.c). So check for path
+ * components (Unix and DOS), and remove them.
+ */
+
+ if (ap_strrchr_c(m->name, '/'))
+ m->name = 1 + ap_strrchr_c(m->name, '/');
+
+ if (ap_strrchr_c(m->name, '\\'))
+ m->name = 1 + ap_strrchr_c(m->name, '\\');
+
+#ifdef _OSD_POSIX
+ /* __FILE__ =
+ * "*POSIX(/home/martin/apache/src/modules/standard/mod_info.c)"
+ */
+
+ /* We cannot fix the string in-place, because it's const */
+ if (m->name[strlen(m->name)-1] == ')') {
+ char *tmp = strdup(m->name); /* FIXME: memory leak, albeit a small one */
+ tmp[strlen(tmp)-1] = '\0';
+ m->name = tmp;
+ }
+#endif /*_OSD_POSIX*/
+
+ ap_add_module_commands(m, p);
+ /* FIXME: is this the right place to call this?
+ * It doesn't appear to be
+ */
+ ap_register_hooks(m, p);
+
+ return NULL;
+}
+
+/*
+ * remove_module undoes what add_module did. There are some caveats:
+ * when the module is removed, its slot is lost so all the current
+ * per-dir and per-server configurations are invalid. So we should
+ * only ever call this function when you are invalidating almost
+ * all our current data. I.e. when doing a restart.
+ */
+
+AP_DECLARE(void) ap_remove_module(module *m)
+{
+ module *modp;
+
+ modp = ap_top_module;
+ if (modp == m) {
+ /* We are the top module, special case */
+ ap_top_module = modp->next;
+ m->next = NULL;
+ }
+ else {
+ /* Not the top module, find use. When found modp will
+ * point to the module _before_ us in the list
+ */
+
+ while (modp && modp->next != m) {
+ modp = modp->next;
+ }
+
+ if (!modp) {
+ /* Uh-oh, this module doesn't exist */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "Cannot remove module %s: not found in module list",
+ m->name);
+ return;
+ }
+
+ /* Eliminate us from the module list */
+ modp->next = modp->next->next;
+ }
+
+ m->module_index = -1; /* simulate being unloaded, should
+ * be unnecessary */
+ dynamic_modules--;
+ total_modules--;
+}
+
+AP_DECLARE(const char *) ap_add_loaded_module(module *mod, apr_pool_t *p)
+{
+ module **m;
+ const char *error;
+
+ /*
+ * Add module pointer to top of chained module list
+ */
+ error = ap_add_module(mod, p);
+ if (error) {
+ return error;
+ }
+
+ /*
+ * And module pointer to list of loaded modules
+ *
+ * Notes: 1. ap_add_module() would already complain if no more space
+ * exists for adding a dynamically loaded module
+ * 2. ap_add_module() accepts double inclusion, so we have
+ * to accept this, too.
+ */
+ for (m = ap_loaded_modules; *m != NULL; m++)
+ ;
+ *m++ = mod;
+ *m = NULL;
+
+ return NULL;
+}
+
+AP_DECLARE(void) ap_remove_loaded_module(module *mod)
+{
+ module **m;
+ module **m2;
+ int done;
+
+ /*
+ * Remove module pointer from chained module list
+ */
+ ap_remove_module(mod);
+
+ /*
+ * Remove module pointer from list of loaded modules
+ *
+ * Note: 1. We cannot determine if the module was successfully
+ * removed by ap_remove_module().
+ * 2. We have not to complain explicity when the module
+ * is not found because ap_remove_module() did it
+ * for us already.
+ */
+ for (m = m2 = ap_loaded_modules, done = 0; *m2 != NULL; m2++) {
+ if (*m2 == mod && done == 0)
+ done = 1;
+ else
+ *m++ = *m2;
+ }
+
+ *m = NULL;
+}
+
+AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process)
+{
+ module **m;
+ module **m2;
+ const char *error;
+
+ apr_hook_global_pool=process->pconf;
+
+ rebuild_conf_hash(process->pconf, 0);
+
+ /*
+ * Initialise total_modules variable and module indices
+ */
+ total_modules = 0;
+ for (m = ap_preloaded_modules; *m != NULL; m++)
+ (*m)->module_index = total_modules++;
+
+ /*
+ * Initialise list of loaded modules
+ */
+ ap_loaded_modules = (module **)apr_palloc(process->pool,
+ sizeof(module *) * (total_modules + DYNAMIC_MODULE_LIMIT + 1));
+
+ if (ap_loaded_modules == NULL) {
+ return "Ouch! Out of memory in ap_setup_prelinked_modules()!";
+ }
+
+ for (m = ap_preloaded_modules, m2 = ap_loaded_modules; *m != NULL; )
+ *m2++ = *m++;
+
+ *m2 = NULL;
+
+ /*
+ * Initialize chain of linked (=activate) modules
+ */
+ for (m = ap_prelinked_modules; *m != NULL; m++) {
+ error = ap_add_module(*m, process->pconf);
+ if (error) {
+ return error;
+ }
+ }
+
+ apr_hook_sort_all();
+
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_find_module_name(module *m)
+{
+ return m->name;
+}
+
+AP_DECLARE(module *) ap_find_linked_module(const char *name)
+{
+ module *modp;
+
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ if (strcmp(modp->name, name) == 0)
+ return modp;
+ }
+
+ return NULL;
+}
+
+/*****************************************************************
+ *
+ * Resource, access, and .htaccess config files now parsed by a common
+ * command loop.
+ *
+ * Let's begin with the basics; parsing the line and
+ * invoking the function...
+ */
+
+#define AP_MAX_ARGC 64
+
+static const char *invoke_cmd(const command_rec *cmd, cmd_parms *parms,
+ void *mconfig, const char *args)
+{
+ char *w, *w2, *w3;
+ const char *errmsg = NULL;
+
+ if ((parms->override & cmd->req_override) == 0)
+ return apr_pstrcat(parms->pool, cmd->name, " not allowed here", NULL);
+
+ parms->info = cmd->cmd_data;
+ parms->cmd = cmd;
+
+ switch (cmd->args_how) {
+ case RAW_ARGS:
+#ifdef RESOLVE_ENV_PER_TOKEN
+ args = ap_resolve_env(parms->pool,args);
+#endif
+ return cmd->AP_RAW_ARGS(parms, mconfig, args);
+
+ case TAKE_ARGV:
+ {
+ char *argv[AP_MAX_ARGC];
+ int argc = 0;
+
+ do {
+ w = ap_getword_conf(parms->pool, &args);
+ if (*w == '\0' && *args == '\0') {
+ break;
+ }
+ argv[argc] = w;
+ argc++;
+ } while (argc < AP_MAX_ARGC && *args != '\0');
+
+ return cmd->AP_TAKE_ARGV(parms, mconfig, argc, argv);
+ }
+
+ case NO_ARGS:
+ if (*args != 0)
+ return apr_pstrcat(parms->pool, cmd->name, " takes no arguments",
+ NULL);
+
+ return cmd->AP_NO_ARGS(parms, mconfig);
+
+ case TAKE1:
+ w = ap_getword_conf(parms->pool, &args);
+
+ if (*w == '\0' || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name, " takes one argument",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE1(parms, mconfig, w);
+
+ case TAKE2:
+ w = ap_getword_conf(parms->pool, &args);
+ w2 = ap_getword_conf(parms->pool, &args);
+
+ if (*w == '\0' || *w2 == '\0' || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name, " takes two arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE2(parms, mconfig, w, w2);
+
+ case TAKE12:
+ w = ap_getword_conf(parms->pool, &args);
+ w2 = ap_getword_conf(parms->pool, &args);
+
+ if (*w == '\0' || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name, " takes 1-2 arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE2(parms, mconfig, w, *w2 ? w2 : NULL);
+
+ case TAKE3:
+ w = ap_getword_conf(parms->pool, &args);
+ w2 = ap_getword_conf(parms->pool, &args);
+ w3 = ap_getword_conf(parms->pool, &args);
+
+ if (*w == '\0' || *w2 == '\0' || *w3 == '\0' || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name, " takes three arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+ case TAKE23:
+ w = ap_getword_conf(parms->pool, &args);
+ w2 = ap_getword_conf(parms->pool, &args);
+ w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL;
+
+ if (*w == '\0' || *w2 == '\0' || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name,
+ " takes two or three arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+ case TAKE123:
+ w = ap_getword_conf(parms->pool, &args);
+ w2 = *args ? ap_getword_conf(parms->pool, &args) : NULL;
+ w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL;
+
+ if (*w == '\0' || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name,
+ " takes one, two or three arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+ case TAKE13:
+ w = ap_getword_conf(parms->pool, &args);
+ w2 = *args ? ap_getword_conf(parms->pool, &args) : NULL;
+ w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL;
+
+ if (*w == '\0' || (w2 && *w2 && !w3) || *args != 0)
+ return apr_pstrcat(parms->pool, cmd->name,
+ " takes one or three arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+ case ITERATE:
+ while (*(w = ap_getword_conf(parms->pool, &args)) != '\0') {
+
+ errmsg = cmd->AP_TAKE1(parms, mconfig, w);
+
+ if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0)
+ return errmsg;
+ }
+
+ return errmsg;
+
+ case ITERATE2:
+ w = ap_getword_conf(parms->pool, &args);
+
+ if (*w == '\0' || *args == 0)
+ return apr_pstrcat(parms->pool, cmd->name,
+ " requires at least two arguments",
+ cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+ while (*(w2 = ap_getword_conf(parms->pool, &args)) != '\0') {
+
+ errmsg = cmd->AP_TAKE2(parms, mconfig, w, w2);
+
+ if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0)
+ return errmsg;
+ }
+
+ return errmsg;
+
+ case FLAG:
+ w = ap_getword_conf(parms->pool, &args);
+
+ if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off")))
+ return apr_pstrcat(parms->pool, cmd->name, " must be On or Off",
+ NULL);
+
+ return cmd->AP_FLAG(parms, mconfig, strcasecmp(w, "off") != 0);
+
+ default:
+ return apr_pstrcat(parms->pool, cmd->name,
+ " is improperly configured internally (server bug)",
+ NULL);
+ }
+}
+
+AP_CORE_DECLARE(const command_rec *) ap_find_command(const char *name,
+ const command_rec *cmds)
+{
+ while (cmds->name) {
+ if (!strcasecmp(name, cmds->name))
+ return cmds;
+
+ ++cmds;
+ }
+
+ return NULL;
+}
+
+AP_CORE_DECLARE(const command_rec *) ap_find_command_in_modules(
+ const char *cmd_name, module **mod)
+{
+ const command_rec *cmdp;
+ module *modp;
+
+ for (modp = *mod; modp; modp = modp->next) {
+ if (modp->cmds && (cmdp = ap_find_command(cmd_name, modp->cmds))) {
+ *mod = modp;
+ return cmdp;
+ }
+ }
+
+ return NULL;
+}
+
+AP_CORE_DECLARE(void *) ap_set_config_vectors(server_rec *server,
+ ap_conf_vector_t *section_vector,
+ const char *section,
+ module *mod, apr_pool_t *pconf)
+{
+ void *section_config = ap_get_module_config(section_vector, mod);
+ void *server_config = ap_get_module_config(server->module_config, mod);
+
+ if (!section_config && mod->create_dir_config) {
+ /* ### need to fix the create_dir_config functions' prototype... */
+ section_config = (*mod->create_dir_config)(pconf, (char *)section);
+ ap_set_module_config(section_vector, mod, section_config);
+ }
+
+ if (!server_config && mod->create_server_config) {
+ server_config = (*mod->create_server_config)(pconf, server);
+ ap_set_module_config(server->module_config, mod, server_config);
+ }
+
+ return section_config;
+}
+
+static const char *execute_now(char *cmd_line, const char *args,
+ cmd_parms *parms,
+ apr_pool_t *p, apr_pool_t *ptemp,
+ ap_directive_t **sub_tree,
+ ap_directive_t *parent);
+
+static const char *ap_build_config_sub(apr_pool_t *p, apr_pool_t *temp_pool,
+ const char *l, cmd_parms *parms,
+ ap_directive_t **current,
+ ap_directive_t **curr_parent,
+ ap_directive_t **conftree)
+{
+ const char *retval = NULL;
+ const char *args;
+ char *cmd_name;
+ ap_directive_t *newdir;
+ module *mod = ap_top_module;
+ const command_rec *cmd;
+
+ if (*l == '#' || *l == '\0')
+ return NULL;
+
+#if RESOLVE_ENV_PER_TOKEN
+ args = l;
+#else
+ args = ap_resolve_env(temp_pool, l);
+#endif
+
+ cmd_name = ap_getword_conf(p, &args);
+ if (*cmd_name == '\0') {
+ /* Note: this branch should not occur. An empty line should have
+ * triggered the exit further above.
+ */
+ return NULL;
+ }
+
+ if (cmd_name[1] != '/') {
+ char *lastc = cmd_name + strlen(cmd_name) - 1;
+ if (*lastc == '>') {
+ *lastc = '\0' ;
+ }
+ if (cmd_name[0] == '<' && *args == '\0') {
+ args = ">";
+ }
+ }
+
+ newdir = apr_pcalloc(p, sizeof(ap_directive_t));
+ newdir->filename = parms->config_file->name;
+ newdir->line_num = parms->config_file->line_number;
+ newdir->directive = cmd_name;
+ newdir->args = apr_pstrdup(p, args);
+
+ if ((cmd = ap_find_command_in_modules(cmd_name, &mod)) != NULL) {
+ if (cmd->req_override & EXEC_ON_READ) {
+ ap_directive_t *sub_tree = NULL;
+
+ parms->err_directive = newdir;
+ retval = execute_now(cmd_name, args, parms, p, temp_pool,
+ &sub_tree, *curr_parent);
+ if (*current) {
+ (*current)->next = sub_tree;
+ }
+ else {
+ *current = sub_tree;
+ if (*curr_parent) {
+ (*curr_parent)->first_child = (*current);
+ }
+ if (*current) {
+ (*current)->parent = (*curr_parent);
+ }
+ }
+ if (*current) {
+ if (!*conftree) {
+ /* Before walking *current to the end of the list,
+ * set the head to *current.
+ */
+ *conftree = *current;
+ }
+ while ((*current)->next != NULL) {
+ (*current) = (*current)->next;
+ (*current)->parent = (*curr_parent);
+ }
+ }
+ return retval;
+ }
+ }
+
+ if (cmd_name[0] == '<') {
+ if (cmd_name[1] != '/') {
+ (*current) = ap_add_node(curr_parent, *current, newdir, 1);
+ }
+ else if (*curr_parent == NULL) {
+ parms->err_directive = newdir;
+ return apr_pstrcat(p, cmd_name,
+ " without matching <", cmd_name + 2,
+ " section", NULL);
+ }
+ else {
+ char *bracket = cmd_name + strlen(cmd_name) - 1;
+
+ if (*bracket != '>') {
+ parms->err_directive = newdir;
+ return apr_pstrcat(p, cmd_name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ *bracket = '\0';
+
+ if (strcasecmp(cmd_name + 2,
+ (*curr_parent)->directive + 1) != 0) {
+ parms->err_directive = newdir;
+ return apr_pstrcat(p, "Expected </",
+ (*curr_parent)->directive + 1, "> but saw ",
+ cmd_name, ">", NULL);
+ }
+
+ *bracket = '>';
+
+ /* done with this section; move up a level */
+ *current = *curr_parent;
+ *curr_parent = (*current)->parent;
+ }
+ }
+ else {
+ *current = ap_add_node(curr_parent, *current, newdir, 0);
+ }
+
+ return retval;
+}
+
+AP_DECLARE(const char *) ap_build_cont_config(apr_pool_t *p,
+ apr_pool_t *temp_pool,
+ cmd_parms *parms,
+ ap_directive_t **current,
+ ap_directive_t **curr_parent,
+ char *orig_directive)
+{
+ char *l;
+ char *bracket;
+ const char *retval;
+ ap_directive_t *sub_tree = NULL;
+
+ /* Since this function can be called recursively, allocate
+ * the temporary 8k string buffer from the temp_pool rather
+ * than the stack to avoid over-running a fixed length stack.
+ */
+ l = apr_palloc(temp_pool, MAX_STRING_LEN);
+
+ bracket = apr_pstrcat(p, orig_directive + 1, ">", NULL);
+ while (!(ap_cfg_getline(l, MAX_STRING_LEN, parms->config_file))) {
+ if (!memcmp(l, "</", 2)
+ && (strcasecmp(l + 2, bracket) == 0)
+ && (*curr_parent == NULL)) {
+ break;
+ }
+ retval = ap_build_config_sub(p, temp_pool, l, parms, current,
+ curr_parent, &sub_tree);
+ if (retval != NULL)
+ return retval;
+
+ if (sub_tree == NULL && curr_parent != NULL) {
+ sub_tree = *curr_parent;
+ }
+
+ if (sub_tree == NULL && current != NULL) {
+ sub_tree = *current;
+ }
+ }
+
+ *current = sub_tree;
+ return NULL;
+}
+
+static const char *ap_walk_config_sub(const ap_directive_t *current,
+ cmd_parms *parms,
+ ap_conf_vector_t *section_vector)
+{
+ const command_rec *cmd;
+ ap_mod_list *ml;
+ char *dir = apr_pstrdup(parms->pool, current->directive);
+
+ ap_str_tolower(dir);
+
+ ml = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING);
+
+ if (ml == NULL) {
+ parms->err_directive = current;
+ return apr_pstrcat(parms->pool, "Invalid command '",
+ current->directive,
+ "', perhaps misspelled or defined by a module "
+ "not included in the server configuration",
+ NULL);
+ }
+
+ for ( ; ml != NULL; ml = ml->next) {
+ void *dir_config = ap_set_config_vectors(parms->server,
+ section_vector,
+ parms->path,
+ ml->m,
+ parms->pool);
+ const char *retval;
+ cmd = ml->cmd;
+
+ /* Once was enough? */
+ if (cmd->req_override & EXEC_ON_READ) {
+ continue;
+ }
+
+ retval = invoke_cmd(cmd, parms, dir_config, current->args);
+
+ if (retval != NULL && strcmp(retval, DECLINE_CMD) != 0) {
+ /* If the directive in error has already been set, don't
+ * replace it. Otherwise, an error inside a container
+ * will be reported as occuring on the first line of the
+ * container.
+ */
+ if (!parms->err_directive) {
+ parms->err_directive = current;
+ }
+ return retval;
+ }
+ }
+
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_walk_config(ap_directive_t *current,
+ cmd_parms *parms,
+ ap_conf_vector_t *section_vector)
+{
+ ap_conf_vector_t *oldconfig = parms->context;
+
+ parms->context = section_vector;
+
+ /* scan through all directives, executing each one */
+ for (; current != NULL; current = current->next) {
+ const char *errmsg;
+
+ parms->directive = current;
+
+ /* actually parse the command and execute the correct function */
+ errmsg = ap_walk_config_sub(current, parms, section_vector);
+ if (errmsg != NULL) {
+ /* restore the context (just in case) */
+ parms->context = oldconfig;
+ return errmsg;
+ }
+ }
+
+ parms->context = oldconfig;
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_build_config(cmd_parms *parms,
+ apr_pool_t *p, apr_pool_t *temp_pool,
+ ap_directive_t **conftree)
+{
+ ap_directive_t *current = *conftree;
+ ap_directive_t *curr_parent = NULL;
+ char *l = apr_palloc (temp_pool, MAX_STRING_LEN);
+ const char *errmsg;
+
+ if (current != NULL) {
+ while (current->next) {
+ current = current->next;
+ }
+ }
+
+ while (!(ap_cfg_getline(l, MAX_STRING_LEN, parms->config_file))) {
+ errmsg = ap_build_config_sub(p, temp_pool, l, parms,
+ &current, &curr_parent, conftree);
+ if (errmsg != NULL)
+ return errmsg;
+
+ if (*conftree == NULL && curr_parent != NULL) {
+ *conftree = curr_parent;
+ }
+
+ if (*conftree == NULL && current != NULL) {
+ *conftree = current;
+ }
+ }
+
+ if (curr_parent != NULL) {
+ errmsg = "";
+
+ while (curr_parent != NULL) {
+ errmsg = apr_psprintf(p, "%s%s%s:%u: %s> was not closed.",
+ errmsg,
+ *errmsg == '\0' ? "" : APR_EOL_STR,
+ curr_parent->filename,
+ curr_parent->line_num,
+ curr_parent->directive);
+
+ parms->err_directive = curr_parent;
+ curr_parent = curr_parent->parent;
+ }
+
+ return errmsg;
+ }
+
+ return NULL;
+}
+
+/*
+ * Generic command functions...
+ */
+
+AP_DECLARE_NONSTD(const char *) ap_set_string_slot(cmd_parms *cmd,
+ void *struct_ptr,
+ const char *arg)
+{
+ int offset = (int)(long)cmd->info;
+
+ *(const char **)((char *)struct_ptr + offset) = arg;
+
+ return NULL;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_int_slot(cmd_parms *cmd,
+ void *struct_ptr,
+ const char *arg)
+{
+ char *endptr;
+ char *error_str = NULL;
+ int offset = (int)(long)cmd->info;
+
+ *(int *)((char*)struct_ptr + offset) = strtol(arg, &endptr, 10);
+
+ if ((*arg == '\0') || (*endptr != '\0')) {
+ error_str = apr_psprintf(cmd->pool,
+ "Invalid value for directive %s, expected integer",
+ cmd->directive->directive);
+ }
+
+ return error_str;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_string_slot_lower(cmd_parms *cmd,
+ void *struct_ptr,
+ const char *arg_)
+{
+ char *arg = apr_pstrdup(cmd->pool,arg_);
+ int offset = (int)(long)cmd->info;
+
+ ap_str_tolower(arg);
+ *(char **)((char *)struct_ptr + offset) = arg;
+
+ return NULL;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_flag_slot(cmd_parms *cmd,
+ void *struct_ptr_v, int arg)
+{
+ int offset = (int)(long)cmd->info;
+ char *struct_ptr = (char *)struct_ptr_v;
+
+ *(int *)(struct_ptr + offset) = arg ? 1 : 0;
+
+ return NULL;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_file_slot(cmd_parms *cmd, void *struct_ptr,
+ const char *arg)
+{
+ /* Prepend server_root to relative arg.
+ * This allows most args to be independent of server_root,
+ * so the server can be moved or mirrored with less pain.
+ */
+ const char *path;
+ int offset = (int)(long)cmd->info;
+
+ path = ap_server_root_relative(cmd->pool, arg);
+
+ if (!path) {
+ return apr_pstrcat(cmd->pool, "Invalid file path ",
+ arg, NULL);
+ }
+
+ *(const char **) ((char*)struct_ptr + offset) = path;
+
+ return NULL;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_deprecated(cmd_parms *cmd,
+ void *struct_ptr,
+ const char *arg)
+{
+ return cmd->cmd->errmsg;
+}
+
+/*****************************************************************
+ *
+ * Reading whole config files...
+ */
+
+static cmd_parms default_parms =
+{NULL, 0, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file)
+{
+ char *newpath = NULL;
+ apr_status_t rv;
+ rv = apr_filepath_merge(&newpath, ap_server_root, file,
+ APR_FILEPATH_TRUENAME, p);
+ if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
+ || APR_STATUS_IS_ENOENT(rv)
+ || APR_STATUS_IS_ENOTDIR(rv))) {
+ return newpath;
+ }
+ else {
+ return NULL;
+ }
+}
+
+AP_DECLARE(const char *) ap_soak_end_container(cmd_parms *cmd, char *directive)
+{
+ char l[MAX_STRING_LEN];
+ const char *args;
+ char *cmd_name;
+
+ while(!(ap_cfg_getline(l, MAX_STRING_LEN, cmd->config_file))) {
+#if RESOLVE_ENV_PER_TOKEN
+ args = l;
+#else
+ args = ap_resolve_env(cmd->temp_pool, l);
+#endif
+
+ cmd_name = ap_getword_conf(cmd->pool, &args);
+ if (cmd_name[0] == '<') {
+ if (cmd_name[1] == '/') {
+ cmd_name[strlen(cmd_name) - 1] = '\0';
+
+ if (strcasecmp(cmd_name + 2, directive + 1) != 0) {
+ return apr_pstrcat(cmd->pool, "Expected </",
+ directive + 1, "> but saw ",
+ cmd_name, ">", NULL);
+ }
+
+ return NULL; /* found end of container */
+ }
+ else {
+ const char *msg;
+
+ if (*args == '\0' && cmd_name[strlen(cmd_name) - 1] == '>') {
+ cmd_name[strlen(cmd_name) - 1] = '\0';
+ }
+
+ if ((msg = ap_soak_end_container(cmd, cmd_name)) != NULL) {
+ return msg;
+ }
+ }
+ }
+ }
+
+ return apr_pstrcat(cmd->pool, "Expected </",
+ directive + 1, "> before end of configuration",
+ NULL);
+}
+
+static const char *execute_now(char *cmd_line, const char *args,
+ cmd_parms *parms,
+ apr_pool_t *p, apr_pool_t *ptemp,
+ ap_directive_t **sub_tree,
+ ap_directive_t *parent)
+{
+ const command_rec *cmd;
+ ap_mod_list *ml;
+ char *dir = apr_pstrdup(parms->pool, cmd_line);
+
+ ap_str_tolower(dir);
+
+ ml = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING);
+
+ if (ml == NULL) {
+ return apr_pstrcat(parms->pool, "Invalid command '",
+ cmd_line,
+ "', perhaps misspelled or defined by a module "
+ "not included in the server configuration",
+ NULL);
+ }
+
+ for ( ; ml != NULL; ml = ml->next) {
+ const char *retval;
+ cmd = ml->cmd;
+
+ retval = invoke_cmd(cmd, parms, sub_tree, args);
+
+ if (retval != NULL) {
+ return retval;
+ }
+ }
+
+ return NULL;
+}
+
+/* This structure and the following functions are needed for the
+ * table-based config file reading. They are passed to the
+ * cfg_open_custom() routine.
+ */
+
+/* Structure to be passed to cfg_open_custom(): it contains an
+ * index which is incremented from 0 to nelts on each call to
+ * cfg_getline() (which in turn calls arr_elts_getstr())
+ * and an apr_array_header_t pointer for the string array.
+ */
+typedef struct {
+ apr_array_header_t *array;
+ int curr_idx;
+} arr_elts_param_t;
+
+
+/* arr_elts_getstr() returns the next line from the string array. */
+static void *arr_elts_getstr(void *buf, size_t bufsiz, void *param)
+{
+ arr_elts_param_t *arr_param = (arr_elts_param_t *)param;
+
+ /* End of array reached? */
+ if (++arr_param->curr_idx > arr_param->array->nelts)
+ return NULL;
+
+ /* return the line */
+ apr_cpystrn(buf,
+ ((char **)arr_param->array->elts)[arr_param->curr_idx - 1],
+ bufsiz);
+
+ return buf;
+}
+
+
+/* arr_elts_close(): dummy close routine (makes sure no more lines can be read) */
+static int arr_elts_close(void *param)
+{
+ arr_elts_param_t *arr_param = (arr_elts_param_t *)param;
+
+ arr_param->curr_idx = arr_param->array->nelts;
+
+ return 0;
+}
+
+static const char *process_command_config(server_rec *s,
+ apr_array_header_t *arr,
+ ap_directive_t **conftree,
+ apr_pool_t *p,
+ apr_pool_t *ptemp)
+{
+ const char *errmsg;
+ cmd_parms parms;
+ arr_elts_param_t arr_parms;
+
+ arr_parms.curr_idx = 0;
+ arr_parms.array = arr;
+
+ if (ap_config_hash == NULL) {
+ rebuild_conf_hash(s->process->pconf, 1);
+ }
+
+ parms = default_parms;
+ parms.pool = p;
+ parms.temp_pool = ptemp;
+ parms.server = s;
+ parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT);
+ parms.override_opts = OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER | OPT_MULTI;
+
+ parms.config_file = ap_pcfg_open_custom(p, "-c/-C directives",
+ &arr_parms, NULL,
+ arr_elts_getstr, arr_elts_close);
+
+ errmsg = ap_build_config(&parms, p, ptemp, conftree);
+ ap_cfg_closefile(parms.config_file);
+
+ if (errmsg) {
+ return apr_pstrcat(p, "Syntax error in -C/-c directive: ", errmsg,
+ NULL);
+ }
+
+ return NULL;
+}
+
+typedef struct {
+ char *fname;
+} fnames;
+
+static int fname_alphasort(const void *fn1, const void *fn2)
+{
+ const fnames *f1 = fn1;
+ const fnames *f2 = fn2;
+
+ return strcmp(f1->fname,f2->fname);
+}
+
+static const char *process_resource_config_nofnmatch(server_rec *s,
+ const char *fname,
+ ap_directive_t **conftree,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ unsigned depth)
+{
+ cmd_parms parms;
+ ap_configfile_t *cfp;
+ const char *error;
+ apr_status_t rv;
+
+ if (ap_is_directory(p, fname)) {
+ apr_dir_t *dirp;
+ apr_finfo_t dirent;
+ int current;
+ apr_array_header_t *candidates = NULL;
+ fnames *fnew;
+ char *path = apr_pstrdup(p, fname);
+
+ if (++depth > AP_MAX_INCLUDE_DIR_DEPTH) {
+ return apr_psprintf(p, "Directory %s exceeds the maximum include "
+ "directory nesting level of %u. You have "
+ "probably a recursion somewhere.", path,
+ AP_MAX_INCLUDE_DIR_DEPTH);
+ }
+
+ /*
+ * first course of business is to grok all the directory
+ * entries here and store 'em away. Recall we need full pathnames
+ * for this.
+ */
+ rv = apr_dir_open(&dirp, path, p);
+ if (rv != APR_SUCCESS) {
+ char errmsg[120];
+ return apr_psprintf(p, "Could not open config directory %s: %s",
+ path, apr_strerror(rv, errmsg, sizeof errmsg));
+ }
+
+ candidates = apr_array_make(p, 1, sizeof(fnames));
+ while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
+ /* strip out '.' and '..' */
+ if (strcmp(dirent.name, ".")
+ && strcmp(dirent.name, "..")) {
+ fnew = (fnames *) apr_array_push(candidates);
+ fnew->fname = ap_make_full_path(p, path, dirent.name);
+ }
+ }
+
+ apr_dir_close(dirp);
+ if (candidates->nelts != 0) {
+ qsort((void *) candidates->elts, candidates->nelts,
+ sizeof(fnames), fname_alphasort);
+
+ /*
+ * Now recurse these... we handle errors and subdirectories
+ * via the recursion, which is nice
+ */
+ for (current = 0; current < candidates->nelts; ++current) {
+ fnew = &((fnames *) candidates->elts)[current];
+ error = process_resource_config_nofnmatch(s, fnew->fname,
+ conftree, p, ptemp,
+ depth);
+ if (error) {
+ return error;
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ /* GCC's initialization extensions are soooo nice here... */
+ parms = default_parms;
+ parms.pool = p;
+ parms.temp_pool = ptemp;
+ parms.server = s;
+ parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT);
+ parms.override_opts = OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER | OPT_MULTI;
+
+ rv = ap_pcfg_openfile(&cfp, p, fname);
+ if (rv != APR_SUCCESS) {
+ char errmsg[120];
+ return apr_psprintf(p, "Could not open configuration file %s: %s",
+ fname, apr_strerror(rv, errmsg, sizeof errmsg));
+ }
+
+ parms.config_file = cfp;
+ error = ap_build_config(&parms, p, ptemp, conftree);
+ ap_cfg_closefile(cfp);
+
+ if (error) {
+ return apr_psprintf(p, "Syntax error on line %d of %s: %s",
+ parms.err_directive->line_num,
+ parms.err_directive->filename, error);
+ }
+
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_process_resource_config(server_rec *s,
+ const char *fname,
+ ap_directive_t **conftree,
+ apr_pool_t *p,
+ apr_pool_t *ptemp)
+{
+ /* XXX: lstat() won't work on the wildcard pattern...
+ */
+
+ /* don't require conf/httpd.conf if we have a -C or -c switch */
+ if ((ap_server_pre_read_config->nelts
+ || ap_server_post_read_config->nelts)
+ && !(strcmp(fname, ap_server_root_relative(p, SERVER_CONFIG_FILE)))) {
+ apr_finfo_t finfo;
+
+ if (apr_stat(&finfo, fname, APR_FINFO_LINK | APR_FINFO_TYPE, p) != APR_SUCCESS)
+ return NULL;
+ }
+
+ if (!apr_fnmatch_test(fname)) {
+ return process_resource_config_nofnmatch(s, fname, conftree, p, ptemp,
+ 0);
+ }
+ else {
+ apr_dir_t *dirp;
+ apr_finfo_t dirent;
+ int current;
+ apr_array_header_t *candidates = NULL;
+ fnames *fnew;
+ apr_status_t rv;
+ char *path = apr_pstrdup(p, fname), *pattern = NULL;
+
+ pattern = ap_strrchr(path, '/');
+
+ AP_DEBUG_ASSERT(pattern != NULL); /* path must be absolute. */
+
+ *pattern++ = '\0';
+
+ if (apr_fnmatch_test(path)) {
+ return apr_pstrcat(p, "Wildcard patterns not allowed in Include ",
+ fname, NULL);
+ }
+
+ if (!ap_is_directory(p, path)){
+ return apr_pstrcat(p, "Include directory '", path, "' not found",
+ NULL);
+ }
+
+ if (!apr_fnmatch_test(pattern)) {
+ return apr_pstrcat(p, "Must include a wildcard pattern for "
+ "Include ", fname, NULL);
+ }
+
+ /*
+ * first course of business is to grok all the directory
+ * entries here and store 'em away. Recall we need full pathnames
+ * for this.
+ */
+ rv = apr_dir_open(&dirp, path, p);
+ if (rv != APR_SUCCESS) {
+ char errmsg[120];
+ return apr_psprintf(p, "Could not open config directory %s: %s",
+ path, apr_strerror(rv, errmsg, sizeof errmsg));
+ }
+
+ candidates = apr_array_make(p, 1, sizeof(fnames));
+ while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
+ /* strip out '.' and '..' */
+ if (strcmp(dirent.name, ".")
+ && strcmp(dirent.name, "..")
+ && (apr_fnmatch(pattern, dirent.name,
+ APR_FNM_PERIOD) == APR_SUCCESS)) {
+ fnew = (fnames *) apr_array_push(candidates);
+ fnew->fname = ap_make_full_path(p, path, dirent.name);
+ }
+ }
+
+ apr_dir_close(dirp);
+ if (candidates->nelts != 0) {
+ const char *error;
+
+ qsort((void *) candidates->elts, candidates->nelts,
+ sizeof(fnames), fname_alphasort);
+
+ /*
+ * Now recurse these... we handle errors and subdirectories
+ * via the recursion, which is nice
+ */
+ for (current = 0; current < candidates->nelts; ++current) {
+ fnew = &((fnames *) candidates->elts)[current];
+ error = process_resource_config_nofnmatch(s, fnew->fname,
+ conftree, p,
+ ptemp, 0);
+ if (error) {
+ return error;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+AP_DECLARE(int) ap_process_config_tree(server_rec *s,
+ ap_directive_t *conftree,
+ apr_pool_t *p,
+ apr_pool_t *ptemp)
+{
+ const char *errmsg;
+ cmd_parms parms;
+
+ parms = default_parms;
+ parms.pool = p;
+ parms.temp_pool = ptemp;
+ parms.server = s;
+ parms.override = (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT);
+ parms.override_opts = OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER | OPT_MULTI;
+ parms.limited = -1;
+
+ errmsg = ap_walk_config(conftree, &parms, s->lookup_defaults);
+ if (errmsg) {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, p,
+ "Syntax error on line %d of %s:",
+ parms.err_directive->line_num,
+ parms.err_directive->filename);
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, p,
+ "%s", errmsg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return OK;
+}
+
+AP_CORE_DECLARE(int) ap_parse_htaccess(ap_conf_vector_t **result,
+ request_rec *r, int override,
+ int override_opts,
+ const char *d, const char *access_name)
+{
+ ap_configfile_t *f = NULL;
+ cmd_parms parms;
+ char *filename = NULL;
+ const struct htaccess_result *cache;
+ struct htaccess_result *new;
+ ap_conf_vector_t *dc = NULL;
+ apr_status_t status;
+
+ /* firstly, search cache */
+ for (cache = r->htaccess; cache != NULL; cache = cache->next) {
+ if (cache->override == override && strcmp(cache->dir, d) == 0) {
+ *result = cache->htaccess;
+ return OK;
+ }
+ }
+
+ parms = default_parms;
+ parms.override = override;
+ parms.override_opts = override_opts;
+ parms.pool = r->pool;
+ parms.temp_pool = r->pool;
+ parms.server = r->server;
+ parms.path = apr_pstrdup(r->pool, d);
+
+ /* loop through the access names and find the first one */
+ while (access_name[0]) {
+ /* AFAICT; there is no use of the actual 'filename' against
+ * any canonicalization, so we will simply take the given
+ * name, ignoring case sensitivity and aliases
+ */
+ filename = ap_make_full_path(r->pool, d,
+ ap_getword_conf(r->pool, &access_name));
+ status = ap_pcfg_openfile(&f, r->pool, filename);
+
+ if (status == APR_SUCCESS) {
+ const char *errmsg;
+ ap_directive_t *temptree = NULL;
+
+ dc = ap_create_per_dir_config(r->pool);
+
+ parms.config_file = f;
+ errmsg = ap_build_config(&parms, r->pool, r->pool, &temptree);
+ if (errmsg == NULL)
+ errmsg = ap_walk_config(temptree, &parms, dc);
+
+ ap_cfg_closefile(f);
+
+ if (errmsg) {
+ ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r,
+ "%s: %s", filename, errmsg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *result = dc;
+ break;
+ }
+ else {
+ if (!APR_STATUS_IS_ENOENT(status)
+ && !APR_STATUS_IS_ENOTDIR(status)) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, status, r,
+ "%s pcfg_openfile: unable to check htaccess file, "
+ "ensure it is readable",
+ filename);
+ apr_table_setn(r->notes, "error-notes",
+ "Server unable to read htaccess file, denying "
+ "access to be safe");
+ return HTTP_FORBIDDEN;
+ }
+ }
+ }
+
+ /* cache it */
+ new = apr_palloc(r->pool, sizeof(struct htaccess_result));
+ new->dir = parms.path;
+ new->override = override;
+ new->override_opts = override_opts;
+ new->htaccess = dc;
+
+ /* add to head of list */
+ new->next = r->htaccess;
+ r->htaccess = new;
+
+ return OK;
+}
+
+AP_CORE_DECLARE(const char *) ap_init_virtual_host(apr_pool_t *p,
+ const char *hostname,
+ server_rec *main_server,
+ server_rec **ps)
+{
+ server_rec *s = (server_rec *) apr_pcalloc(p, sizeof(server_rec));
+
+ /* TODO: this crap belongs in http_core */
+ s->process = main_server->process;
+ s->server_admin = NULL;
+ s->server_hostname = NULL;
+ s->server_scheme = NULL;
+ s->error_fname = NULL;
+ s->timeout = 0;
+ s->keep_alive_timeout = 0;
+ s->keep_alive = -1;
+ s->keep_alive_max = -1;
+ s->error_log = main_server->error_log;
+ s->loglevel = main_server->loglevel;
+ /* useful default, otherwise we get a port of 0 on redirects */
+ s->port = main_server->port;
+ s->next = NULL;
+
+ s->is_virtual = 1;
+ s->names = apr_array_make(p, 4, sizeof(char **));
+ s->wild_names = apr_array_make(p, 4, sizeof(char **));
+
+ s->module_config = create_empty_config(p);
+ s->lookup_defaults = ap_create_per_dir_config(p);
+
+ s->limit_req_line = main_server->limit_req_line;
+ s->limit_req_fieldsize = main_server->limit_req_fieldsize;
+ s->limit_req_fields = main_server->limit_req_fields;
+
+ *ps = s;
+
+ return ap_parse_vhost_addrs(p, hostname, s);
+}
+
+
+AP_DECLARE(void) ap_fixup_virtual_hosts(apr_pool_t *p, server_rec *main_server)
+{
+ server_rec *virt;
+
+ for (virt = main_server->next; virt; virt = virt->next) {
+ merge_server_configs(p, main_server->module_config,
+ virt->module_config);
+
+ virt->lookup_defaults =
+ ap_merge_per_dir_configs(p, main_server->lookup_defaults,
+ virt->lookup_defaults);
+
+ if (virt->server_admin == NULL)
+ virt->server_admin = main_server->server_admin;
+
+ if (virt->timeout == 0)
+ virt->timeout = main_server->timeout;
+
+ if (virt->keep_alive_timeout == 0)
+ virt->keep_alive_timeout = main_server->keep_alive_timeout;
+
+ if (virt->keep_alive == -1)
+ virt->keep_alive = main_server->keep_alive;
+
+ if (virt->keep_alive_max == -1)
+ virt->keep_alive_max = main_server->keep_alive_max;
+
+ /* XXX: this is really something that should be dealt with by a
+ * post-config api phase
+ */
+ ap_core_reorder_directories(p, virt);
+ }
+
+ ap_core_reorder_directories(p, main_server);
+}
+
+/*****************************************************************
+ *
+ * Getting *everything* configured...
+ */
+
+static void init_config_globals(apr_pool_t *p)
+{
+ /* Global virtual host hash bucket pointers. Init to null. */
+ ap_init_vhost_config(p);
+}
+
+static server_rec *init_server_config(process_rec *process, apr_pool_t *p)
+{
+ apr_status_t rv;
+ server_rec *s = (server_rec *) apr_pcalloc(p, sizeof(server_rec));
+
+ apr_file_open_stderr(&s->error_log, p);
+ s->process = process;
+ s->port = 0;
+ s->server_admin = DEFAULT_ADMIN;
+ s->server_hostname = NULL;
+ s->server_scheme = NULL;
+ s->error_fname = DEFAULT_ERRORLOG;
+ s->loglevel = DEFAULT_LOGLEVEL;
+ s->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE;
+ s->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE;
+ s->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS;
+ s->timeout = apr_time_from_sec(DEFAULT_TIMEOUT);
+ s->keep_alive_timeout = apr_time_from_sec(DEFAULT_KEEPALIVE_TIMEOUT);
+ s->keep_alive_max = DEFAULT_KEEPALIVE;
+ s->keep_alive = 1;
+ s->next = NULL;
+ s->addrs = apr_pcalloc(p, sizeof(server_addr_rec));
+
+ /* NOT virtual host; don't match any real network interface */
+ rv = apr_sockaddr_info_get(&s->addrs->host_addr,
+ NULL, APR_INET, 0, 0, p);
+ ap_assert(rv == APR_SUCCESS); /* otherwise: bug or no storage */
+
+ s->addrs->host_port = 0; /* matches any port */
+ s->addrs->virthost = ""; /* must be non-NULL */
+ s->names = s->wild_names = NULL;
+
+ s->module_config = create_server_config(p, s);
+ s->lookup_defaults = create_default_per_dir_config(p);
+
+ return s;
+}
+
+
+AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp,
+ const char *filename,
+ ap_directive_t **conftree)
+{
+ const char *confname, *error;
+ apr_pool_t *p = process->pconf;
+ server_rec *s = init_server_config(process, p);
+
+ init_config_globals(p);
+
+ /* All server-wide config files now have the SAME syntax... */
+ error = process_command_config(s, ap_server_pre_read_config, conftree,
+ p, ptemp);
+ if (error) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, "%s: %s",
+ ap_server_argv0, error);
+ return NULL;
+ }
+
+ /* process_command_config may change the ServerRoot so
+ * compute this config file name afterwards.
+ */
+ confname = ap_server_root_relative(p, filename);
+
+ if (!confname) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT,
+ APR_EBADPATH, NULL, "Invalid config file path %s",
+ filename);
+ return NULL;
+ }
+
+ error = ap_process_resource_config(s, confname, conftree, p, ptemp);
+ if (error) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL,
+ "%s: %s", ap_server_argv0, error);
+ return NULL;
+ }
+
+ error = process_command_config(s, ap_server_post_read_config, conftree,
+ p, ptemp);
+
+ if (error) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, "%s: %s",
+ ap_server_argv0, error);
+ return NULL;
+ }
+
+ return s;
+}
+
+AP_DECLARE(void) ap_single_module_configure(apr_pool_t *p, server_rec *s,
+ module *m)
+{
+ if (m->create_server_config)
+ ap_set_module_config(s->module_config, m,
+ (*m->create_server_config)(p, s));
+
+ if (m->create_dir_config)
+ ap_set_module_config(s->lookup_defaults, m,
+ (*m->create_dir_config)(p, NULL));
+}
+
+AP_DECLARE(void) ap_run_rewrite_args(process_rec *process)
+{
+ module *m;
+
+ for (m = ap_top_module; m; m = m->next) {
+ if (m->rewrite_args) {
+ (*m->rewrite_args)(process);
+ }
+ }
+}
+
+/********************************************************************
+ * Configuration directives are restricted in terms of where they may
+ * appear in the main configuration files and/or .htaccess files according
+ * to the bitmask req_override in the command_rec structure.
+ * If any of the overrides set in req_override are also allowed in the
+ * context in which the command is read, then the command is allowed.
+ * The context is determined as follows:
+ *
+ * inside *.conf --> override = (RSRC_CONF|OR_ALL)&~(OR_AUTHCFG|OR_LIMIT);
+ * within <Directory> or <Location> --> override = OR_ALL|ACCESS_CONF;
+ * within .htaccess --> override = AllowOverride for current directory;
+ *
+ * the result is, well, a rather confusing set of possibilities for when
+ * a particular directive is allowed to be used. This procedure prints
+ * in English where the given (pc) directive can be used.
+ */
+static void show_overrides(const command_rec *pc, module *pm)
+{
+ int n = 0;
+
+ printf("\tAllowed in *.conf ");
+ if ((pc->req_override & (OR_OPTIONS | OR_FILEINFO | OR_INDEXES))
+ || ((pc->req_override & RSRC_CONF)
+ && ((pc->req_override & (ACCESS_CONF | OR_AUTHCFG | OR_LIMIT))))) {
+ printf("anywhere");
+ }
+ else if (pc->req_override & RSRC_CONF) {
+ printf("only outside <Directory>, <Files> or <Location>");
+ }
+ else {
+ printf("only inside <Directory>, <Files> or <Location>");
+ }
+
+ /* Warn if the directive is allowed inside <Directory> or .htaccess
+ * but module doesn't support per-dir configuration
+ */
+ if ((pc->req_override & (OR_ALL | ACCESS_CONF)) && !pm->create_dir_config)
+ printf(" [no per-dir config]");
+
+ if (pc->req_override & OR_ALL) {
+ printf(" and in .htaccess\n\twhen AllowOverride");
+
+ if ((pc->req_override & OR_ALL) == OR_ALL) {
+ printf(" isn't None");
+ }
+ else {
+ printf(" includes ");
+
+ if (pc->req_override & OR_AUTHCFG) {
+ if (n++)
+ printf(" or ");
+
+ printf("AuthConfig");
+ }
+
+ if (pc->req_override & OR_LIMIT) {
+ if (n++)
+ printf(" or ");
+
+ printf("Limit");
+ }
+
+ if (pc->req_override & OR_OPTIONS) {
+ if (n++)
+ printf(" or ");
+
+ printf("Options");
+ }
+
+ if (pc->req_override & OR_FILEINFO) {
+ if (n++)
+ printf(" or ");
+
+ printf("FileInfo");
+ }
+
+ if (pc->req_override & OR_INDEXES) {
+ if (n++)
+ printf(" or ");
+
+ printf("Indexes");
+ }
+ }
+ }
+
+ printf("\n");
+}
+
+/* Show the preloaded configuration directives, the help string explaining
+ * the directive arguments, in what module they are handled, and in
+ * what parts of the configuration they are allowed. Used for httpd -L.
+ */
+AP_DECLARE(void) ap_show_directives(void)
+{
+ const command_rec *pc;
+ int n;
+
+ for (n = 0; ap_loaded_modules[n]; ++n) {
+ for (pc = ap_loaded_modules[n]->cmds; pc && pc->name; ++pc) {
+ printf("%s (%s)\n", pc->name, ap_loaded_modules[n]->name);
+
+ if (pc->errmsg)
+ printf("\t%s\n", pc->errmsg);
+
+ show_overrides(pc, ap_loaded_modules[n]);
+ }
+ }
+}
+
+/* Show the preloaded module names. Used for httpd -l. */
+AP_DECLARE(void) ap_show_modules(void)
+{
+ int n;
+
+ printf("Compiled in modules:\n");
+ for (n = 0; ap_loaded_modules[n]; ++n)
+ printf(" %s\n", ap_loaded_modules[n]->name);
+}
+
+AP_DECLARE(const char *) ap_show_mpm(void)
+{
+ return MPM_NAME;
+}
diff --git a/server/config.m4 b/server/config.m4
new file mode 100644
index 00000000..85fa4d17
--- /dev/null
+++ b/server/config.m4
@@ -0,0 +1,15 @@
+dnl ## Check for libraries
+
+dnl ## Check for header files
+
+AC_CHECK_HEADERS(bstring.h unistd.h)
+
+dnl ## Check for typedefs, structures, and compiler characteristics.
+
+dnl ## Check for library functions
+
+AC_CHECK_FUNCS(syslog)
+
+dnl Obsolete scoreboard code uses this.
+ AC_CHECK_HEADERS(sys/times.h)
+ AC_CHECK_FUNCS(times)
diff --git a/server/connection.c b/server/connection.c
new file mode 100644
index 00000000..d4880b0b
--- /dev/null
+++ b/server/connection.c
@@ -0,0 +1,181 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_connection.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "ap_mpm.h"
+#include "mpm_default.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_vhost.h"
+#include "scoreboard.h"
+#include "http_log.h"
+#include "util_filter.h"
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(create_connection)
+ APR_HOOK_LINK(process_connection)
+ APR_HOOK_LINK(pre_connection)
+)
+AP_IMPLEMENT_HOOK_RUN_FIRST(conn_rec *,create_connection,
+ (apr_pool_t *p, server_rec *server, apr_socket_t *csd, long conn_id, void *sbh, apr_bucket_alloc_t *alloc),
+ (p, server, csd, conn_id, sbh, alloc), NULL)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,process_connection,(conn_rec *c),(c),DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,pre_connection,(conn_rec *c, void *csd),(c, csd),OK,DECLINED)
+/*
+ * More machine-dependent networking gooo... on some systems,
+ * you've got to be *really* sure that all the packets are acknowledged
+ * before closing the connection, since the client will not be able
+ * to see the last response if their TCP buffer is flushed by a RST
+ * packet from us, which is what the server's TCP stack will send
+ * if it receives any request data after closing the connection.
+ *
+ * In an ideal world, this function would be accomplished by simply
+ * setting the socket option SO_LINGER and handling it within the
+ * server's TCP stack while the process continues on to the next request.
+ * Unfortunately, it seems that most (if not all) operating systems
+ * block the server process on close() when SO_LINGER is used.
+ * For those that don't, see USE_SO_LINGER below. For the rest,
+ * we have created a home-brew lingering_close.
+ *
+ * Many operating systems tend to block, puke, or otherwise mishandle
+ * calls to shutdown only half of the connection. You should define
+ * NO_LINGCLOSE in ap_config.h if such is the case for your system.
+ */
+#ifndef MAX_SECS_TO_LINGER
+#define MAX_SECS_TO_LINGER 30
+#endif
+
+AP_CORE_DECLARE(void) ap_flush_conn(conn_rec *c)
+{
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+
+ bb = apr_brigade_create(c->pool, c->bucket_alloc);
+
+ /* FLUSH bucket */
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ /* End Of Connection bucket */
+ b = ap_bucket_eoc_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ ap_pass_brigade(c->output_filters, bb);
+}
+
+/* we now proceed to read from the client until we get EOF, or until
+ * MAX_SECS_TO_LINGER has passed. the reasons for doing this are
+ * documented in a draft:
+ *
+ * http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-connection-00.txt
+ *
+ * in a nutshell -- if we don't make this effort we risk causing
+ * TCP RST packets to be sent which can tear down a connection before
+ * all the response data has been sent to the client.
+ */
+#define SECONDS_TO_LINGER 2
+AP_DECLARE(void) ap_lingering_close(conn_rec *c)
+{
+ char dummybuf[512];
+ apr_size_t nbytes;
+ apr_time_t timeup = 0;
+ apr_socket_t *csd = ap_get_module_config(c->conn_config, &core_module);
+
+ if (!csd) {
+ return;
+ }
+
+ ap_update_child_status(c->sbh, SERVER_CLOSING, NULL);
+
+#ifdef NO_LINGCLOSE
+ ap_flush_conn(c); /* just close it */
+ apr_socket_close(csd);
+ return;
+#endif
+
+ /* Close the connection, being careful to send out whatever is still
+ * in our buffers. If possible, try to avoid a hard close until the
+ * client has ACKed our FIN and/or has stopped sending us data.
+ */
+
+ /* Send any leftover data to the client, but never try to again */
+ ap_flush_conn(c);
+
+ if (c->aborted) {
+ apr_socket_close(csd);
+ return;
+ }
+
+ /* Shut down the socket for write, which will send a FIN
+ * to the peer.
+ */
+ if (apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS
+ || c->aborted) {
+ apr_socket_close(csd);
+ return;
+ }
+
+ /* Read available data from the client whilst it continues sending
+ * it, for a maximum time of MAX_SECS_TO_LINGER. If the client
+ * does not send any data within 2 seconds (a value pulled from
+ * Apache 1.3 which seems to work well), give up.
+ */
+ apr_socket_timeout_set(csd, apr_time_from_sec(SECONDS_TO_LINGER));
+ apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 1);
+
+ /* The common path here is that the initial apr_socket_recv() call
+ * will return 0 bytes read; so that case must avoid the expensive
+ * apr_time_now() call and time arithmetic. */
+
+ do {
+ nbytes = sizeof(dummybuf);
+ if (apr_socket_recv(csd, dummybuf, &nbytes) || nbytes == 0)
+ break;
+
+ if (timeup == 0) {
+ /* First time through; calculate now + 30 seconds. */
+ timeup = apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER);
+ continue;
+ }
+ } while (apr_time_now() < timeup);
+
+ apr_socket_close(csd);
+ return;
+}
+
+AP_CORE_DECLARE(void) ap_process_connection(conn_rec *c, void *csd)
+{
+ int rc;
+ ap_update_vhost_given_ip(c);
+
+ rc = ap_run_pre_connection(c, csd);
+ if (rc != OK && rc != DONE) {
+ c->aborted = 1;
+ }
+
+ if (!c->aborted) {
+ ap_run_process_connection(c);
+ }
+}
+
diff --git a/server/core.c b/server/core.c
new file mode 100644
index 00000000..3cf40444
--- /dev/null
+++ b/server/core.c
@@ -0,0 +1,3999 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_fnmatch.h"
+#include "apr_hash.h"
+#include "apr_thread_proc.h" /* for RLIMIT stuff */
+#include "apr_hooks.h"
+
+#define APR_WANT_IOVEC
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h" /* For index_of_response(). Grump. */
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_main.h" /* For the default_handler below... */
+#include "http_log.h"
+#include "util_md5.h"
+#include "http_connection.h"
+#include "apr_buckets.h"
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "mpm.h"
+#include "mpm_common.h"
+#include "scoreboard.h"
+#include "mod_core.h"
+#include "mod_proxy.h"
+#include "ap_listen.h"
+
+#include "mod_so.h" /* for ap_find_loaded_module_symbol */
+
+/* LimitRequestBody handling */
+#define AP_LIMIT_REQ_BODY_UNSET ((apr_off_t) -1)
+#define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 0)
+
+/* LimitXMLRequestBody handling */
+#define AP_LIMIT_UNSET ((long) -1)
+#define AP_DEFAULT_LIMIT_XML_BODY ((size_t)1000000)
+
+#define AP_MIN_SENDFILE_BYTES (256)
+
+/* maximum include nesting level */
+#ifndef AP_MAX_INCLUDE_DEPTH
+#define AP_MAX_INCLUDE_DEPTH (128)
+#endif
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(get_mgmt_items)
+)
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int, get_mgmt_items,
+ (apr_pool_t *p, const char *val, apr_hash_t *ht),
+ (p, val, ht), OK, DECLINED)
+
+/* Server core module... This module provides support for really basic
+ * server operations, including options and commands which control the
+ * operation of other modules. Consider this the bureaucracy module.
+ *
+ * The core module also defines handlers, etc., do handle just enough
+ * to allow a server with the core module ONLY to actually serve documents
+ * (though it slaps DefaultType on all of 'em); this was useful in testing,
+ * but may not be worth preserving.
+ *
+ * This file could almost be mod_core.c, except for the stuff which affects
+ * the http_conf_globals.
+ */
+
+/* Handles for core filters */
+AP_DECLARE_DATA ap_filter_rec_t *ap_subreq_core_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_core_output_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_content_length_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_core_input_filter_handle;
+
+/* magic pointer for ErrorDocument xxx "default" */
+static char errordocument_default;
+
+static void *create_core_dir_config(apr_pool_t *a, char *dir)
+{
+ core_dir_config *conf;
+ int i;
+
+ conf = (core_dir_config *)apr_pcalloc(a, sizeof(core_dir_config));
+
+ /* conf->r and conf->d[_*] are initialized by dirsection() or left NULL */
+
+ conf->opts = dir ? OPT_UNSET : OPT_UNSET|OPT_ALL;
+ conf->opts_add = conf->opts_remove = OPT_NONE;
+ conf->override = dir ? OR_UNSET : OR_UNSET|OR_ALL;
+ conf->override_opts = OPT_UNSET | OPT_ALL | OPT_INCNOEXEC | OPT_SYM_OWNER
+ | OPT_MULTI;
+
+ conf->content_md5 = 2;
+ conf->accept_path_info = 3;
+
+ conf->use_canonical_name = USE_CANONICAL_NAME_UNSET;
+ conf->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_UNSET;
+
+ conf->hostname_lookups = HOSTNAME_LOOKUP_UNSET;
+ conf->satisfy = apr_palloc(a, sizeof(*conf->satisfy) * METHODS);
+ for (i = 0; i < METHODS; ++i) {
+ conf->satisfy[i] = SATISFY_NOSPEC;
+ }
+
+#ifdef RLIMIT_CPU
+ conf->limit_cpu = NULL;
+#endif
+#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+ conf->limit_mem = NULL;
+#endif
+#ifdef RLIMIT_NPROC
+ conf->limit_nproc = NULL;
+#endif
+
+ conf->limit_req_body = AP_LIMIT_REQ_BODY_UNSET;
+ conf->limit_xml_body = AP_LIMIT_UNSET;
+ conf->sec_file = apr_array_make(a, 2, sizeof(ap_conf_vector_t *));
+
+ conf->server_signature = srv_sig_unset;
+
+ conf->add_default_charset = ADD_DEFAULT_CHARSET_UNSET;
+ conf->add_default_charset_name = DEFAULT_ADD_DEFAULT_CHARSET_NAME;
+
+ /* Overriding all negotiation
+ */
+ conf->mime_type = NULL;
+ conf->handler = NULL;
+ conf->output_filters = NULL;
+ conf->input_filters = NULL;
+
+ /*
+ * Flag for use of inodes in ETags.
+ */
+ conf->etag_bits = ETAG_UNSET;
+ conf->etag_add = ETAG_UNSET;
+ conf->etag_remove = ETAG_UNSET;
+
+ conf->enable_mmap = ENABLE_MMAP_UNSET;
+ conf->enable_sendfile = ENABLE_SENDFILE_UNSET;
+ conf->allow_encoded_slashes = 0;
+
+ return (void *)conf;
+}
+
+/*
+ * Overlay one hash table of ct_output_filters onto another
+ */
+static void *merge_ct_filters(apr_pool_t *p,
+ const void *key,
+ apr_ssize_t klen,
+ const void *overlay_val,
+ const void *base_val,
+ const void *data)
+{
+ ap_filter_rec_t *cur;
+ const ap_filter_rec_t *overlay_info = (const ap_filter_rec_t *)overlay_val;
+ const ap_filter_rec_t *base_info = (const ap_filter_rec_t *)base_val;
+
+ cur = NULL;
+
+ while (overlay_info) {
+ ap_filter_rec_t *new;
+
+ new = apr_pcalloc(p, sizeof(ap_filter_rec_t));
+ new->name = apr_pstrdup(p, overlay_info->name);
+ new->next = cur;
+ cur = new;
+ overlay_info = overlay_info->next;
+ }
+
+ while (base_info) {
+ ap_filter_rec_t *f;
+ int found = 0;
+
+ /* We can't have dups. */
+ f = cur;
+ while (f) {
+ if (!strcasecmp(base_info->name, f->name)) {
+ found = 1;
+ break;
+ }
+
+ f = f->next;
+ }
+
+ if (!found) {
+ f = apr_pcalloc(p, sizeof(ap_filter_rec_t));
+ f->name = apr_pstrdup(p, base_info->name);
+ f->next = cur;
+ cur = f;
+ }
+
+ base_info = base_info->next;
+ }
+
+ return cur;
+}
+
+static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv)
+{
+ core_dir_config *base = (core_dir_config *)basev;
+ core_dir_config *new = (core_dir_config *)newv;
+ core_dir_config *conf;
+ int i;
+
+ /* Create this conf by duplicating the base, replacing elements
+ * (or creating copies for merging) where new-> values exist.
+ */
+ conf = (core_dir_config *)apr_palloc(a, sizeof(core_dir_config));
+ memcpy(conf, base, sizeof(core_dir_config));
+
+ conf->d = new->d;
+ conf->d_is_fnmatch = new->d_is_fnmatch;
+ conf->d_components = new->d_components;
+ conf->r = new->r;
+
+ if (new->opts & OPT_UNSET) {
+ /* there was no explicit setting of new->opts, so we merge
+ * preserve the invariant (opts_add & opts_remove) == 0
+ */
+ conf->opts_add = (conf->opts_add & ~new->opts_remove) | new->opts_add;
+ conf->opts_remove = (conf->opts_remove & ~new->opts_add)
+ | new->opts_remove;
+ conf->opts = (conf->opts & ~conf->opts_remove) | conf->opts_add;
+ if ((base->opts & OPT_INCNOEXEC) && (new->opts & OPT_INCLUDES)) {
+ conf->opts = (conf->opts & ~OPT_INCNOEXEC) | OPT_INCLUDES;
+ }
+ }
+ else {
+ /* otherwise we just copy, because an explicit opts setting
+ * overrides all earlier +/- modifiers
+ */
+ conf->opts = new->opts;
+ conf->opts_add = new->opts_add;
+ conf->opts_remove = new->opts_remove;
+ }
+
+ if (!(new->override & OR_UNSET)) {
+ conf->override = new->override;
+ }
+
+ if (!(new->override_opts & OPT_UNSET)) {
+ conf->override_opts = new->override_opts;
+ }
+
+ if (new->ap_default_type) {
+ conf->ap_default_type = new->ap_default_type;
+ }
+
+ if (new->ap_auth_type) {
+ conf->ap_auth_type = new->ap_auth_type;
+ }
+
+ if (new->ap_auth_name) {
+ conf->ap_auth_name = new->ap_auth_name;
+ }
+
+ if (new->ap_requires) {
+ conf->ap_requires = new->ap_requires;
+ }
+
+ if (conf->response_code_strings == NULL) {
+ conf->response_code_strings = new->response_code_strings;
+ }
+ else if (new->response_code_strings != NULL) {
+ /* If we merge, the merge-result must have it's own array
+ */
+ conf->response_code_strings = apr_palloc(a,
+ sizeof(*conf->response_code_strings) * RESPONSE_CODES);
+ memcpy(conf->response_code_strings, base->response_code_strings,
+ sizeof(*conf->response_code_strings) * RESPONSE_CODES);
+
+ for (i = 0; i < RESPONSE_CODES; ++i) {
+ if (new->response_code_strings[i] != NULL) {
+ conf->response_code_strings[i] = new->response_code_strings[i];
+ }
+ }
+ }
+ /* Otherwise we simply use the base->response_code_strings array
+ */
+
+ if (new->hostname_lookups != HOSTNAME_LOOKUP_UNSET) {
+ conf->hostname_lookups = new->hostname_lookups;
+ }
+
+ if ((new->content_md5 & 2) == 0) {
+ conf->content_md5 = new->content_md5;
+ }
+
+ if (new->accept_path_info != 3) {
+ conf->accept_path_info = new->accept_path_info;
+ }
+
+ if (new->use_canonical_name != USE_CANONICAL_NAME_UNSET) {
+ conf->use_canonical_name = new->use_canonical_name;
+ }
+
+ if (new->use_canonical_phys_port != USE_CANONICAL_PHYS_PORT_UNSET) {
+ conf->use_canonical_phys_port = new->use_canonical_phys_port;
+ }
+
+#ifdef RLIMIT_CPU
+ if (new->limit_cpu) {
+ conf->limit_cpu = new->limit_cpu;
+ }
+#endif
+
+#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+ if (new->limit_mem) {
+ conf->limit_mem = new->limit_mem;
+ }
+#endif
+
+#ifdef RLIMIT_NPROC
+ if (new->limit_nproc) {
+ conf->limit_nproc = new->limit_nproc;
+ }
+#endif
+
+ if (new->limit_req_body != AP_LIMIT_REQ_BODY_UNSET) {
+ conf->limit_req_body = new->limit_req_body;
+ }
+
+ if (new->limit_xml_body != AP_LIMIT_UNSET)
+ conf->limit_xml_body = new->limit_xml_body;
+ else
+ conf->limit_xml_body = base->limit_xml_body;
+
+ if (!conf->sec_file) {
+ conf->sec_file = new->sec_file;
+ }
+ else if (new->sec_file) {
+ /* If we merge, the merge-result must have it's own array
+ */
+ conf->sec_file = apr_array_append(a, base->sec_file, new->sec_file);
+ }
+ /* Otherwise we simply use the base->sec_file array
+ */
+
+ /* use a separate ->satisfy[] array either way */
+ conf->satisfy = apr_palloc(a, sizeof(*conf->satisfy) * METHODS);
+ for (i = 0; i < METHODS; ++i) {
+ if (new->satisfy[i] != SATISFY_NOSPEC) {
+ conf->satisfy[i] = new->satisfy[i];
+ } else {
+ conf->satisfy[i] = base->satisfy[i];
+ }
+ }
+
+ if (new->server_signature != srv_sig_unset) {
+ conf->server_signature = new->server_signature;
+ }
+
+ if (new->add_default_charset != ADD_DEFAULT_CHARSET_UNSET) {
+ conf->add_default_charset = new->add_default_charset;
+ conf->add_default_charset_name = new->add_default_charset_name;
+ }
+
+ /* Overriding all negotiation
+ */
+ if (new->mime_type) {
+ conf->mime_type = new->mime_type;
+ }
+
+ if (new->handler) {
+ conf->handler = new->handler;
+ }
+
+ if (new->output_filters) {
+ conf->output_filters = new->output_filters;
+ }
+
+ if (new->input_filters) {
+ conf->input_filters = new->input_filters;
+ }
+
+ if (conf->ct_output_filters && new->ct_output_filters) {
+ conf->ct_output_filters = apr_hash_merge(a,
+ new->ct_output_filters,
+ conf->ct_output_filters,
+ merge_ct_filters,
+ NULL);
+ }
+ else if (new->ct_output_filters) {
+ conf->ct_output_filters = apr_hash_copy(a, new->ct_output_filters);
+ }
+ else if (conf->ct_output_filters) {
+ /* That memcpy above isn't enough. */
+ conf->ct_output_filters = apr_hash_copy(a, base->ct_output_filters);
+ }
+
+ /*
+ * Now merge the setting of the FileETag directive.
+ */
+ if (new->etag_bits == ETAG_UNSET) {
+ conf->etag_add =
+ (conf->etag_add & (~ new->etag_remove)) | new->etag_add;
+ conf->etag_remove =
+ (conf->opts_remove & (~ new->etag_add)) | new->etag_remove;
+ conf->etag_bits =
+ (conf->etag_bits & (~ conf->etag_remove)) | conf->etag_add;
+ }
+ else {
+ conf->etag_bits = new->etag_bits;
+ conf->etag_add = new->etag_add;
+ conf->etag_remove = new->etag_remove;
+ }
+
+ if (conf->etag_bits != ETAG_NONE) {
+ conf->etag_bits &= (~ ETAG_NONE);
+ }
+
+ if (new->enable_mmap != ENABLE_MMAP_UNSET) {
+ conf->enable_mmap = new->enable_mmap;
+ }
+
+ if (new->enable_sendfile != ENABLE_SENDFILE_UNSET) {
+ conf->enable_sendfile = new->enable_sendfile;
+ }
+
+ conf->allow_encoded_slashes = new->allow_encoded_slashes;
+
+ return (void*)conf;
+}
+
+static void *create_core_server_config(apr_pool_t *a, server_rec *s)
+{
+ core_server_config *conf;
+ int is_virtual = s->is_virtual;
+
+ conf = (core_server_config *)apr_pcalloc(a, sizeof(core_server_config));
+
+#ifdef GPROF
+ conf->gprof_dir = NULL;
+#endif
+
+ conf->access_name = is_virtual ? NULL : DEFAULT_ACCESS_FNAME;
+ conf->ap_document_root = is_virtual ? NULL : DOCUMENT_LOCATION;
+ conf->sec_dir = apr_array_make(a, 40, sizeof(ap_conf_vector_t *));
+ conf->sec_url = apr_array_make(a, 40, sizeof(ap_conf_vector_t *));
+
+ /* recursion stopper */
+ conf->redirect_limit = 0; /* 0 == unset */
+ conf->subreq_limit = 0;
+
+ conf->protocol = NULL;
+ conf->accf_map = apr_table_make(a, 5);
+
+#ifdef APR_TCP_DEFER_ACCEPT
+ apr_table_set(conf->accf_map, "http", "data");
+ apr_table_set(conf->accf_map, "https", "data");
+#endif
+
+#if APR_HAS_SO_ACCEPTFILTER
+#ifndef ACCEPT_FILTER_NAME
+#define ACCEPT_FILTER_NAME "httpready"
+#ifdef __FreeBSD_version
+#if __FreeBSD_version < 411000 /* httpready broken before 4.1.1 */
+#undef ACCEPT_FILTER_NAME
+#define ACCEPT_FILTER_NAME "dataready"
+#endif
+#endif
+#endif
+ apr_table_set(conf->accf_map, "http", ACCEPT_FILTER_NAME);
+ apr_table_set(conf->accf_map, "https", "dataready");
+#endif
+
+ conf->trace_enable = AP_TRACE_UNSET;
+
+ return (void *)conf;
+}
+
+static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
+{
+ core_server_config *base = (core_server_config *)basev;
+ core_server_config *virt = (core_server_config *)virtv;
+ core_server_config *conf;
+
+ conf = (core_server_config *)apr_palloc(p, sizeof(core_server_config));
+ memcpy(conf, virt, sizeof(core_server_config));
+
+ if (!conf->access_name) {
+ conf->access_name = base->access_name;
+ }
+
+ if (!conf->ap_document_root) {
+ conf->ap_document_root = base->ap_document_root;
+ }
+
+ if (!conf->protocol) {
+ conf->protocol = base->protocol;
+ }
+
+ conf->sec_dir = apr_array_append(p, base->sec_dir, virt->sec_dir);
+ conf->sec_url = apr_array_append(p, base->sec_url, virt->sec_url);
+
+ conf->redirect_limit = virt->redirect_limit
+ ? virt->redirect_limit
+ : base->redirect_limit;
+
+ conf->subreq_limit = virt->subreq_limit
+ ? virt->subreq_limit
+ : base->subreq_limit;
+
+ conf->trace_enable = (virt->trace_enable != AP_TRACE_UNSET)
+ ? virt->trace_enable
+ : base->trace_enable;
+
+ return conf;
+}
+
+/* Add per-directory configuration entry (for <directory> section);
+ * these are part of the core server config.
+ */
+
+AP_CORE_DECLARE(void) ap_add_per_dir_conf(server_rec *s, void *dir_config)
+{
+ core_server_config *sconf = ap_get_module_config(s->module_config,
+ &core_module);
+ void **new_space = (void **)apr_array_push(sconf->sec_dir);
+
+ *new_space = dir_config;
+}
+
+AP_CORE_DECLARE(void) ap_add_per_url_conf(server_rec *s, void *url_config)
+{
+ core_server_config *sconf = ap_get_module_config(s->module_config,
+ &core_module);
+ void **new_space = (void **)apr_array_push(sconf->sec_url);
+
+ *new_space = url_config;
+}
+
+AP_CORE_DECLARE(void) ap_add_file_conf(core_dir_config *conf, void *url_config)
+{
+ void **new_space = (void **)apr_array_push(conf->sec_file);
+
+ *new_space = url_config;
+}
+
+/* We need to do a stable sort, qsort isn't stable. So to make it stable
+ * we'll be maintaining the original index into the list, and using it
+ * as the minor key during sorting. The major key is the number of
+ * components (where the root component is zero).
+ */
+struct reorder_sort_rec {
+ ap_conf_vector_t *elt;
+ int orig_index;
+};
+
+static int reorder_sorter(const void *va, const void *vb)
+{
+ const struct reorder_sort_rec *a = va;
+ const struct reorder_sort_rec *b = vb;
+ core_dir_config *core_a;
+ core_dir_config *core_b;
+
+ core_a = ap_get_module_config(a->elt, &core_module);
+ core_b = ap_get_module_config(b->elt, &core_module);
+
+ /* a regex always sorts after a non-regex
+ */
+ if (!core_a->r && core_b->r) {
+ return -1;
+ }
+ else if (core_a->r && !core_b->r) {
+ return 1;
+ }
+
+ /* we always sort next by the number of components
+ */
+ if (core_a->d_components < core_b->d_components) {
+ return -1;
+ }
+ else if (core_a->d_components > core_b->d_components) {
+ return 1;
+ }
+
+ /* They have the same number of components, we now have to compare
+ * the minor key to maintain the original order (from the config.)
+ */
+ return a->orig_index - b->orig_index;
+}
+
+void ap_core_reorder_directories(apr_pool_t *p, server_rec *s)
+{
+ core_server_config *sconf;
+ apr_array_header_t *sec_dir;
+ struct reorder_sort_rec *sortbin;
+ int nelts;
+ ap_conf_vector_t **elts;
+ int i;
+ apr_pool_t *tmp;
+
+ sconf = ap_get_module_config(s->module_config, &core_module);
+ sec_dir = sconf->sec_dir;
+ nelts = sec_dir->nelts;
+ elts = (ap_conf_vector_t **)sec_dir->elts;
+
+ if (!nelts) {
+ /* simple case of already being sorted... */
+ /* We're not checking this condition to be fast... we're checking
+ * it to avoid trying to palloc zero bytes, which can trigger some
+ * memory debuggers to barf
+ */
+ return;
+ }
+
+ /* we have to allocate tmp space to do a stable sort */
+ apr_pool_create(&tmp, p);
+ sortbin = apr_palloc(tmp, sec_dir->nelts * sizeof(*sortbin));
+ for (i = 0; i < nelts; ++i) {
+ sortbin[i].orig_index = i;
+ sortbin[i].elt = elts[i];
+ }
+
+ qsort(sortbin, nelts, sizeof(*sortbin), reorder_sorter);
+
+ /* and now copy back to the original array */
+ for (i = 0; i < nelts; ++i) {
+ elts[i] = sortbin[i].elt;
+ }
+
+ apr_pool_destroy(tmp);
+}
+
+/*****************************************************************
+ *
+ * There are some elements of the core config structures in which
+ * other modules have a legitimate interest (this is ugly, but necessary
+ * to preserve NCSA back-compatibility). So, we have a bunch of accessors
+ * here...
+ */
+
+AP_DECLARE(int) ap_allow_options(request_rec *r)
+{
+ core_dir_config *conf =
+ (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
+
+ return conf->opts;
+}
+
+AP_DECLARE(int) ap_allow_overrides(request_rec *r)
+{
+ core_dir_config *conf;
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ return conf->override;
+}
+
+AP_DECLARE(const char *) ap_auth_type(request_rec *r)
+{
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ return conf->ap_auth_type;
+}
+
+AP_DECLARE(const char *) ap_auth_name(request_rec *r)
+{
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ return conf->ap_auth_name;
+}
+
+AP_DECLARE(const char *) ap_default_type(request_rec *r)
+{
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ return conf->ap_default_type
+ ? conf->ap_default_type
+ : DEFAULT_CONTENT_TYPE;
+}
+
+AP_DECLARE(const char *) ap_document_root(request_rec *r) /* Don't use this! */
+{
+ core_server_config *conf;
+
+ conf = (core_server_config *)ap_get_module_config(r->server->module_config,
+ &core_module);
+
+ return conf->ap_document_root;
+}
+
+AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r)
+{
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ return conf->ap_requires;
+}
+
+AP_DECLARE(int) ap_satisfies(request_rec *r)
+{
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ return conf->satisfy[r->method_number];
+}
+
+/* Should probably just get rid of this... the only code that cares is
+ * part of the core anyway (and in fact, it isn't publicised to other
+ * modules).
+ */
+
+char *ap_response_code_string(request_rec *r, int error_index)
+{
+ core_dir_config *dirconf;
+ core_request_config *reqconf;
+
+ /* check for string registered via ap_custom_response() first */
+ reqconf = (core_request_config *)ap_get_module_config(r->request_config,
+ &core_module);
+ if (reqconf->response_code_strings != NULL &&
+ reqconf->response_code_strings[error_index] != NULL) {
+ return reqconf->response_code_strings[error_index];
+ }
+
+ /* check for string specified via ErrorDocument */
+ dirconf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ if (dirconf->response_code_strings == NULL) {
+ return NULL;
+ }
+
+ if (dirconf->response_code_strings[error_index] == &errordocument_default) {
+ return NULL;
+ }
+
+ return dirconf->response_code_strings[error_index];
+}
+
+
+/* Code from Harald Hanche-Olsen <hanche@imf.unit.no> */
+static APR_INLINE void do_double_reverse (conn_rec *conn)
+{
+ apr_sockaddr_t *sa;
+ apr_status_t rv;
+
+ if (conn->double_reverse) {
+ /* already done */
+ return;
+ }
+
+ if (conn->remote_host == NULL || conn->remote_host[0] == '\0') {
+ /* single reverse failed, so don't bother */
+ conn->double_reverse = -1;
+ return;
+ }
+
+ rv = apr_sockaddr_info_get(&sa, conn->remote_host, APR_UNSPEC, 0, 0, conn->pool);
+ if (rv == APR_SUCCESS) {
+ while (sa) {
+ if (apr_sockaddr_equal(sa, conn->remote_addr)) {
+ conn->double_reverse = 1;
+ return;
+ }
+
+ sa = sa->next;
+ }
+ }
+
+ conn->double_reverse = -1;
+}
+
+AP_DECLARE(const char *) ap_get_remote_host(conn_rec *conn, void *dir_config,
+ int type, int *str_is_ip)
+{
+ int hostname_lookups;
+ int ignored_str_is_ip;
+
+ if (!str_is_ip) { /* caller doesn't want to know */
+ str_is_ip = &ignored_str_is_ip;
+ }
+ *str_is_ip = 0;
+
+ /* If we haven't checked the host name, and we want to */
+ if (dir_config) {
+ hostname_lookups =
+ ((core_dir_config *)ap_get_module_config(dir_config, &core_module))
+ ->hostname_lookups;
+
+ if (hostname_lookups == HOSTNAME_LOOKUP_UNSET) {
+ hostname_lookups = HOSTNAME_LOOKUP_OFF;
+ }
+ }
+ else {
+ /* the default */
+ hostname_lookups = HOSTNAME_LOOKUP_OFF;
+ }
+
+ if (type != REMOTE_NOLOOKUP
+ && conn->remote_host == NULL
+ && (type == REMOTE_DOUBLE_REV
+ || hostname_lookups != HOSTNAME_LOOKUP_OFF)) {
+
+ if (apr_getnameinfo(&conn->remote_host, conn->remote_addr, 0)
+ == APR_SUCCESS) {
+ ap_str_tolower(conn->remote_host);
+
+ if (hostname_lookups == HOSTNAME_LOOKUP_DOUBLE) {
+ do_double_reverse(conn);
+ if (conn->double_reverse != 1) {
+ conn->remote_host = NULL;
+ }
+ }
+ }
+
+ /* if failed, set it to the NULL string to indicate error */
+ if (conn->remote_host == NULL) {
+ conn->remote_host = "";
+ }
+ }
+
+ if (type == REMOTE_DOUBLE_REV) {
+ do_double_reverse(conn);
+ if (conn->double_reverse == -1) {
+ return NULL;
+ }
+ }
+
+ /*
+ * Return the desired information; either the remote DNS name, if found,
+ * or either NULL (if the hostname was requested) or the IP address
+ * (if any identifier was requested).
+ */
+ if (conn->remote_host != NULL && conn->remote_host[0] != '\0') {
+ return conn->remote_host;
+ }
+ else {
+ if (type == REMOTE_HOST || type == REMOTE_DOUBLE_REV) {
+ return NULL;
+ }
+ else {
+ *str_is_ip = 1;
+ return conn->remote_ip;
+ }
+ }
+}
+
+/*
+ * Optional function coming from mod_ident, used for looking up ident user
+ */
+static APR_OPTIONAL_FN_TYPE(ap_ident_lookup) *ident_lookup;
+
+AP_DECLARE(const char *) ap_get_remote_logname(request_rec *r)
+{
+ if (r->connection->remote_logname != NULL) {
+ return r->connection->remote_logname;
+ }
+
+ if (ident_lookup) {
+ return ident_lookup(r);
+ }
+
+ return NULL;
+}
+
+/* There are two options regarding what the "name" of a server is. The
+ * "canonical" name as defined by ServerName and Port, or the "client's
+ * name" as supplied by a possible Host: header or full URI.
+ *
+ * The DNS option to UseCanonicalName causes this routine to do a
+ * reverse lookup on the local IP address of the connection and use
+ * that for the ServerName. This makes its value more reliable while
+ * at the same time allowing Demon's magic virtual hosting to work.
+ * The assumption is that DNS lookups are sufficiently quick...
+ * -- fanf 1998-10-03
+ */
+AP_DECLARE(const char *) ap_get_server_name(request_rec *r)
+{
+ conn_rec *conn = r->connection;
+ core_dir_config *d;
+ const char *retval;
+
+ d = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ switch (d->use_canonical_name) {
+ case USE_CANONICAL_NAME_ON:
+ retval = r->server->server_hostname;
+ break;
+ case USE_CANONICAL_NAME_DNS:
+ if (conn->local_host == NULL) {
+ if (apr_getnameinfo(&conn->local_host,
+ conn->local_addr, 0) != APR_SUCCESS)
+ conn->local_host = apr_pstrdup(conn->pool,
+ r->server->server_hostname);
+ else {
+ ap_str_tolower(conn->local_host);
+ }
+ }
+ retval = conn->local_host;
+ break;
+ case USE_CANONICAL_NAME_OFF:
+ case USE_CANONICAL_NAME_UNSET:
+ retval = r->hostname ? r->hostname : r->server->server_hostname;
+ break;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "ap_get_server_name: Invalid UCN Option somehow");
+ retval = "localhost";
+ break;
+ }
+ return retval;
+}
+
+/*
+ * Get the current server name from the request for the purposes
+ * of using in a URL. If the server name is an IPv6 literal
+ * address, it will be returned in URL format (e.g., "[fe80::1]").
+ */
+static const char *get_server_name_for_url(request_rec *r)
+{
+ const char *plain_server_name = ap_get_server_name(r);
+
+#if APR_HAVE_IPV6
+ if (ap_strchr_c(plain_server_name, ':')) { /* IPv6 literal? */
+ return apr_psprintf(r->pool, "[%s]", plain_server_name);
+ }
+#endif
+ return plain_server_name;
+}
+
+AP_DECLARE(apr_port_t) ap_get_server_port(const request_rec *r)
+{
+ apr_port_t port;
+ core_dir_config *d =
+ (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
+
+ switch (d->use_canonical_name) {
+ case USE_CANONICAL_NAME_OFF:
+ case USE_CANONICAL_NAME_DNS:
+ case USE_CANONICAL_NAME_UNSET:
+ if (d->use_canonical_phys_port == USE_CANONICAL_PHYS_PORT_ON)
+ port = r->parsed_uri.port_str ? r->parsed_uri.port :
+ r->connection->local_addr->port ? r->connection->local_addr->port :
+ r->server->port ? r->server->port :
+ ap_default_port(r);
+ else /* USE_CANONICAL_PHYS_PORT_OFF or USE_CANONICAL_PHYS_PORT_UNSET */
+ port = r->parsed_uri.port_str ? r->parsed_uri.port :
+ r->server->port ? r->server->port :
+ ap_default_port(r);
+ break;
+ case USE_CANONICAL_NAME_ON:
+ /* With UseCanonicalName on (and in all versions prior to 1.3)
+ * Apache will use the hostname and port specified in the
+ * ServerName directive to construct a canonical name for the
+ * server. (If no port was specified in the ServerName
+ * directive, Apache uses the port supplied by the client if
+ * any is supplied, and finally the default port for the protocol
+ * used.
+ */
+ if (d->use_canonical_phys_port == USE_CANONICAL_PHYS_PORT_ON)
+ port = r->server->port ? r->server->port :
+ r->connection->local_addr->port ? r->connection->local_addr->port :
+ ap_default_port(r);
+ else /* USE_CANONICAL_PHYS_PORT_OFF or USE_CANONICAL_PHYS_PORT_UNSET */
+ port = r->server->port ? r->server->port :
+ ap_default_port(r);
+ break;
+ default:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "ap_get_server_port: Invalid UCN Option somehow");
+ port = ap_default_port(r);
+ break;
+ }
+
+ return port;
+}
+
+AP_DECLARE(char *) ap_construct_url(apr_pool_t *p, const char *uri,
+ request_rec *r)
+{
+ unsigned port = ap_get_server_port(r);
+ const char *host = get_server_name_for_url(r);
+
+ if (ap_is_default_port(port, r)) {
+ return apr_pstrcat(p, ap_http_scheme(r), "://", host, uri, NULL);
+ }
+
+ return apr_psprintf(p, "%s://%s:%u%s", ap_http_scheme(r), host, port, uri);
+}
+
+AP_DECLARE(apr_off_t) ap_get_limit_req_body(const request_rec *r)
+{
+ core_dir_config *d =
+ (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
+
+ if (d->limit_req_body == AP_LIMIT_REQ_BODY_UNSET) {
+ return AP_DEFAULT_LIMIT_REQ_BODY;
+ }
+
+ return d->limit_req_body;
+}
+
+
+/*****************************************************************
+ *
+ * Commands... this module handles almost all of the NCSA httpd.conf
+ * commands, but most of the old srm.conf is in the the modules.
+ */
+
+
+/* returns a parent if it matches the given directive */
+static const ap_directive_t * find_parent(const ap_directive_t *dirp,
+ const char *what)
+{
+ while (dirp->parent != NULL) {
+ dirp = dirp->parent;
+
+ /* ### it would be nice to have atom-ized directives */
+ if (strcasecmp(dirp->directive, what) == 0)
+ return dirp;
+ }
+
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_check_cmd_context(cmd_parms *cmd,
+ unsigned forbidden)
+{
+ const char *gt = (cmd->cmd->name[0] == '<'
+ && cmd->cmd->name[strlen(cmd->cmd->name)-1] != '>')
+ ? ">" : "";
+ const ap_directive_t *found;
+
+ if ((forbidden & NOT_IN_VIRTUALHOST) && cmd->server->is_virtual) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, gt,
+ " cannot occur within <VirtualHost> section", NULL);
+ }
+
+ if ((forbidden & NOT_IN_LIMIT) && cmd->limited != -1) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, gt,
+ " cannot occur within <Limit> section", NULL);
+ }
+
+ if ((forbidden & NOT_IN_DIR_LOC_FILE) == NOT_IN_DIR_LOC_FILE) {
+ if (cmd->path != NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, gt,
+ " cannot occur within <Directory/Location/Files> "
+ "section", NULL);
+ }
+ if (cmd->cmd->req_override & EXEC_ON_READ) {
+ /* EXEC_ON_READ must be NOT_IN_DIR_LOC_FILE, if not, it will
+ * (deliberately) segfault below in the individual tests...
+ */
+ return NULL;
+ }
+ }
+
+ if (((forbidden & NOT_IN_DIRECTORY)
+ && ((found = find_parent(cmd->directive, "<Directory"))
+ || (found = find_parent(cmd->directive, "<DirectoryMatch"))))
+ || ((forbidden & NOT_IN_LOCATION)
+ && ((found = find_parent(cmd->directive, "<Location"))
+ || (found = find_parent(cmd->directive, "<LocationMatch"))))
+ || ((forbidden & NOT_IN_FILES)
+ && ((found = find_parent(cmd->directive, "<Files"))
+ || (found = find_parent(cmd->directive, "<FilesMatch"))))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, gt,
+ " cannot occur within ", found->directive,
+ "> section", NULL);
+ }
+
+ return NULL;
+}
+
+static const char *set_access_name(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ void *sconf = cmd->server->module_config;
+ core_server_config *conf = ap_get_module_config(sconf, &core_module);
+
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ conf->access_name = apr_pstrdup(cmd->pool, arg);
+ return NULL;
+}
+
+#ifdef GPROF
+static const char *set_gprof_dir(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ void *sconf = cmd->server->module_config;
+ core_server_config *conf = ap_get_module_config(sconf, &core_module);
+
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ conf->gprof_dir = apr_pstrdup(cmd->pool, arg);
+ return NULL;
+}
+#endif /*GPROF*/
+
+static const char *set_add_default_charset(cmd_parms *cmd,
+ void *d_, const char *arg)
+{
+ core_dir_config *d = d_;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!strcasecmp(arg, "Off")) {
+ d->add_default_charset = ADD_DEFAULT_CHARSET_OFF;
+ }
+ else if (!strcasecmp(arg, "On")) {
+ d->add_default_charset = ADD_DEFAULT_CHARSET_ON;
+ d->add_default_charset_name = DEFAULT_ADD_DEFAULT_CHARSET_NAME;
+ }
+ else {
+ d->add_default_charset = ADD_DEFAULT_CHARSET_ON;
+ d->add_default_charset_name = arg;
+ }
+
+ return NULL;
+}
+
+static const char *set_document_root(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ void *sconf = cmd->server->module_config;
+ core_server_config *conf = ap_get_module_config(sconf, &core_module);
+
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* Make it absolute, relative to ServerRoot */
+ arg = ap_server_root_relative(cmd->pool, arg);
+
+ /* TODO: ap_configtestonly && ap_docrootcheck && */
+ if (apr_filepath_merge((char**)&conf->ap_document_root, NULL, arg,
+ APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS
+ || !ap_is_directory(cmd->pool, arg)) {
+ if (cmd->server->is_virtual) {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0,
+ cmd->pool,
+ "Warning: DocumentRoot [%s] does not exist",
+ arg);
+ conf->ap_document_root = arg;
+ }
+ else {
+ return "DocumentRoot must be a directory";
+ }
+ }
+ return NULL;
+}
+
+AP_DECLARE(void) ap_custom_response(request_rec *r, int status,
+ const char *string)
+{
+ core_request_config *conf =
+ ap_get_module_config(r->request_config, &core_module);
+ int idx;
+
+ if (conf->response_code_strings == NULL) {
+ conf->response_code_strings =
+ apr_pcalloc(r->pool,
+ sizeof(*conf->response_code_strings) * RESPONSE_CODES);
+ }
+
+ idx = ap_index_of_response(status);
+
+ conf->response_code_strings[idx] =
+ ((ap_is_url(string) || (*string == '/')) && (*string != '"')) ?
+ apr_pstrdup(r->pool, string) : apr_pstrcat(r->pool, "\"", string, NULL);
+}
+
+static const char *set_error_document(cmd_parms *cmd, void *conf_,
+ const char *errno_str, const char *msg)
+{
+ core_dir_config *conf = conf_;
+ int error_number, index_number, idx500;
+ enum { MSG, LOCAL_PATH, REMOTE_PATH } what = MSG;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* 1st parameter should be a 3 digit number, which we recognize;
+ * convert it into an array index
+ */
+ error_number = atoi(errno_str);
+ idx500 = ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);
+
+ if (error_number == HTTP_INTERNAL_SERVER_ERROR) {
+ index_number = idx500;
+ }
+ else if ((index_number = ap_index_of_response(error_number)) == idx500) {
+ return apr_pstrcat(cmd->pool, "Unsupported HTTP response code ",
+ errno_str, NULL);
+ }
+
+ /* Heuristic to determine second argument. */
+ if (ap_strchr_c(msg,' '))
+ what = MSG;
+ else if (msg[0] == '/')
+ what = LOCAL_PATH;
+ else if (ap_is_url(msg))
+ what = REMOTE_PATH;
+ else
+ what = MSG;
+
+ /* The entry should be ignored if it is a full URL for a 401 error */
+
+ if (error_number == 401 && what == REMOTE_PATH) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server,
+ "cannot use a full URL in a 401 ErrorDocument "
+ "directive --- ignoring!");
+ }
+ else { /* Store it... */
+ if (conf->response_code_strings == NULL) {
+ conf->response_code_strings =
+ apr_pcalloc(cmd->pool,
+ sizeof(*conf->response_code_strings) *
+ RESPONSE_CODES);
+ }
+
+ if (strcmp(msg, "default") == 0) {
+ /* special case: ErrorDocument 404 default restores the
+ * canned server error response
+ */
+ conf->response_code_strings[index_number] = &errordocument_default;
+ }
+ else {
+ /* hack. Prefix a " if it is a msg; as that is what
+ * http_protocol.c relies on to distinguish between
+ * a msg and a (local) path.
+ */
+ conf->response_code_strings[index_number] = (what == MSG) ?
+ apr_pstrcat(cmd->pool, "\"",msg,NULL) :
+ apr_pstrdup(cmd->pool, msg);
+ }
+ }
+
+ return NULL;
+}
+
+static const char *set_allow_opts(cmd_parms *cmd, allow_options_t *opts,
+ const char *l)
+{
+ allow_options_t opt;
+ int first = 1;
+
+ char *w, *p = (char *) l;
+ char *tok_state;
+
+ while ((w = apr_strtok(p, ",", &tok_state)) != NULL) {
+
+ if (first) {
+ p = NULL;
+ *opts = OPT_NONE;
+ first = 0;
+ }
+
+ if (!strcasecmp(w, "Indexes")) {
+ opt = OPT_INDEXES;
+ }
+ else if (!strcasecmp(w, "Includes")) {
+ opt = OPT_INCLUDES;
+ }
+ else if (!strcasecmp(w, "IncludesNOEXEC")) {
+ opt = (OPT_INCLUDES | OPT_INCNOEXEC);
+ }
+ else if (!strcasecmp(w, "FollowSymLinks")) {
+ opt = OPT_SYM_LINKS;
+ }
+ else if (!strcasecmp(w, "SymLinksIfOwnerMatch")) {
+ opt = OPT_SYM_OWNER;
+ }
+ else if (!strcasecmp(w, "ExecCGI")) {
+ opt = OPT_EXECCGI;
+ }
+ else if (!strcasecmp(w, "MultiViews")) {
+ opt = OPT_MULTI;
+ }
+ else if (!strcasecmp(w, "RunScripts")) { /* AI backcompat. Yuck */
+ opt = OPT_MULTI|OPT_EXECCGI;
+ }
+ else if (!strcasecmp(w, "None")) {
+ opt = OPT_NONE;
+ }
+ else if (!strcasecmp(w, "All")) {
+ opt = OPT_ALL;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Illegal option ", w, NULL);
+ }
+
+ *opts |= opt;
+ }
+
+ (*opts) &= (~OPT_UNSET);
+
+ return NULL;
+}
+
+static const char *set_override(cmd_parms *cmd, void *d_, const char *l)
+{
+ core_dir_config *d = d_;
+ char *w;
+ char *k, *v;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* Throw a warning if we're in <Location> or <Files> */
+ if (ap_check_cmd_context(cmd, NOT_IN_LOCATION | NOT_IN_FILES)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "Useless use of AllowOverride in line %d.",
+ cmd->directive->line_num);
+ }
+
+ d->override = OR_NONE;
+ while (l[0]) {
+ w = ap_getword_conf(cmd->pool, &l);
+
+ k = w;
+ v = strchr(k, '=');
+ if (v) {
+ *v++ = '\0';
+ }
+
+ if (!strcasecmp(w, "Limit")) {
+ d->override |= OR_LIMIT;
+ }
+ else if (!strcasecmp(k, "Options")) {
+ d->override |= OR_OPTIONS;
+ if (v)
+ set_allow_opts(cmd, &(d->override_opts), v);
+ else
+ d->override_opts = OPT_ALL;
+ }
+ else if (!strcasecmp(w, "FileInfo")) {
+ d->override |= OR_FILEINFO;
+ }
+ else if (!strcasecmp(w, "AuthConfig")) {
+ d->override |= OR_AUTHCFG;
+ }
+ else if (!strcasecmp(w, "Indexes")) {
+ d->override |= OR_INDEXES;
+ }
+ else if (!strcasecmp(w, "None")) {
+ d->override = OR_NONE;
+ }
+ else if (!strcasecmp(w, "All")) {
+ d->override = OR_ALL;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Illegal override option ", w, NULL);
+ }
+
+ d->override &= ~OR_UNSET;
+ }
+
+ return NULL;
+}
+
+static const char *set_options(cmd_parms *cmd, void *d_, const char *l)
+{
+ core_dir_config *d = d_;
+ allow_options_t opt;
+ int first = 1;
+ char action;
+
+ while (l[0]) {
+ char *w = ap_getword_conf(cmd->pool, &l);
+ action = '\0';
+
+ if (*w == '+' || *w == '-') {
+ action = *(w++);
+ }
+ else if (first) {
+ d->opts = OPT_NONE;
+ first = 0;
+ }
+
+ if (!strcasecmp(w, "Indexes")) {
+ opt = OPT_INDEXES;
+ }
+ else if (!strcasecmp(w, "Includes")) {
+ opt = OPT_INCLUDES;
+ }
+ else if (!strcasecmp(w, "IncludesNOEXEC")) {
+ opt = (OPT_INCLUDES | OPT_INCNOEXEC);
+ }
+ else if (!strcasecmp(w, "FollowSymLinks")) {
+ opt = OPT_SYM_LINKS;
+ }
+ else if (!strcasecmp(w, "SymLinksIfOwnerMatch")) {
+ opt = OPT_SYM_OWNER;
+ }
+ else if (!strcasecmp(w, "ExecCGI")) {
+ opt = OPT_EXECCGI;
+ }
+ else if (!strcasecmp(w, "MultiViews")) {
+ opt = OPT_MULTI;
+ }
+ else if (!strcasecmp(w, "RunScripts")) { /* AI backcompat. Yuck */
+ opt = OPT_MULTI|OPT_EXECCGI;
+ }
+ else if (!strcasecmp(w, "None")) {
+ opt = OPT_NONE;
+ }
+ else if (!strcasecmp(w, "All")) {
+ opt = OPT_ALL;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Illegal option ", w, NULL);
+ }
+
+ if (!(cmd->override_opts & opt) && opt != OPT_NONE) {
+ return apr_pstrcat(cmd->pool, "Option ", w, " not allowed here", NULL);
+ }
+ else if (action == '-') {
+ /* we ensure the invariant (d->opts_add & d->opts_remove) == 0 */
+ d->opts_remove |= opt;
+ d->opts_add &= ~opt;
+ d->opts &= ~opt;
+ }
+ else if (action == '+') {
+ d->opts_add |= opt;
+ d->opts_remove &= ~opt;
+ d->opts |= opt;
+ }
+ else {
+ d->opts |= opt;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Note what data should be used when forming file ETag values.
+ * It would be nicer to do this as an ITERATE, but then we couldn't
+ * remember the +/- state properly.
+ */
+static const char *set_etag_bits(cmd_parms *cmd, void *mconfig,
+ const char *args_p)
+{
+ core_dir_config *cfg;
+ etag_components_t bit;
+ char action;
+ char *token;
+ const char *args;
+ int valid;
+ int first;
+ int explicit;
+
+ cfg = (core_dir_config *)mconfig;
+
+ args = args_p;
+ first = 1;
+ explicit = 0;
+ while (args[0] != '\0') {
+ action = '*';
+ bit = ETAG_UNSET;
+ valid = 1;
+ token = ap_getword_conf(cmd->pool, &args);
+ if ((*token == '+') || (*token == '-')) {
+ action = *token;
+ token++;
+ }
+ else {
+ /*
+ * The occurrence of an absolute setting wipes
+ * out any previous relative ones. The first such
+ * occurrence forgets any inherited ones, too.
+ */
+ if (first) {
+ cfg->etag_bits = ETAG_UNSET;
+ cfg->etag_add = ETAG_UNSET;
+ cfg->etag_remove = ETAG_UNSET;
+ first = 0;
+ }
+ }
+
+ if (strcasecmp(token, "None") == 0) {
+ if (action != '*') {
+ valid = 0;
+ }
+ else {
+ cfg->etag_bits = bit = ETAG_NONE;
+ explicit = 1;
+ }
+ }
+ else if (strcasecmp(token, "All") == 0) {
+ if (action != '*') {
+ valid = 0;
+ }
+ else {
+ explicit = 1;
+ cfg->etag_bits = bit = ETAG_ALL;
+ }
+ }
+ else if (strcasecmp(token, "Size") == 0) {
+ bit = ETAG_SIZE;
+ }
+ else if ((strcasecmp(token, "LMTime") == 0)
+ || (strcasecmp(token, "MTime") == 0)
+ || (strcasecmp(token, "LastModified") == 0)) {
+ bit = ETAG_MTIME;
+ }
+ else if (strcasecmp(token, "INode") == 0) {
+ bit = ETAG_INODE;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "Unknown keyword '",
+ token, "' for ", cmd->cmd->name,
+ " directive", NULL);
+ }
+
+ if (! valid) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name, " keyword '",
+ token, "' cannot be used with '+' or '-'",
+ NULL);
+ }
+
+ if (action == '+') {
+ /*
+ * Make sure it's in the 'add' list and absent from the
+ * 'subtract' list.
+ */
+ cfg->etag_add |= bit;
+ cfg->etag_remove &= (~ bit);
+ }
+ else if (action == '-') {
+ cfg->etag_remove |= bit;
+ cfg->etag_add &= (~ bit);
+ }
+ else {
+ /*
+ * Non-relative values wipe out any + or - values
+ * accumulated so far.
+ */
+ cfg->etag_bits |= bit;
+ cfg->etag_add = ETAG_UNSET;
+ cfg->etag_remove = ETAG_UNSET;
+ explicit = 1;
+ }
+ }
+
+ /*
+ * Any setting at all will clear the 'None' and 'Unset' bits.
+ */
+
+ if (cfg->etag_add != ETAG_UNSET) {
+ cfg->etag_add &= (~ ETAG_UNSET);
+ }
+
+ if (cfg->etag_remove != ETAG_UNSET) {
+ cfg->etag_remove &= (~ ETAG_UNSET);
+ }
+
+ if (explicit) {
+ cfg->etag_bits &= (~ ETAG_UNSET);
+
+ if ((cfg->etag_bits & ETAG_NONE) != ETAG_NONE) {
+ cfg->etag_bits &= (~ ETAG_NONE);
+ }
+ }
+
+ return NULL;
+}
+
+static const char *set_enable_mmap(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ d->enable_mmap = ENABLE_MMAP_ON;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ d->enable_mmap = ENABLE_MMAP_OFF;
+ }
+ else {
+ return "parameter must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
+static const char *set_enable_sendfile(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ d->enable_sendfile = ENABLE_SENDFILE_ON;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ d->enable_sendfile = ENABLE_SENDFILE_OFF;
+ }
+ else {
+ return "parameter must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
+static const char *satisfy(cmd_parms *cmd, void *c_, const char *arg)
+{
+ core_dir_config *c = c_;
+ int satisfy = SATISFY_NOSPEC;
+ int i;
+
+ if (!strcasecmp(arg, "all")) {
+ satisfy = SATISFY_ALL;
+ }
+ else if (!strcasecmp(arg, "any")) {
+ satisfy = SATISFY_ANY;
+ }
+ else {
+ return "Satisfy either 'any' or 'all'.";
+ }
+
+ for (i = 0; i < METHODS; ++i) {
+ if (cmd->limited & (AP_METHOD_BIT << i)) {
+ c->satisfy[i] = satisfy;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *require(cmd_parms *cmd, void *c_, const char *arg)
+{
+ require_line *r;
+ core_dir_config *c = c_;
+
+ if (!c->ap_requires) {
+ c->ap_requires = apr_array_make(cmd->pool, 2, sizeof(require_line));
+ }
+
+ r = (require_line *)apr_array_push(c->ap_requires);
+ r->requirement = apr_pstrdup(cmd->pool, arg);
+ r->method_mask = cmd->limited;
+
+ return NULL;
+}
+
+/*
+ * Report a missing-'>' syntax error.
+ */
+static char *unclosed_directive(cmd_parms *cmd)
+{
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+}
+
+/*
+ * Report a missing args in '<Foo >' syntax error.
+ */
+static char *missing_container_arg(cmd_parms *cmd)
+{
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+}
+
+AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd,
+ void *dummy,
+ const char *arg)
+{
+ const char *endp = ap_strrchr_c(arg, '>');
+ const char *limited_methods;
+ void *tog = cmd->cmd->cmd_data;
+ apr_int64_t limited = 0;
+ const char *errmsg;
+
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ limited_methods = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (!limited_methods[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ while (limited_methods[0]) {
+ char *method = ap_getword_conf(cmd->pool, &limited_methods);
+ int methnum;
+
+ /* check for builtin or module registered method number */
+ methnum = ap_method_number_of(method);
+
+ if (methnum == M_TRACE && !tog) {
+ return "TRACE cannot be controlled by <Limit>, see TraceEnable";
+ }
+ else if (methnum == M_INVALID) {
+ /* method has not been registered yet, but resorce restriction
+ * is always checked before method handling, so register it.
+ */
+ methnum = ap_method_register(cmd->pool, method);
+ }
+
+ limited |= (AP_METHOD_BIT << methnum);
+ }
+
+ /* Killing two features with one function,
+ * if (tog == NULL) <Limit>, else <LimitExcept>
+ */
+ cmd->limited = tog ? ~limited : limited;
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, cmd->context);
+
+ cmd->limited = -1;
+
+ return errmsg;
+}
+
+/* XXX: Bogus - need to do this differently (at least OS2/Netware suffer
+ * the same problem!!!
+ * We use this in <DirectoryMatch> and <FilesMatch>, to ensure that
+ * people don't get bitten by wrong-cased regex matches
+ */
+
+#ifdef WIN32
+#define USE_ICASE AP_REG_ICASE
+#else
+#define USE_ICASE 0
+#endif
+
+static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *errmsg;
+ const char *endp = ap_strrchr_c(arg, '>');
+ int old_overrides = cmd->override;
+ char *old_path = cmd->path;
+ core_dir_config *conf;
+ ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool);
+ ap_regex_t *r = NULL;
+ const command_rec *thiscmd = cmd->cmd;
+
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (!arg[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ if (!arg) {
+ if (thiscmd->cmd_data)
+ return "<DirectoryMatch > block must specify a path";
+ else
+ return "<Directory > block must specify a path";
+ }
+
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ cmd->override = OR_ALL|ACCESS_CONF;
+
+ if (!strcmp(cmd->path, "~")) {
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ if (!cmd->path)
+ return "<Directory ~ > block must specify a path";
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ }
+ else if (thiscmd->cmd_data) { /* <DirectoryMatch> */
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ }
+ else if (!strcmp(cmd->path, "/") == 0)
+ {
+ char *newpath;
+
+ /*
+ * Ensure that the pathname is canonical, and append the trailing /
+ */
+ apr_status_t rv = apr_filepath_merge(&newpath, NULL, cmd->path,
+ APR_FILEPATH_TRUENAME, cmd->pool);
+ if (rv != APR_SUCCESS && rv != APR_EPATHWILD) {
+ return apr_pstrcat(cmd->pool, "<Directory \"", cmd->path,
+ "\"> path is invalid.", NULL);
+ }
+
+ cmd->path = newpath;
+ if (cmd->path[strlen(cmd->path) - 1] != '/')
+ cmd->path = apr_pstrcat(cmd->pool, cmd->path, "/", NULL);
+ }
+
+ /* initialize our config and fetch it */
+ conf = ap_set_config_vectors(cmd->server, new_dir_conf, cmd->path,
+ &core_module, cmd->pool);
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf);
+ if (errmsg != NULL)
+ return errmsg;
+
+ conf->r = r;
+ conf->d = cmd->path;
+ conf->d_is_fnmatch = (apr_fnmatch_test(conf->d) != 0);
+
+ /* Make this explicit - the "/" root has 0 elements, that is, we
+ * will always merge it, and it will always sort and merge first.
+ * All others are sorted and tested by the number of slashes.
+ */
+ if (strcmp(conf->d, "/") == 0)
+ conf->d_components = 0;
+ else
+ conf->d_components = ap_count_dirs(conf->d);
+
+ ap_add_per_dir_conf(cmd->server, new_dir_conf);
+
+ if (*arg != '\0') {
+ return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
+ "> arguments not (yet) supported.", NULL);
+ }
+
+ cmd->path = old_path;
+ cmd->override = old_overrides;
+
+ return NULL;
+}
+
+static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *errmsg;
+ const char *endp = ap_strrchr_c(arg, '>');
+ int old_overrides = cmd->override;
+ char *old_path = cmd->path;
+ core_dir_config *conf;
+ ap_regex_t *r = NULL;
+ const command_rec *thiscmd = cmd->cmd;
+ ap_conf_vector_t *new_url_conf = ap_create_per_dir_config(cmd->pool);
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (!arg[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ cmd->override = OR_ALL|ACCESS_CONF;
+
+ if (thiscmd->cmd_data) { /* <LocationMatch> */
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ }
+ else if (!strcmp(cmd->path, "~")) {
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ }
+
+ /* initialize our config and fetch it */
+ conf = ap_set_config_vectors(cmd->server, new_url_conf, cmd->path,
+ &core_module, cmd->pool);
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_url_conf);
+ if (errmsg != NULL)
+ return errmsg;
+
+ conf->d = apr_pstrdup(cmd->pool, cmd->path); /* No mangling, please */
+ conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0;
+ conf->r = r;
+
+ ap_add_per_url_conf(cmd->server, new_url_conf);
+
+ if (*arg != '\0') {
+ return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
+ "> arguments not (yet) supported.", NULL);
+ }
+
+ cmd->path = old_path;
+ cmd->override = old_overrides;
+
+ return NULL;
+}
+
+static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *errmsg;
+ const char *endp = ap_strrchr_c(arg, '>');
+ int old_overrides = cmd->override;
+ char *old_path = cmd->path;
+ core_dir_config *conf;
+ ap_regex_t *r = NULL;
+ const command_rec *thiscmd = cmd->cmd;
+ core_dir_config *c = mconfig;
+ ap_conf_vector_t *new_file_conf = ap_create_per_dir_config(cmd->pool);
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT|NOT_IN_LOCATION);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (!arg[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ /* Only if not an .htaccess file */
+ if (!old_path) {
+ cmd->override = OR_ALL|ACCESS_CONF;
+ }
+
+ if (thiscmd->cmd_data) { /* <FilesMatch> */
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ }
+ else if (!strcmp(cmd->path, "~")) {
+ cmd->path = ap_getword_conf(cmd->pool, &arg);
+ r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+ if (!r) {
+ return "Regex could not be compiled";
+ }
+ }
+ else {
+ char *newpath;
+ /* Ensure that the pathname is canonical, but we
+ * can't test the case/aliases without a fixed path */
+ if (apr_filepath_merge(&newpath, "", cmd->path,
+ 0, cmd->pool) != APR_SUCCESS)
+ return apr_pstrcat(cmd->pool, "<Files \"", cmd->path,
+ "\"> is invalid.", NULL);
+ cmd->path = newpath;
+ }
+
+ /* initialize our config and fetch it */
+ conf = ap_set_config_vectors(cmd->server, new_file_conf, cmd->path,
+ &core_module, cmd->pool);
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_file_conf);
+ if (errmsg != NULL)
+ return errmsg;
+
+ conf->d = cmd->path;
+ conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0;
+ conf->r = r;
+
+ ap_add_file_conf(c, new_file_conf);
+
+ if (*arg != '\0') {
+ return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
+ "> arguments not (yet) supported.", NULL);
+ }
+
+ cmd->path = old_path;
+ cmd->override = old_overrides;
+
+ return NULL;
+}
+
+static const char *start_ifmod(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *endp = ap_strrchr_c(arg, '>');
+ int not = (arg[0] == '!');
+ module *found;
+
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (not) {
+ arg++;
+ }
+
+ if (!arg[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ found = ap_find_linked_module(arg);
+
+ /* search prelinked stuff */
+ if (!found) {
+ ap_module_symbol_t *current = ap_prelinked_module_symbols;
+
+ for (; current->name; ++current) {
+ if (!strcmp(current->name, arg)) {
+ found = current->modp;
+ break;
+ }
+ }
+ }
+
+ /* search dynamic stuff */
+ if (!found) {
+ APR_OPTIONAL_FN_TYPE(ap_find_loaded_module_symbol) *check_symbol =
+ APR_RETRIEVE_OPTIONAL_FN(ap_find_loaded_module_symbol);
+
+ if (check_symbol) {
+ found = check_symbol(cmd->server, arg);
+ }
+ }
+
+ if ((!not && found) || (not && !found)) {
+ ap_directive_t *parent = NULL;
+ ap_directive_t *current = NULL;
+ const char *retval;
+
+ retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd,
+ &current, &parent, "<IfModule");
+ *(ap_directive_t **)mconfig = current;
+ return retval;
+ }
+ else {
+ *(ap_directive_t **)mconfig = NULL;
+ return ap_soak_end_container(cmd, "<IfModule");
+ }
+}
+
+AP_DECLARE(int) ap_exists_config_define(const char *name)
+{
+ char **defines;
+ int i;
+
+ defines = (char **)ap_server_config_defines->elts;
+ for (i = 0; i < ap_server_config_defines->nelts; i++) {
+ if (strcmp(defines[i], name) == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static const char *start_ifdefine(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *endp;
+ int defined;
+ int not = 0;
+
+ endp = ap_strrchr_c(arg, '>');
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (arg[0] == '!') {
+ not = 1;
+ arg++;
+ }
+
+ if (!arg[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ defined = ap_exists_config_define(arg);
+ if ((!not && defined) || (not && !defined)) {
+ ap_directive_t *parent = NULL;
+ ap_directive_t *current = NULL;
+ const char *retval;
+
+ retval = ap_build_cont_config(cmd->pool, cmd->temp_pool, cmd,
+ &current, &parent, "<IfDefine");
+ *(ap_directive_t **)dummy = current;
+ return retval;
+ }
+ else {
+ *(ap_directive_t **)dummy = NULL;
+ return ap_soak_end_container(cmd, "<IfDefine");
+ }
+}
+
+/* httpd.conf commands... beginning with the <VirtualHost> business */
+
+static const char *virtualhost_section(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ server_rec *main_server = cmd->server, *s;
+ const char *errmsg;
+ const char *endp = ap_strrchr_c(arg, '>');
+ apr_pool_t *p = cmd->pool;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return unclosed_directive(cmd);
+ }
+
+ arg = apr_pstrndup(cmd->pool, arg, endp - arg);
+
+ if (!arg[0]) {
+ return missing_container_arg(cmd);
+ }
+
+ /* FIXME: There's another feature waiting to happen here -- since you
+ can now put multiple addresses/names on a single <VirtualHost>
+ you might want to use it to group common definitions and then
+ define other "subhosts" with their individual differences. But
+ personally I'd rather just do it with a macro preprocessor. -djg */
+ if (main_server->is_virtual) {
+ return "<VirtualHost> doesn't nest!";
+ }
+
+ errmsg = ap_init_virtual_host(p, arg, main_server, &s);
+ if (errmsg) {
+ return errmsg;
+ }
+
+ s->next = main_server->next;
+ main_server->next = s;
+
+ s->defn_name = cmd->directive->filename;
+ s->defn_line_number = cmd->directive->line_num;
+
+ cmd->server = s;
+
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd,
+ s->lookup_defaults);
+
+ cmd->server = main_server;
+
+ return errmsg;
+}
+
+static const char *set_server_alias(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ if (!cmd->server->names) {
+ return "ServerAlias only used in <VirtualHost>";
+ }
+
+ while (*arg) {
+ char **item, *name = ap_getword_conf(cmd->pool, &arg);
+
+ if (ap_is_matchexp(name)) {
+ item = (char **)apr_array_push(cmd->server->wild_names);
+ }
+ else {
+ item = (char **)apr_array_push(cmd->server->names);
+ }
+
+ *item = name;
+ }
+
+ return NULL;
+}
+
+static const char *set_accf_map(cmd_parms *cmd, void *dummy,
+ const char *iproto, const char* iaccf)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+ &core_module);
+ char* proto;
+ char* accf;
+ if (err != NULL) {
+ return err;
+ }
+
+ proto = apr_pstrdup(cmd->pool, iproto);
+ ap_str_tolower(proto);
+ accf = apr_pstrdup(cmd->pool, iaccf);
+ ap_str_tolower(accf);
+ apr_table_set(conf->accf_map, proto, accf);
+
+ return NULL;
+}
+
+AP_DECLARE(const char*) ap_get_server_protocol(server_rec* s)
+{
+ core_server_config *conf = ap_get_module_config(s->module_config,
+ &core_module);
+ return conf->protocol;
+}
+
+AP_DECLARE(void) ap_set_server_protocol(server_rec* s, const char* proto)
+{
+ core_server_config *conf = ap_get_module_config(s->module_config,
+ &core_module);
+ conf->protocol = proto;
+}
+
+static const char *set_protocol(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+ &core_module);
+ char* proto;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ proto = apr_pstrdup(cmd->pool, arg);
+ ap_str_tolower(proto);
+ conf->protocol = proto;
+
+ return NULL;
+}
+
+static const char *set_server_string_slot(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ /* This one's pretty generic... */
+
+ int offset = (int)(long)cmd->info;
+ char *struct_ptr = (char *)cmd->server;
+
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ *(const char **)(struct_ptr + offset) = arg;
+ return NULL;
+}
+
+/*
+ * The ServerName directive takes one argument with format
+ * [scheme://]fully-qualified-domain-name[:port], for instance
+ * ServerName www.example.com
+ * ServerName www.example.com:80
+ * ServerName https://www.example.com:443
+ */
+
+static const char *server_hostname_port(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ const char *portstr, *part;
+ char *scheme;
+ int port;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ part = ap_strstr_c(arg, "://");
+
+ if (part) {
+ scheme = apr_pstrmemdup(cmd->pool, arg, part - arg);
+ ap_str_tolower(scheme);
+ cmd->server->server_scheme = scheme;
+ part += 3;
+ } else {
+ part = arg;
+ }
+
+ portstr = ap_strchr_c(part, ':');
+ if (portstr) {
+ cmd->server->server_hostname = apr_pstrmemdup(cmd->pool, part,
+ portstr - part);
+ portstr++;
+ port = atoi(portstr);
+ if (port <= 0 || port >= 65536) { /* 65536 == 1<<16 */
+ return apr_pstrcat(cmd->temp_pool, "The port number \"", arg,
+ "\" is outside the appropriate range "
+ "(i.e., 1..65535).", NULL);
+ }
+ }
+ else {
+ cmd->server->server_hostname = apr_pstrdup(cmd->pool, part);
+ port = 0;
+ }
+
+ cmd->server->port = port;
+ return NULL;
+}
+
+static const char *set_signature_flag(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "On") == 0) {
+ d->server_signature = srv_sig_on;
+ }
+ else if (strcasecmp(arg, "Off") == 0) {
+ d->server_signature = srv_sig_off;
+ }
+ else if (strcasecmp(arg, "EMail") == 0) {
+ d->server_signature = srv_sig_withmail;
+ }
+ else {
+ return "ServerSignature: use one of: off | on | email";
+ }
+
+ return NULL;
+}
+
+static const char *set_server_root(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if ((apr_filepath_merge((char**)&ap_server_root, NULL, arg,
+ APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
+ || !ap_is_directory(cmd->pool, ap_server_root)) {
+ return "ServerRoot must be a valid directory";
+ }
+
+ return NULL;
+}
+
+static const char *set_timeout(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ cmd->server->timeout = apr_time_from_sec(atoi(arg));
+ return NULL;
+}
+
+static const char *set_allow2f(cmd_parms *cmd, void *d_, int arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ d->allow_encoded_slashes = arg != 0;
+ return NULL;
+}
+
+static const char *set_hostname_lookups(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!strcasecmp(arg, "on")) {
+ d->hostname_lookups = HOSTNAME_LOOKUP_ON;
+ }
+ else if (!strcasecmp(arg, "off")) {
+ d->hostname_lookups = HOSTNAME_LOOKUP_OFF;
+ }
+ else if (!strcasecmp(arg, "double")) {
+ d->hostname_lookups = HOSTNAME_LOOKUP_DOUBLE;
+ }
+ else {
+ return "parameter must be 'on', 'off', or 'double'";
+ }
+
+ return NULL;
+}
+
+static const char *set_serverpath(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ cmd->server->path = arg;
+ cmd->server->pathlen = (int)strlen(arg);
+ return NULL;
+}
+
+static const char *set_content_md5(cmd_parms *cmd, void *d_, int arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ d->content_md5 = arg != 0;
+ return NULL;
+}
+
+static const char *set_accept_path_info(cmd_parms *cmd, void *d_, const char *arg)
+{
+ core_dir_config *d = d_;
+
+ if (strcasecmp(arg, "on") == 0) {
+ d->accept_path_info = AP_REQ_ACCEPT_PATH_INFO;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ d->accept_path_info = AP_REQ_REJECT_PATH_INFO;
+ }
+ else if (strcasecmp(arg, "default") == 0) {
+ d->accept_path_info = AP_REQ_DEFAULT_PATH_INFO;
+ }
+ else {
+ return "AcceptPathInfo must be set to on, off or default";
+ }
+
+ return NULL;
+}
+
+static const char *set_use_canonical_name(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ d->use_canonical_name = USE_CANONICAL_NAME_ON;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ d->use_canonical_name = USE_CANONICAL_NAME_OFF;
+ }
+ else if (strcasecmp(arg, "dns") == 0) {
+ d->use_canonical_name = USE_CANONICAL_NAME_DNS;
+ }
+ else {
+ return "parameter must be 'on', 'off', or 'dns'";
+ }
+
+ return NULL;
+}
+
+static const char *set_use_canonical_phys_port(cmd_parms *cmd, void *d_,
+ const char *arg)
+{
+ core_dir_config *d = d_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_ON;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_OFF;
+ }
+ else {
+ return "parameter must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
+
+static const char *include_config (cmd_parms *cmd, void *dummy,
+ const char *name)
+{
+ ap_directive_t *conftree = NULL;
+ const char* conffile, *error;
+ unsigned *recursion;
+ void *data;
+
+ apr_pool_userdata_get(&data, "ap_include_sentinel", cmd->pool);
+ if (data) {
+ recursion = data;
+ }
+ else {
+ data = recursion = apr_palloc(cmd->pool, sizeof(*recursion));
+ *recursion = 0;
+ apr_pool_userdata_setn(data, "ap_include_sentinel", NULL, cmd->pool);
+ }
+
+ if (++*recursion > AP_MAX_INCLUDE_DEPTH) {
+ *recursion = 0;
+ return apr_psprintf(cmd->pool, "Exceeded maximum include depth of %u. "
+ "You have probably a recursion somewhere.",
+ AP_MAX_INCLUDE_DEPTH);
+ }
+
+ conffile = ap_server_root_relative(cmd->pool, name);
+ if (!conffile) {
+ *recursion = 0;
+ return apr_pstrcat(cmd->pool, "Invalid Include path ",
+ name, NULL);
+ }
+
+ error = ap_process_resource_config(cmd->server, conffile,
+ &conftree, cmd->pool, cmd->temp_pool);
+ if (error) {
+ *recursion = 0;
+ return error;
+ }
+
+ *(ap_directive_t **)dummy = conftree;
+
+ /* recursion level done */
+ if (*recursion) {
+ --*recursion;
+ }
+
+ return NULL;
+}
+
+static const char *set_loglevel(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ char *str;
+
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ if (err != NULL) {
+ return err;
+ }
+
+ if ((str = ap_getword_conf(cmd->pool, &arg))) {
+ if (!strcasecmp(str, "emerg")) {
+ cmd->server->loglevel = APLOG_EMERG;
+ }
+ else if (!strcasecmp(str, "alert")) {
+ cmd->server->loglevel = APLOG_ALERT;
+ }
+ else if (!strcasecmp(str, "crit")) {
+ cmd->server->loglevel = APLOG_CRIT;
+ }
+ else if (!strcasecmp(str, "error")) {
+ cmd->server->loglevel = APLOG_ERR;
+ }
+ else if (!strcasecmp(str, "warn")) {
+ cmd->server->loglevel = APLOG_WARNING;
+ }
+ else if (!strcasecmp(str, "notice")) {
+ cmd->server->loglevel = APLOG_NOTICE;
+ }
+ else if (!strcasecmp(str, "info")) {
+ cmd->server->loglevel = APLOG_INFO;
+ }
+ else if (!strcasecmp(str, "debug")) {
+ cmd->server->loglevel = APLOG_DEBUG;
+ }
+ else {
+ return "LogLevel requires level keyword: one of "
+ "emerg/alert/crit/error/warn/notice/info/debug";
+ }
+ }
+ else {
+ return "LogLevel requires level keyword";
+ }
+
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_psignature(const char *prefix, request_rec *r)
+{
+ char sport[20];
+ core_dir_config *conf;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+ if ((conf->server_signature == srv_sig_off)
+ || (conf->server_signature == srv_sig_unset)) {
+ return "";
+ }
+
+ apr_snprintf(sport, sizeof sport, "%u", (unsigned) ap_get_server_port(r));
+
+ if (conf->server_signature == srv_sig_withmail) {
+ return apr_pstrcat(r->pool, prefix, "<address>",
+ ap_get_server_version(),
+ " Server at <a href=\"",
+ ap_is_url(r->server->server_admin) ? "" : "mailto:",
+ ap_escape_html(r->pool, r->server->server_admin),
+ "\">",
+ ap_escape_html(r->pool, ap_get_server_name(r)),
+ "</a> Port ", sport,
+ "</address>\n", NULL);
+ }
+
+ return apr_pstrcat(r->pool, prefix, "<address>", ap_get_server_version(),
+ " Server at ",
+ ap_escape_html(r->pool, ap_get_server_name(r)),
+ " Port ", sport,
+ "</address>\n", NULL);
+}
+
+/*
+ * Load an authorisation realm into our location configuration, applying the
+ * usual rules that apply to realms.
+ */
+static const char *set_authname(cmd_parms *cmd, void *mconfig,
+ const char *word1)
+{
+ core_dir_config *aconfig = (core_dir_config *)mconfig;
+
+ aconfig->ap_auth_name = ap_escape_quotes(cmd->pool, word1);
+ return NULL;
+}
+
+/*
+ * Handle a request to include the server's OS platform in the Server
+ * response header field (the ServerTokens directive). Unfortunately
+ * this requires a new global in order to communicate the setting back to
+ * http_main so it can insert the information in the right place in the
+ * string.
+ */
+
+static char *server_version = NULL;
+static int version_locked = 0;
+
+enum server_token_type {
+ SrvTk_MAJOR, /* eg: Apache/2 */
+ SrvTk_MINOR, /* eg. Apache/2.0 */
+ SrvTk_MINIMAL, /* eg: Apache/2.0.41 */
+ SrvTk_OS, /* eg: Apache/2.0.41 (UNIX) */
+ SrvTk_FULL, /* eg: Apache/2.0.41 (UNIX) PHP/4.2.2 FooBar/1.2b */
+ SrvTk_PRODUCT_ONLY /* eg: Apache */
+};
+static enum server_token_type ap_server_tokens = SrvTk_FULL;
+
+static apr_status_t reset_version(void *dummy)
+{
+ version_locked = 0;
+ ap_server_tokens = SrvTk_FULL;
+ server_version = NULL;
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(void) ap_get_server_revision(ap_version_t *version)
+{
+ version->major = AP_SERVER_MAJORVERSION_NUMBER;
+ version->minor = AP_SERVER_MINORVERSION_NUMBER;
+ version->patch = AP_SERVER_PATCHLEVEL_NUMBER;
+ version->add_string = AP_SERVER_ADD_STRING;
+}
+
+AP_DECLARE(const char *) ap_get_server_version(void)
+{
+ return (server_version ? server_version : AP_SERVER_BASEVERSION);
+}
+
+AP_DECLARE(void) ap_add_version_component(apr_pool_t *pconf, const char *component)
+{
+ if (! version_locked) {
+ /*
+ * If the version string is null, register our cleanup to reset the
+ * pointer on pool destruction. We also know that, if NULL,
+ * we are adding the original SERVER_BASEVERSION string.
+ */
+ if (server_version == NULL) {
+ apr_pool_cleanup_register(pconf, NULL, reset_version,
+ apr_pool_cleanup_null);
+ server_version = apr_pstrdup(pconf, component);
+ }
+ else {
+ /*
+ * Tack the given component identifier to the end of
+ * the existing string.
+ */
+ server_version = apr_pstrcat(pconf, server_version, " ",
+ component, NULL);
+ }
+ }
+}
+
+/*
+ * This routine adds the real server base identity to the version string,
+ * and then locks out changes until the next reconfig.
+ */
+static void ap_set_version(apr_pool_t *pconf)
+{
+ if (ap_server_tokens == SrvTk_PRODUCT_ONLY) {
+ ap_add_version_component(pconf, AP_SERVER_BASEPRODUCT);
+ }
+ else if (ap_server_tokens == SrvTk_MINIMAL) {
+ ap_add_version_component(pconf, AP_SERVER_BASEVERSION);
+ }
+ else if (ap_server_tokens == SrvTk_MINOR) {
+ ap_add_version_component(pconf, AP_SERVER_BASEPRODUCT "/" AP_SERVER_MINORREVISION);
+ }
+ else if (ap_server_tokens == SrvTk_MAJOR) {
+ ap_add_version_component(pconf, AP_SERVER_BASEPRODUCT "/" AP_SERVER_MAJORVERSION);
+ }
+ else {
+ ap_add_version_component(pconf, AP_SERVER_BASEVERSION " (" PLATFORM ")");
+ }
+
+ /*
+ * Lock the server_version string if we're not displaying
+ * the full set of tokens
+ */
+ if (ap_server_tokens != SrvTk_FULL) {
+ version_locked++;
+ }
+}
+
+static const char *set_serv_tokens(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!strcasecmp(arg, "OS")) {
+ ap_server_tokens = SrvTk_OS;
+ }
+ else if (!strcasecmp(arg, "Min") || !strcasecmp(arg, "Minimal")) {
+ ap_server_tokens = SrvTk_MINIMAL;
+ }
+ else if (!strcasecmp(arg, "Major")) {
+ ap_server_tokens = SrvTk_MAJOR;
+ }
+ else if (!strcasecmp(arg, "Minor") ) {
+ ap_server_tokens = SrvTk_MINOR;
+ }
+ else if (!strcasecmp(arg, "Prod") || !strcasecmp(arg, "ProductOnly")) {
+ ap_server_tokens = SrvTk_PRODUCT_ONLY;
+ }
+ else {
+ ap_server_tokens = SrvTk_FULL;
+ }
+
+ return NULL;
+}
+
+static const char *set_limit_req_line(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ int lim;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ lim = atoi(arg);
+ if (lim < 0) {
+ return apr_pstrcat(cmd->temp_pool, "LimitRequestLine \"", arg,
+ "\" must be a non-negative integer", NULL);
+ }
+
+ cmd->server->limit_req_line = lim;
+ return NULL;
+}
+
+static const char *set_limit_req_fieldsize(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ int lim;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ lim = atoi(arg);
+ if (lim < 0) {
+ return apr_pstrcat(cmd->temp_pool, "LimitRequestFieldsize \"", arg,
+ "\" must be a non-negative integer",
+ NULL);
+ }
+
+ cmd->server->limit_req_fieldsize = lim;
+ return NULL;
+}
+
+static const char *set_limit_req_fields(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd,
+ NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+ int lim;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ lim = atoi(arg);
+ if (lim < 0) {
+ return apr_pstrcat(cmd->temp_pool, "LimitRequestFields \"", arg,
+ "\" must be a non-negative integer (0 = no limit)",
+ NULL);
+ }
+
+ cmd->server->limit_req_fields = lim;
+ return NULL;
+}
+
+static const char *set_limit_req_body(cmd_parms *cmd, void *conf_,
+ const char *arg)
+{
+ core_dir_config *conf = conf_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+ char *errp;
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (APR_SUCCESS != apr_strtoff(&conf->limit_req_body, arg, &errp, 10)) {
+ return "LimitRequestBody argument is not parsable.";
+ }
+ if (*errp || conf->limit_req_body < 0) {
+ return "LimitRequestBody requires a non-negative integer.";
+ }
+
+ return NULL;
+}
+
+static const char *set_limit_xml_req_body(cmd_parms *cmd, void *conf_,
+ const char *arg)
+{
+ core_dir_config *conf = conf_;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ conf->limit_xml_body = atol(arg);
+ if (conf->limit_xml_body < 0)
+ return "LimitXMLRequestBody requires a non-negative integer.";
+
+ return NULL;
+}
+
+AP_DECLARE(size_t) ap_get_limit_xml_body(const request_rec *r)
+{
+ core_dir_config *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &core_module);
+ if (conf->limit_xml_body == AP_LIMIT_UNSET)
+ return AP_DEFAULT_LIMIT_XML_BODY;
+
+ return (size_t)conf->limit_xml_body;
+}
+
+#if !defined (RLIMIT_CPU) || !(defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)) || !defined (RLIMIT_NPROC)
+static const char *no_set_limit(cmd_parms *cmd, void *conf_,
+ const char *arg, const char *arg2)
+{
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, cmd->server,
+ "%s not supported on this platform", cmd->cmd->name);
+
+ return NULL;
+}
+#endif
+
+#ifdef RLIMIT_CPU
+static const char *set_limit_cpu(cmd_parms *cmd, void *conf_,
+ const char *arg, const char *arg2)
+{
+ core_dir_config *conf = conf_;
+
+ unixd_set_rlimit(cmd, &conf->limit_cpu, arg, arg2, RLIMIT_CPU);
+ return NULL;
+}
+#endif
+
+#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)
+static const char *set_limit_mem(cmd_parms *cmd, void *conf_,
+ const char *arg, const char * arg2)
+{
+ core_dir_config *conf = conf_;
+
+#if defined(RLIMIT_AS)
+ unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2 ,RLIMIT_AS);
+#elif defined(RLIMIT_DATA)
+ unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_DATA);
+#elif defined(RLIMIT_VMEM)
+ unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_VMEM);
+#endif
+
+ return NULL;
+}
+#endif
+
+#ifdef RLIMIT_NPROC
+static const char *set_limit_nproc(cmd_parms *cmd, void *conf_,
+ const char *arg, const char * arg2)
+{
+ core_dir_config *conf = conf_;
+
+ unixd_set_rlimit(cmd, &conf->limit_nproc, arg, arg2, RLIMIT_NPROC);
+ return NULL;
+}
+#endif
+
+static const char *set_recursion_limit(cmd_parms *cmd, void *dummy,
+ const char *arg1, const char *arg2)
+{
+ core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+ &core_module);
+ int limit = atoi(arg1);
+
+ if (limit <= 0) {
+ return "The recursion limit must be greater than zero.";
+ }
+ if (limit < 4) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "Limiting internal redirects to very low numbers may "
+ "cause normal requests to fail.");
+ }
+
+ conf->redirect_limit = limit;
+
+ if (arg2) {
+ limit = atoi(arg2);
+
+ if (limit <= 0) {
+ return "The recursion limit must be greater than zero.";
+ }
+ if (limit < 4) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+ "Limiting the subrequest depth to a very low level may"
+ " cause normal requests to fail.");
+ }
+ }
+
+ conf->subreq_limit = limit;
+
+ return NULL;
+}
+
+static void log_backtrace(const request_rec *r)
+{
+ const request_rec *top = r;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "r->uri = %s", r->uri ? r->uri : "(unexpectedly NULL)");
+
+ while (top && (top->prev || top->main)) {
+ if (top->prev) {
+ top = top->prev;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "redirected from r->uri = %s",
+ top->uri ? top->uri : "(unexpectedly NULL)");
+ }
+
+ if (!top->prev && top->main) {
+ top = top->main;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "subrequested from r->uri = %s",
+ top->uri ? top->uri : "(unexpectedly NULL)");
+ }
+ }
+}
+
+/*
+ * check whether redirect limit is reached
+ */
+AP_DECLARE(int) ap_is_recursion_limit_exceeded(const request_rec *r)
+{
+ core_server_config *conf = ap_get_module_config(r->server->module_config,
+ &core_module);
+ const request_rec *top = r;
+ int redirects = 0, subreqs = 0;
+ int rlimit = conf->redirect_limit
+ ? conf->redirect_limit
+ : AP_DEFAULT_MAX_INTERNAL_REDIRECTS;
+ int slimit = conf->subreq_limit
+ ? conf->subreq_limit
+ : AP_DEFAULT_MAX_SUBREQ_DEPTH;
+
+
+ while (top->prev || top->main) {
+ if (top->prev) {
+ if (++redirects >= rlimit) {
+ /* uuh, too much. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Request exceeded the limit of %d internal "
+ "redirects due to probable configuration error. "
+ "Use 'LimitInternalRecursion' to increase the "
+ "limit if necessary. Use 'LogLevel debug' to get "
+ "a backtrace.", rlimit);
+
+ /* post backtrace */
+ log_backtrace(r);
+
+ /* return failure */
+ return 1;
+ }
+
+ top = top->prev;
+ }
+
+ if (!top->prev && top->main) {
+ if (++subreqs >= slimit) {
+ /* uuh, too much. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Request exceeded the limit of %d subrequest "
+ "nesting levels due to probable confguration "
+ "error. Use 'LimitInternalRecursion' to increase "
+ "the limit if necessary. Use 'LogLevel debug' to "
+ "get a backtrace.", slimit);
+
+ /* post backtrace */
+ log_backtrace(r);
+
+ /* return failure */
+ return 1;
+ }
+
+ top = top->main;
+ }
+ }
+
+ /* recursion state: ok */
+ return 0;
+}
+
+static const char *add_ct_output_filters(cmd_parms *cmd, void *conf_,
+ const char *arg, const char *arg2)
+{
+ core_dir_config *conf = conf_;
+ ap_filter_rec_t *old, *new = NULL;
+ const char *filter_name;
+
+ if (!conf->ct_output_filters) {
+ conf->ct_output_filters = apr_hash_make(cmd->pool);
+ old = NULL;
+ }
+ else {
+ old = (ap_filter_rec_t*) apr_hash_get(conf->ct_output_filters, arg2,
+ APR_HASH_KEY_STRING);
+ /* find last entry */
+ if (old) {
+ while (old->next) {
+ old = old->next;
+ }
+ }
+ }
+
+ while (*arg &&
+ (filter_name = ap_getword(cmd->pool, &arg, ';')) &&
+ strcmp(filter_name, "")) {
+ new = apr_pcalloc(cmd->pool, sizeof(ap_filter_rec_t));
+ new->name = filter_name;
+
+ /* We found something, so let's append it. */
+ if (old) {
+ old->next = new;
+ }
+ else {
+ apr_hash_set(conf->ct_output_filters, arg2,
+ APR_HASH_KEY_STRING, new);
+ }
+ old = new;
+ }
+
+ if (!new) {
+ return "invalid filter name";
+ }
+
+ return NULL;
+}
+/*
+ * Insert filters requested by the AddOutputFilterByType
+ * configuration directive. We cannot add filters based
+ * on content-type until after the handler has started
+ * to run. Only then do we reliably know the content-type.
+ */
+void ap_add_output_filters_by_type(request_rec *r)
+{
+ core_dir_config *conf;
+ const char *ctype;
+
+ conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ /* We can't do anything with no content-type or if we don't have a
+ * filter configured.
+ */
+ if (!r->content_type || !conf->ct_output_filters) {
+ return;
+ }
+
+ /* remove c-t decoration */
+ ctype = ap_field_noparam(r->pool, r->content_type);
+ if (ctype) {
+ ap_filter_rec_t *ct_filter;
+ ct_filter = apr_hash_get(conf->ct_output_filters, ctype,
+ APR_HASH_KEY_STRING);
+ while (ct_filter) {
+ ap_add_output_filter(ct_filter->name, NULL, r, r->connection);
+ ct_filter = ct_filter->next;
+ }
+ }
+
+ return;
+}
+
+static const char *set_trace_enable(cmd_parms *cmd, void *dummy,
+ const char *arg1)
+{
+ core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+ &core_module);
+
+ if (strcasecmp(arg1, "on") == 0) {
+ conf->trace_enable = AP_TRACE_ENABLE;
+ }
+ else if (strcasecmp(arg1, "off") == 0) {
+ conf->trace_enable = AP_TRACE_DISABLE;
+ }
+ else if (strcasecmp(arg1, "extended") == 0) {
+ conf->trace_enable = AP_TRACE_EXTENDED;
+ }
+ else {
+ return "TraceEnable must be one of 'on', 'off', or 'extended'";
+ }
+
+ return NULL;
+}
+
+/* Note --- ErrorDocument will now work from .htaccess files.
+ * The AllowOverride of Fileinfo allows webmasters to turn it off
+ */
+
+static const command_rec core_cmds[] = {
+
+/* Old access config file commands */
+
+AP_INIT_RAW_ARGS("<Directory", dirsection, NULL, RSRC_CONF,
+ "Container for directives affecting resources located in the specified "
+ "directories"),
+AP_INIT_RAW_ARGS("<Location", urlsection, NULL, RSRC_CONF,
+ "Container for directives affecting resources accessed through the "
+ "specified URL paths"),
+AP_INIT_RAW_ARGS("<VirtualHost", virtualhost_section, NULL, RSRC_CONF,
+ "Container to map directives to a particular virtual host, takes one or "
+ "more host addresses"),
+AP_INIT_RAW_ARGS("<Files", filesection, NULL, OR_ALL,
+ "Container for directives affecting files matching specified patterns"),
+AP_INIT_RAW_ARGS("<Limit", ap_limit_section, NULL, OR_ALL,
+ "Container for authentication directives when accessed using specified HTTP "
+ "methods"),
+AP_INIT_RAW_ARGS("<LimitExcept", ap_limit_section, (void*)1, OR_ALL,
+ "Container for authentication directives to be applied when any HTTP "
+ "method other than those specified is used to access the resource"),
+AP_INIT_TAKE1("<IfModule", start_ifmod, NULL, EXEC_ON_READ | OR_ALL,
+ "Container for directives based on existance of specified modules"),
+AP_INIT_TAKE1("<IfDefine", start_ifdefine, NULL, EXEC_ON_READ | OR_ALL,
+ "Container for directives based on existance of command line defines"),
+AP_INIT_RAW_ARGS("<DirectoryMatch", dirsection, (void*)1, RSRC_CONF,
+ "Container for directives affecting resources located in the "
+ "specified directories"),
+AP_INIT_RAW_ARGS("<LocationMatch", urlsection, (void*)1, RSRC_CONF,
+ "Container for directives affecting resources accessed through the "
+ "specified URL paths"),
+AP_INIT_RAW_ARGS("<FilesMatch", filesection, (void*)1, OR_ALL,
+ "Container for directives affecting files matching specified patterns"),
+AP_INIT_TAKE1("AuthType", ap_set_string_slot,
+ (void*)APR_OFFSETOF(core_dir_config, ap_auth_type), OR_AUTHCFG,
+ "An HTTP authorization type (e.g., \"Basic\")"),
+AP_INIT_TAKE1("AuthName", set_authname, NULL, OR_AUTHCFG,
+ "The authentication realm (e.g. \"Members Only\")"),
+AP_INIT_RAW_ARGS("Require", require, NULL, OR_AUTHCFG,
+ "Selects which authenticated users or groups may access a protected space"),
+AP_INIT_TAKE1("Satisfy", satisfy, NULL, OR_AUTHCFG,
+ "access policy if both allow and require used ('all' or 'any')"),
+#ifdef GPROF
+AP_INIT_TAKE1("GprofDir", set_gprof_dir, NULL, RSRC_CONF,
+ "Directory to plop gmon.out files"),
+#endif
+AP_INIT_TAKE1("AddDefaultCharset", set_add_default_charset, NULL, OR_FILEINFO,
+ "The name of the default charset to add to any Content-Type without one or 'Off' to disable"),
+AP_INIT_TAKE1("AcceptPathInfo", set_accept_path_info, NULL, OR_FILEINFO,
+ "Set to on or off for PATH_INFO to be accepted by handlers, or default for the per-handler preference"),
+
+/* Old resource config file commands */
+
+AP_INIT_RAW_ARGS("AccessFileName", set_access_name, NULL, RSRC_CONF,
+ "Name(s) of per-directory config files (default: .htaccess)"),
+AP_INIT_TAKE1("DocumentRoot", set_document_root, NULL, RSRC_CONF,
+ "Root directory of the document tree"),
+AP_INIT_TAKE2("ErrorDocument", set_error_document, NULL, OR_FILEINFO,
+ "Change responses for HTTP errors"),
+AP_INIT_RAW_ARGS("AllowOverride", set_override, NULL, ACCESS_CONF,
+ "Controls what groups of directives can be configured by per-directory "
+ "config files"),
+AP_INIT_RAW_ARGS("Options", set_options, NULL, OR_OPTIONS,
+ "Set a number of attributes for a given directory"),
+AP_INIT_TAKE1("DefaultType", ap_set_string_slot,
+ (void*)APR_OFFSETOF(core_dir_config, ap_default_type),
+ OR_FILEINFO, "the default MIME type for untypable files"),
+AP_INIT_RAW_ARGS("FileETag", set_etag_bits, NULL, OR_FILEINFO,
+ "Specify components used to construct a file's ETag"),
+AP_INIT_TAKE1("EnableMMAP", set_enable_mmap, NULL, OR_FILEINFO,
+ "Controls whether memory-mapping may be used to read files"),
+AP_INIT_TAKE1("EnableSendfile", set_enable_sendfile, NULL, OR_FILEINFO,
+ "Controls whether sendfile may be used to transmit files"),
+
+/* Old server config file commands */
+
+AP_INIT_TAKE1("Protocol", set_protocol, NULL, RSRC_CONF,
+ "Set the Protocol for httpd to use."),
+AP_INIT_TAKE2("AcceptFilter", set_accf_map, NULL, RSRC_CONF,
+ "Set the Accept Filter to use for a protocol"),
+AP_INIT_TAKE1("Port", ap_set_deprecated, NULL, RSRC_CONF,
+ "Port was replaced with Listen in Apache 2.0"),
+AP_INIT_TAKE1("HostnameLookups", set_hostname_lookups, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "\"on\" to enable, \"off\" to disable reverse DNS lookups, or \"double\" to "
+ "enable double-reverse DNS lookups"),
+AP_INIT_TAKE1("ServerAdmin", set_server_string_slot,
+ (void *)APR_OFFSETOF(server_rec, server_admin), RSRC_CONF,
+ "The email address of the server administrator"),
+AP_INIT_TAKE1("ServerName", server_hostname_port, NULL, RSRC_CONF,
+ "The hostname and port of the server"),
+AP_INIT_TAKE1("ServerSignature", set_signature_flag, NULL, OR_ALL,
+ "En-/disable server signature (on|off|email)"),
+AP_INIT_TAKE1("ServerRoot", set_server_root, NULL, RSRC_CONF | EXEC_ON_READ,
+ "Common directory of server-related files (logs, confs, etc.)"),
+AP_INIT_TAKE1("ErrorLog", set_server_string_slot,
+ (void *)APR_OFFSETOF(server_rec, error_fname), RSRC_CONF,
+ "The filename of the error log"),
+AP_INIT_RAW_ARGS("ServerAlias", set_server_alias, NULL, RSRC_CONF,
+ "A name or names alternately used to access the server"),
+AP_INIT_TAKE1("ServerPath", set_serverpath, NULL, RSRC_CONF,
+ "The pathname the server can be reached at"),
+AP_INIT_TAKE1("Timeout", set_timeout, NULL, RSRC_CONF,
+ "Timeout duration (sec)"),
+AP_INIT_FLAG("ContentDigest", set_content_md5, NULL, OR_OPTIONS,
+ "whether or not to send a Content-MD5 header with each request"),
+AP_INIT_TAKE1("UseCanonicalName", set_use_canonical_name, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "How to work out the ServerName : Port when constructing URLs"),
+AP_INIT_TAKE1("UseCanonicalPhysicalPort", set_use_canonical_phys_port, NULL,
+ RSRC_CONF|ACCESS_CONF,
+ "Whether to use the physical Port when constructing URLs"),
+/* TODO: RlimitFoo should all be part of mod_cgi, not in the core */
+/* TODO: ListenBacklog in MPM */
+AP_INIT_TAKE1("Include", include_config, NULL,
+ (RSRC_CONF | ACCESS_CONF | EXEC_ON_READ),
+ "Name of the config file to be included"),
+AP_INIT_TAKE1("LogLevel", set_loglevel, NULL, RSRC_CONF,
+ "Level of verbosity in error logging"),
+AP_INIT_TAKE1("NameVirtualHost", ap_set_name_virtual_host, NULL, RSRC_CONF,
+ "A numeric IP address:port, or the name of a host"),
+AP_INIT_TAKE1("ServerTokens", set_serv_tokens, NULL, RSRC_CONF,
+ "Determine tokens displayed in the Server: header - Min(imal), OS or Full"),
+AP_INIT_TAKE1("LimitRequestLine", set_limit_req_line, NULL, RSRC_CONF,
+ "Limit on maximum size of an HTTP request line"),
+AP_INIT_TAKE1("LimitRequestFieldsize", set_limit_req_fieldsize, NULL,
+ RSRC_CONF,
+ "Limit on maximum size of an HTTP request header field"),
+AP_INIT_TAKE1("LimitRequestFields", set_limit_req_fields, NULL, RSRC_CONF,
+ "Limit (0 = unlimited) on max number of header fields in a request message"),
+AP_INIT_TAKE1("LimitRequestBody", set_limit_req_body,
+ (void*)APR_OFFSETOF(core_dir_config, limit_req_body), OR_ALL,
+ "Limit (in bytes) on maximum size of request message body"),
+AP_INIT_TAKE1("LimitXMLRequestBody", set_limit_xml_req_body, NULL, OR_ALL,
+ "Limit (in bytes) on maximum size of an XML-based request "
+ "body"),
+
+/* System Resource Controls */
+#ifdef RLIMIT_CPU
+AP_INIT_TAKE12("RLimitCPU", set_limit_cpu,
+ (void*)APR_OFFSETOF(core_dir_config, limit_cpu),
+ OR_ALL, "Soft/hard limits for max CPU usage in seconds"),
+#else
+AP_INIT_TAKE12("RLimitCPU", no_set_limit, NULL,
+ OR_ALL, "Soft/hard limits for max CPU usage in seconds"),
+#endif
+#if defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined (RLIMIT_AS)
+AP_INIT_TAKE12("RLimitMEM", set_limit_mem,
+ (void*)APR_OFFSETOF(core_dir_config, limit_mem),
+ OR_ALL, "Soft/hard limits for max memory usage per process"),
+#else
+AP_INIT_TAKE12("RLimitMEM", no_set_limit, NULL,
+ OR_ALL, "Soft/hard limits for max memory usage per process"),
+#endif
+#ifdef RLIMIT_NPROC
+AP_INIT_TAKE12("RLimitNPROC", set_limit_nproc,
+ (void*)APR_OFFSETOF(core_dir_config, limit_nproc),
+ OR_ALL, "soft/hard limits for max number of processes per uid"),
+#else
+AP_INIT_TAKE12("RLimitNPROC", no_set_limit, NULL,
+ OR_ALL, "soft/hard limits for max number of processes per uid"),
+#endif
+
+/* internal recursion stopper */
+AP_INIT_TAKE12("LimitInternalRecursion", set_recursion_limit, NULL, RSRC_CONF,
+ "maximum recursion depth of internal redirects and subrequests"),
+
+AP_INIT_TAKE1("ForceType", ap_set_string_slot_lower,
+ (void *)APR_OFFSETOF(core_dir_config, mime_type), OR_FILEINFO,
+ "a mime type that overrides other configured type"),
+AP_INIT_TAKE1("SetHandler", ap_set_string_slot_lower,
+ (void *)APR_OFFSETOF(core_dir_config, handler), OR_FILEINFO,
+ "a handler name that overrides any other configured handler"),
+AP_INIT_TAKE1("SetOutputFilter", ap_set_string_slot,
+ (void *)APR_OFFSETOF(core_dir_config, output_filters), OR_FILEINFO,
+ "filter (or ; delimited list of filters) to be run on the request content"),
+AP_INIT_TAKE1("SetInputFilter", ap_set_string_slot,
+ (void *)APR_OFFSETOF(core_dir_config, input_filters), OR_FILEINFO,
+ "filter (or ; delimited list of filters) to be run on the request body"),
+AP_INIT_ITERATE2("AddOutputFilterByType", add_ct_output_filters,
+ (void *)APR_OFFSETOF(core_dir_config, ct_output_filters), OR_FILEINFO,
+ "output filter name followed by one or more content-types"),
+AP_INIT_FLAG("AllowEncodedSlashes", set_allow2f, NULL, RSRC_CONF,
+ "Allow URLs containing '/' encoded as '%2F'"),
+
+/*
+ * These are default configuration directives that mpms can/should
+ * pay attention to. If an mpm wishes to use these, they should
+ * #defined them in mpm.h.
+ */
+#ifdef AP_MPM_WANT_SET_PIDFILE
+AP_INIT_TAKE1("PidFile", ap_mpm_set_pidfile, NULL, RSRC_CONF,
+ "A file for logging the server process ID"),
+#endif
+#ifdef AP_MPM_WANT_SET_SCOREBOARD
+AP_INIT_TAKE1("ScoreBoardFile", ap_mpm_set_scoreboard, NULL, RSRC_CONF,
+ "A file for Apache to maintain runtime process management information"),
+#endif
+#ifdef AP_MPM_WANT_SET_LOCKFILE
+AP_INIT_TAKE1("LockFile", ap_mpm_set_lockfile, NULL, RSRC_CONF,
+ "The lockfile used when Apache needs to lock the accept() call"),
+#endif
+#ifdef AP_MPM_WANT_SET_MAX_REQUESTS
+AP_INIT_TAKE1("MaxRequestsPerChild", ap_mpm_set_max_requests, NULL, RSRC_CONF,
+ "Maximum number of requests a particular child serves before dying."),
+#endif
+#ifdef AP_MPM_WANT_SET_COREDUMPDIR
+AP_INIT_TAKE1("CoreDumpDirectory", ap_mpm_set_coredumpdir, NULL, RSRC_CONF,
+ "The location of the directory Apache changes to before dumping core"),
+#endif
+#ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+AP_INIT_TAKE1("AcceptMutex", ap_mpm_set_accept_lock_mech, NULL, RSRC_CONF,
+ ap_valid_accept_mutex_string),
+#endif
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+AP_INIT_TAKE1("MaxMemFree", ap_mpm_set_max_mem_free, NULL, RSRC_CONF,
+ "Maximum number of 1k blocks a particular childs allocator may hold."),
+#endif
+#ifdef AP_MPM_WANT_SET_STACKSIZE
+AP_INIT_TAKE1("ThreadStackSize", ap_mpm_set_thread_stacksize, NULL, RSRC_CONF,
+ "Size in bytes of stack used by threads handling client connections"),
+#endif
+#if AP_ENABLE_EXCEPTION_HOOK
+AP_INIT_TAKE1("EnableExceptionHook", ap_mpm_set_exception_hook, NULL, RSRC_CONF,
+ "Controls whether exception hook may be called after a crash"),
+#endif
+AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF,
+ "'on' (default), 'off' or 'extended' to trace request body content"),
+{ NULL }
+};
+
+/*****************************************************************
+ *
+ * Core handlers for various phases of server operation...
+ */
+
+AP_DECLARE_NONSTD(int) ap_core_translate(request_rec *r)
+{
+ void *sconf = r->server->module_config;
+ core_server_config *conf = ap_get_module_config(sconf, &core_module);
+ apr_status_t rv;
+
+ /* XXX this seems too specific, this should probably become
+ * some general-case test
+ */
+ if (r->proxyreq) {
+ return HTTP_FORBIDDEN;
+ }
+ if (!r->uri || ((r->uri[0] != '/') && strcmp(r->uri, "*"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Invalid URI in request %s", r->the_request);
+ return HTTP_BAD_REQUEST;
+ }
+
+ if (r->server->path
+ && !strncmp(r->uri, r->server->path, r->server->pathlen)
+ && (r->server->path[r->server->pathlen - 1] == '/'
+ || r->uri[r->server->pathlen] == '/'
+ || r->uri[r->server->pathlen] == '\0'))
+ {
+ /* skip all leading /'s (e.g. http://localhost///foo)
+ * so we are looking at only the relative path.
+ */
+ char *path = r->uri + r->server->pathlen;
+ while (*path == '/') {
+ ++path;
+ }
+ if ((rv = apr_filepath_merge(&r->filename, conf->ap_document_root, path,
+ APR_FILEPATH_TRUENAME
+ | APR_FILEPATH_SECUREROOT, r->pool))
+ != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "Cannot map %s to file", r->the_request);
+ return HTTP_FORBIDDEN;
+ }
+ r->canonical_filename = r->filename;
+ }
+ else {
+ /*
+ * Make sure that we do not mess up the translation by adding two
+ * /'s in a row. This happens under windows when the document
+ * root ends with a /
+ */
+ /* skip all leading /'s (e.g. http://localhost///foo)
+ * so we are looking at only the relative path.
+ */
+ char *path = r->uri;
+ while (*path == '/') {
+ ++path;
+ }
+ if ((rv = apr_filepath_merge(&r->filename, conf->ap_document_root, path,
+ APR_FILEPATH_TRUENAME
+ | APR_FILEPATH_SECUREROOT, r->pool))
+ != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "Cannot map %s to file", r->the_request);
+ return HTTP_FORBIDDEN;
+ }
+ r->canonical_filename = r->filename;
+ }
+
+ return OK;
+}
+
+/*****************************************************************
+ *
+ * Test the filesystem name through directory_walk and file_walk
+ */
+static int core_map_to_storage(request_rec *r)
+{
+ int access_status;
+
+ if ((access_status = ap_directory_walk(r))) {
+ return access_status;
+ }
+
+ if ((access_status = ap_file_walk(r))) {
+ return access_status;
+ }
+
+ return OK;
+}
+
+
+static int do_nothing(request_rec *r) { return OK; }
+
+
+static int core_override_type(request_rec *r)
+{
+ core_dir_config *conf =
+ (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+
+ /* Check for overrides with ForceType / SetHandler
+ */
+ if (conf->mime_type && strcmp(conf->mime_type, "none"))
+ ap_set_content_type(r, (char*) conf->mime_type);
+
+ if (conf->handler && strcmp(conf->handler, "none"))
+ r->handler = conf->handler;
+
+ /* Deal with the poor soul who is trying to force path_info to be
+ * accepted within the core_handler, where they will let the subreq
+ * address its contents. This is toggled by the user in the very
+ * beginning of the fixup phase, so modules should override the user's
+ * discretion in their own module fixup phase. It is tristate, if
+ * the user doesn't specify, the result is 2 (which the module may
+ * interpret to its own customary behavior.) It won't be touched
+ * if the value is no longer undefined (2), so any module changing
+ * the value prior to the fixup phase OVERRIDES the user's choice.
+ */
+ if ((r->used_path_info == AP_REQ_DEFAULT_PATH_INFO)
+ && (conf->accept_path_info != 3)) {
+ r->used_path_info = conf->accept_path_info;
+ }
+
+ return OK;
+}
+
+static int default_handler(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *e;
+ core_dir_config *d;
+ int errstatus;
+ apr_file_t *fd = NULL;
+ apr_status_t status;
+ /* XXX if/when somebody writes a content-md5 filter we either need to
+ * remove this support or coordinate when to use the filter vs.
+ * when to use this code
+ * The current choice of when to compute the md5 here matches the 1.3
+ * support fairly closely (unlike 1.3, we don't handle computing md5
+ * when the charset is translated).
+ */
+ int bld_content_md5;
+
+ d = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+ bld_content_md5 = (d->content_md5 & 1)
+ && r->output_filters->frec->ftype != AP_FTYPE_RESOURCE;
+
+ ap_allow_standard_methods(r, MERGE_ALLOW, M_GET, M_OPTIONS, M_POST, -1);
+
+ /* If filters intend to consume the request body, they must
+ * register an InputFilter to slurp the contents of the POST
+ * data from the POST input stream. It no longer exists when
+ * the output filters are invoked by the default handler.
+ */
+ if ((errstatus = ap_discard_request_body(r)) != OK) {
+ return errstatus;
+ }
+
+ if (r->method_number == M_GET || r->method_number == M_POST) {
+ if (r->finfo.filetype == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "File does not exist: %s", r->filename);
+ return HTTP_NOT_FOUND;
+ }
+
+ /* Don't try to serve a dir. Some OSs do weird things with
+ * raw I/O on a dir.
+ */
+ if (r->finfo.filetype == APR_DIR) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Attempt to serve directory: %s", r->filename);
+ return HTTP_NOT_FOUND;
+ }
+
+ if ((r->used_path_info != AP_REQ_ACCEPT_PATH_INFO) &&
+ r->path_info && *r->path_info)
+ {
+ /* default to reject */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "File does not exist: %s",
+ apr_pstrcat(r->pool, r->filename, r->path_info, NULL));
+ return HTTP_NOT_FOUND;
+ }
+
+ /* We understood the (non-GET) method, but it might not be legal for
+ this particular resource. Check to see if the 'deliver_script'
+ flag is set. If so, then we go ahead and deliver the file since
+ it isn't really content (only GET normally returns content).
+
+ Note: based on logic further above, the only possible non-GET
+ method at this point is POST. In the future, we should enable
+ script delivery for all methods. */
+ if (r->method_number != M_GET) {
+ core_request_config *req_cfg;
+
+ req_cfg = ap_get_module_config(r->request_config, &core_module);
+ if (!req_cfg->deliver_script) {
+ /* The flag hasn't been set for this request. Punt. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "This resource does not accept the %s method.",
+ r->method);
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+ }
+
+
+ if ((status = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY
+#if APR_HAS_SENDFILE
+ | ((d->enable_sendfile == ENABLE_SENDFILE_OFF)
+ ? 0 : APR_SENDFILE_ENABLED)
+#endif
+ , 0, r->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+ "file permissions deny server access: %s", r->filename);
+ return HTTP_FORBIDDEN;
+ }
+
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
+ ap_set_etag(r);
+ apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
+ ap_set_content_length(r, r->finfo.size);
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ if ((errstatus = ap_meets_conditions(r)) != OK) {
+ apr_file_close(fd);
+ r->status = errstatus;
+ }
+ else {
+ if (bld_content_md5) {
+ apr_table_setn(r->headers_out, "Content-MD5",
+ ap_md5digest(r->pool, fd));
+ }
+
+ /* For platforms where the size of the file may be larger than
+ * that which can be stored in a single bucket (where the
+ * length field is an apr_size_t), split it into several
+ * buckets: */
+ if (sizeof(apr_off_t) > sizeof(apr_size_t)
+ && r->finfo.size > AP_MAX_SENDFILE) {
+ apr_off_t fsize = r->finfo.size;
+ e = apr_bucket_file_create(fd, 0, AP_MAX_SENDFILE, r->pool,
+ c->bucket_alloc);
+ while (fsize > AP_MAX_SENDFILE) {
+ apr_bucket *ce;
+ apr_bucket_copy(e, &ce);
+ APR_BRIGADE_INSERT_TAIL(bb, ce);
+ e->start += AP_MAX_SENDFILE;
+ fsize -= AP_MAX_SENDFILE;
+ }
+ e->length = (apr_size_t)fsize; /* Resize just the last bucket */
+ }
+ else {
+ e = apr_bucket_file_create(fd, 0, (apr_size_t)r->finfo.size,
+ r->pool, c->bucket_alloc);
+ }
+
+#if APR_HAS_MMAP
+ if (d->enable_mmap == ENABLE_MMAP_OFF) {
+ (void)apr_bucket_file_enable_mmap(e, 0);
+ }
+#endif
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ status = ap_pass_brigade(r->output_filters, bb);
+ if (status == APR_SUCCESS
+ || r->status != HTTP_OK
+ || c->aborted) {
+ return OK;
+ }
+ else {
+ /* no way to know what type of error occurred */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ "default_handler: ap_pass_brigade returned %i",
+ status);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ else { /* unusual method (not GET or POST) */
+ if (r->method_number == M_INVALID) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Invalid method in request %s", r->the_request);
+ return HTTP_NOT_IMPLEMENTED;
+ }
+
+ if (r->method_number == M_OPTIONS) {
+ return ap_send_http_options(r);
+ }
+ return HTTP_METHOD_NOT_ALLOWED;
+ }
+}
+
+/* Optional function coming from mod_logio, used for logging of output
+ * traffic
+ */
+APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *logio_add_bytes_out;
+
+static int core_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out);
+ ident_lookup = APR_RETRIEVE_OPTIONAL_FN(ap_ident_lookup);
+
+ ap_set_version(pconf);
+ ap_setup_make_content_type(pconf);
+ return OK;
+}
+
+static void core_insert_filter(request_rec *r)
+{
+ core_dir_config *conf = (core_dir_config *)
+ ap_get_module_config(r->per_dir_config,
+ &core_module);
+ const char *filter, *filters = conf->output_filters;
+
+ if (filters) {
+ while (*filters && (filter = ap_getword(r->pool, &filters, ';'))) {
+ ap_add_output_filter(filter, NULL, r, r->connection);
+ }
+ }
+
+ filters = conf->input_filters;
+ if (filters) {
+ while (*filters && (filter = ap_getword(r->pool, &filters, ';'))) {
+ ap_add_input_filter(filter, NULL, r, r->connection);
+ }
+ }
+}
+
+static apr_size_t num_request_notes = AP_NUM_STD_NOTES;
+
+static apr_status_t reset_request_notes(void *dummy)
+{
+ num_request_notes = AP_NUM_STD_NOTES;
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(apr_size_t) ap_register_request_note(void)
+{
+ apr_pool_cleanup_register(apr_hook_global_pool, NULL, reset_request_notes,
+ apr_pool_cleanup_null);
+ return num_request_notes++;
+}
+
+AP_DECLARE(void **) ap_get_request_note(request_rec *r, apr_size_t note_num)
+{
+ core_request_config *req_cfg;
+
+ if (note_num >= num_request_notes) {
+ return NULL;
+ }
+
+ req_cfg = (core_request_config *)
+ ap_get_module_config(r->request_config, &core_module);
+
+ if (!req_cfg) {
+ return NULL;
+ }
+
+ return &(req_cfg->notes[note_num]);
+}
+
+static int core_create_req(request_rec *r)
+{
+ /* Alloc the config struct and the array of request notes in
+ * a single block for efficiency
+ */
+ core_request_config *req_cfg;
+
+ req_cfg = apr_pcalloc(r->pool, sizeof(core_request_config) +
+ sizeof(void *) * num_request_notes);
+ req_cfg->notes = (void **)((char *)req_cfg + sizeof(core_request_config));
+
+ /* ### temporarily enable script delivery as the default */
+ req_cfg->deliver_script = 1;
+
+ if (r->main) {
+ core_request_config *main_req_cfg = (core_request_config *)
+ ap_get_module_config(r->main->request_config, &core_module);
+ req_cfg->bb = main_req_cfg->bb;
+ }
+ else {
+ req_cfg->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ }
+
+ ap_set_module_config(r->request_config, &core_module, req_cfg);
+
+ return OK;
+}
+
+static int core_create_proxy_req(request_rec *r, request_rec *pr)
+{
+ return core_create_req(pr);
+}
+
+static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server,
+ apr_socket_t *csd, long id, void *sbh,
+ apr_bucket_alloc_t *alloc)
+{
+ apr_status_t rv;
+ conn_rec *c = (conn_rec *) apr_pcalloc(ptrans, sizeof(conn_rec));
+
+ c->sbh = sbh;
+ (void)ap_update_child_status(c->sbh, SERVER_BUSY_READ, (request_rec *)NULL);
+
+ /* Got a connection structure, so initialize what fields we can
+ * (the rest are zeroed out by pcalloc).
+ */
+ c->conn_config = ap_create_conn_config(ptrans);
+ c->notes = apr_table_make(ptrans, 5);
+
+ c->pool = ptrans;
+ if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, csd))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, server,
+ "apr_socket_addr_get(APR_LOCAL)");
+ apr_socket_close(csd);
+ return NULL;
+ }
+
+ apr_sockaddr_ip_get(&c->local_ip, c->local_addr);
+ if ((rv = apr_socket_addr_get(&c->remote_addr, APR_REMOTE, csd))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, server,
+ "apr_socket_addr_get(APR_REMOTE)");
+ apr_socket_close(csd);
+ return NULL;
+ }
+
+ apr_sockaddr_ip_get(&c->remote_ip, c->remote_addr);
+ c->base_server = server;
+
+ c->id = id;
+ c->bucket_alloc = alloc;
+
+ return c;
+}
+
+static int core_pre_connection(conn_rec *c, void *csd)
+{
+ core_net_rec *net = apr_palloc(c->pool, sizeof(*net));
+ apr_status_t rv;
+
+#ifdef AP_MPM_DISABLE_NAGLE_ACCEPTED_SOCK
+ /* The Nagle algorithm says that we should delay sending partial
+ * packets in hopes of getting more data. We don't want to do
+ * this; we are not telnet. There are bad interactions between
+ * persistent connections and Nagle's algorithm that have very severe
+ * performance penalties. (Failing to disable Nagle is not much of a
+ * problem with simple HTTP.)
+ */
+ rv = apr_socket_opt_set(csd, APR_TCP_NODELAY, 1);
+ if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
+ /* expected cause is that the client disconnected already,
+ * hence the debug level
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c,
+ "apr_socket_opt_set(APR_TCP_NODELAY)");
+ }
+#endif
+
+ /* The core filter requires the timeout mode to be set, which
+ * incidentally sets the socket to be nonblocking. If this
+ * is not initialized correctly, Linux - for example - will
+ * be initially blocking, while Solaris will be non blocking
+ * and any initial read will fail.
+ */
+ rv = apr_socket_timeout_set(csd, c->base_server->timeout);
+ if (rv != APR_SUCCESS) {
+ /* expected cause is that the client disconnected already */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c,
+ "apr_socket_timeout_set");
+ }
+
+ net->c = c;
+ net->in_ctx = NULL;
+ net->out_ctx = NULL;
+ net->client_socket = csd;
+
+ ap_set_module_config(net->c->conn_config, &core_module, csd);
+ ap_add_input_filter_handle(ap_core_input_filter_handle, net, NULL, net->c);
+ ap_add_output_filter_handle(ap_core_output_filter_handle, net, NULL, net->c);
+ return DONE;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* create_connection and install_transport_filters are
+ * hooks that should always be APR_HOOK_REALLY_LAST to give other
+ * modules the opportunity to install alternate network transports
+ * and stop other functions from being run.
+ */
+ ap_hook_create_connection(core_create_conn, NULL, NULL,
+ APR_HOOK_REALLY_LAST);
+ ap_hook_pre_connection(core_pre_connection, NULL, NULL,
+ APR_HOOK_REALLY_LAST);
+
+ ap_hook_post_config(core_post_config,NULL,NULL,APR_HOOK_REALLY_FIRST);
+ ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST);
+ ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST);
+ /* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */
+ ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_fixups(core_override_type,NULL,NULL,APR_HOOK_REALLY_FIRST);
+ ap_hook_access_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_create_request(core_create_req, NULL, NULL, APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(proxy, create_req, core_create_proxy_req, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE);
+
+ /* register the core's insert_filter hook and register core-provided
+ * filters
+ */
+ ap_hook_insert_filter(core_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
+
+ ap_core_input_filter_handle =
+ ap_register_input_filter("CORE_IN", ap_core_input_filter,
+ NULL, AP_FTYPE_NETWORK);
+ ap_content_length_filter_handle =
+ ap_register_output_filter("CONTENT_LENGTH", ap_content_length_filter,
+ NULL, AP_FTYPE_PROTOCOL);
+ ap_core_output_filter_handle =
+ ap_register_output_filter("CORE", ap_core_output_filter,
+ NULL, AP_FTYPE_NETWORK);
+ ap_subreq_core_filter_handle =
+ ap_register_output_filter("SUBREQ_CORE", ap_sub_req_output_filter,
+ NULL, AP_FTYPE_CONTENT_SET);
+ ap_old_write_func =
+ ap_register_output_filter("OLD_WRITE", ap_old_write_filter,
+ NULL, AP_FTYPE_RESOURCE - 10);
+}
+
+AP_DECLARE_DATA module core_module = {
+ STANDARD20_MODULE_STUFF,
+ create_core_dir_config, /* create per-directory config structure */
+ merge_core_dir_configs, /* merge per-directory config structures */
+ create_core_server_config, /* create per-server config structure */
+ merge_core_server_configs, /* merge per-server config structures */
+ core_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
+
diff --git a/server/core_filters.c b/server/core_filters.c
new file mode 100644
index 00000000..acbe1901
--- /dev/null
+++ b/server/core_filters.c
@@ -0,0 +1,926 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file core_filters.c
+ * @brief Core input/output network filters.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_fnmatch.h"
+#include "apr_hash.h"
+#include "apr_thread_proc.h" /* for RLIMIT stuff */
+#include "apr_hooks.h"
+
+#define APR_WANT_IOVEC
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h" /* For index_of_response(). Grump. */
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_main.h" /* For the default_handler below... */
+#include "http_log.h"
+#include "util_md5.h"
+#include "http_connection.h"
+#include "apr_buckets.h"
+#include "util_filter.h"
+#include "util_ebcdic.h"
+#include "mpm.h"
+#include "mpm_common.h"
+#include "scoreboard.h"
+#include "mod_core.h"
+#include "mod_proxy.h"
+#include "ap_listen.h"
+
+#include "mod_so.h" /* for ap_find_loaded_module_symbol */
+
+#define AP_MIN_SENDFILE_BYTES (256)
+
+/**
+ * Remove all zero length buckets from the brigade.
+ */
+#define BRIGADE_NORMALIZE(b) \
+do { \
+ apr_bucket *e = APR_BRIGADE_FIRST(b); \
+ do { \
+ if (e->length == 0 && !APR_BUCKET_IS_METADATA(e)) { \
+ apr_bucket *d; \
+ d = APR_BUCKET_NEXT(e); \
+ apr_bucket_delete(e); \
+ e = d; \
+ } \
+ else { \
+ e = APR_BUCKET_NEXT(e); \
+ } \
+ } while (!APR_BRIGADE_EMPTY(b) && (e != APR_BRIGADE_SENTINEL(b))); \
+} while (0)
+
+
+/**
+ * Split the contents of a brigade after bucket 'e' to an existing brigade
+ *
+ * XXXX: Should this function be added to APR-Util?
+ */
+static void brigade_move(apr_bucket_brigade *b, apr_bucket_brigade *a,
+ apr_bucket *e)
+{
+ apr_bucket *f;
+
+ if (e != APR_BRIGADE_SENTINEL(b)) {
+ f = APR_RING_LAST(&b->list);
+ APR_RING_UNSPLICE(e, f, link);
+ APR_RING_SPLICE_HEAD(&a->list, e, f, apr_bucket, link);
+ }
+
+ APR_BRIGADE_CHECK_CONSISTENCY(a);
+ APR_BRIGADE_CHECK_CONSISTENCY(b);
+}
+
+int ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b,
+ ap_input_mode_t mode, apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ apr_bucket *e;
+ apr_status_t rv;
+ core_net_rec *net = f->ctx;
+ core_ctx_t *ctx = net->in_ctx;
+ const char *str;
+ apr_size_t len;
+
+ if (mode == AP_MODE_INIT) {
+ /*
+ * this mode is for filters that might need to 'initialize'
+ * a connection before reading request data from a client.
+ * NNTP over SSL for example needs to handshake before the
+ * server sends the welcome message.
+ * such filters would have changed the mode before this point
+ * is reached. however, protocol modules such as NNTP should
+ * not need to know anything about SSL. given the example, if
+ * SSL is not in the filter chain, AP_MODE_INIT is a noop.
+ */
+ return APR_SUCCESS;
+ }
+
+ if (!ctx)
+ {
+ ctx = apr_pcalloc(f->c->pool, sizeof(*ctx));
+ ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ ctx->tmpbb = apr_brigade_create(ctx->b->p, ctx->b->bucket_alloc);
+ /* seed the brigade with the client socket. */
+ e = apr_bucket_socket_create(net->client_socket, f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(ctx->b, e);
+ net->in_ctx = ctx;
+ }
+ else if (APR_BRIGADE_EMPTY(ctx->b)) {
+ return APR_EOF;
+ }
+
+ /* ### This is bad. */
+ BRIGADE_NORMALIZE(ctx->b);
+
+ /* check for empty brigade again *AFTER* BRIGADE_NORMALIZE()
+ * If we have lost our socket bucket (see above), we are EOF.
+ *
+ * Ideally, this should be returning SUCCESS with EOS bucket, but
+ * some higher-up APIs (spec. read_request_line via ap_rgetline)
+ * want an error code. */
+ if (APR_BRIGADE_EMPTY(ctx->b)) {
+ return APR_EOF;
+ }
+
+ if (mode == AP_MODE_GETLINE) {
+ /* we are reading a single LF line, e.g. the HTTP headers */
+ rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN);
+ /* We should treat EAGAIN here the same as we do for EOF (brigade is
+ * empty). We do this by returning whatever we have read. This may
+ * or may not be bogus, but is consistent (for now) with EOF logic.
+ */
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ rv = APR_SUCCESS;
+ }
+ return rv;
+ }
+
+ /* ### AP_MODE_PEEK is a horrific name for this mode because we also
+ * eat any CRLFs that we see. That's not the obvious intention of
+ * this mode. Determine whether anyone actually uses this or not. */
+ if (mode == AP_MODE_EATCRLF) {
+ apr_bucket *e;
+ const char *c;
+
+ /* The purpose of this loop is to ignore any CRLF (or LF) at the end
+ * of a request. Many browsers send extra lines at the end of POST
+ * requests. We use the PEEK method to determine if there is more
+ * data on the socket, so that we know if we should delay sending the
+ * end of one request until we have served the second request in a
+ * pipelined situation. We don't want to actually delay sending a
+ * response if the server finds a CRLF (or LF), becuause that doesn't
+ * mean that there is another request, just a blank line.
+ */
+ while (1) {
+ if (APR_BRIGADE_EMPTY(ctx->b))
+ return APR_EOF;
+
+ e = APR_BRIGADE_FIRST(ctx->b);
+
+ rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ);
+
+ if (rv != APR_SUCCESS)
+ return rv;
+
+ c = str;
+ while (c < str + len) {
+ if (*c == APR_ASCII_LF)
+ c++;
+ else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF)
+ c += 2;
+ else
+ return APR_SUCCESS;
+ }
+
+ /* If we reach here, we were a bucket just full of CRLFs, so
+ * just toss the bucket. */
+ /* FIXME: Is this the right thing to do in the core? */
+ apr_bucket_delete(e);
+ }
+ return APR_SUCCESS;
+ }
+
+ /* If mode is EXHAUSTIVE, we want to just read everything until the end
+ * of the brigade, which in this case means the end of the socket.
+ * To do this, we attach the brigade that has currently been setaside to
+ * the brigade that was passed down, and send that brigade back.
+ *
+ * NOTE: This is VERY dangerous to use, and should only be done with
+ * extreme caution. However, the Perchild MPM needs this feature
+ * if it is ever going to work correctly again. With this, the Perchild
+ * MPM can easily request the socket and all data that has been read,
+ * which means that it can pass it to the correct child process.
+ */
+ if (mode == AP_MODE_EXHAUSTIVE) {
+ apr_bucket *e;
+
+ /* Tack on any buckets that were set aside. */
+ APR_BRIGADE_CONCAT(b, ctx->b);
+
+ /* Since we've just added all potential buckets (which will most
+ * likely simply be the socket bucket) we know this is the end,
+ * so tack on an EOS too. */
+ /* We have read until the brigade was empty, so we know that we
+ * must be EOS. */
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ return APR_SUCCESS;
+ }
+
+ /* read up to the amount they specified. */
+ if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE) {
+ apr_bucket *e;
+
+ AP_DEBUG_ASSERT(readbytes > 0);
+
+ e = APR_BRIGADE_FIRST(ctx->b);
+ rv = apr_bucket_read(e, &str, &len, block);
+
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ return APR_SUCCESS;
+ }
+ else if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ else if (block == APR_BLOCK_READ && len == 0) {
+ /* We wanted to read some bytes in blocking mode. We read
+ * 0 bytes. Hence, we now assume we are EOS.
+ *
+ * When we are in normal mode, return an EOS bucket to the
+ * caller.
+ * When we are in speculative mode, leave ctx->b empty, so
+ * that the next call returns an EOS bucket.
+ */
+ apr_bucket_delete(e);
+
+ if (mode == AP_MODE_READBYTES) {
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ }
+ return APR_SUCCESS;
+ }
+
+ /* We can only return at most what we read. */
+ if (len < readbytes) {
+ readbytes = len;
+ }
+
+ rv = apr_brigade_partition(ctx->b, readbytes, &e);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Must do move before CONCAT */
+ brigade_move(ctx->b, ctx->tmpbb, e);
+
+ if (mode == AP_MODE_READBYTES) {
+ APR_BRIGADE_CONCAT(b, ctx->b);
+ }
+ else if (mode == AP_MODE_SPECULATIVE) {
+ apr_bucket *copy_bucket;
+
+ for (e = APR_BRIGADE_FIRST(ctx->b);
+ e != APR_BRIGADE_SENTINEL(ctx->b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ rv = apr_bucket_copy(e, &copy_bucket);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ APR_BRIGADE_INSERT_TAIL(b, copy_bucket);
+ }
+ }
+
+ /* Take what was originally there and place it back on ctx->b */
+ APR_BRIGADE_CONCAT(ctx->b, ctx->tmpbb);
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t writev_it_all(apr_socket_t *s,
+ struct iovec *vec, int nvec,
+ apr_size_t len, apr_size_t *nbytes)
+{
+ apr_size_t bytes_written = 0;
+ apr_status_t rv;
+ apr_size_t n = len;
+ int i = 0;
+
+ *nbytes = 0;
+
+ /* XXX handle checking for non-blocking socket */
+ while (bytes_written != len) {
+ rv = apr_socket_sendv(s, vec + i, nvec - i, &n);
+ *nbytes += n;
+ bytes_written += n;
+ if (rv != APR_SUCCESS)
+ return rv;
+
+ /* If the write did not complete, adjust the iovecs and issue
+ * apr_socket_sendv again
+ */
+ if (bytes_written < len) {
+ /* Skip over the vectors that have already been written */
+ apr_size_t cnt = vec[i].iov_len;
+ while (n >= cnt && i + 1 < nvec) {
+ i++;
+ cnt += vec[i].iov_len;
+ }
+
+ if (n < cnt) {
+ /* Handle partial write of vec i */
+ vec[i].iov_base = (char *) vec[i].iov_base +
+ (vec[i].iov_len - (cnt - n));
+ vec[i].iov_len = cnt -n;
+ }
+ }
+
+ n = len - bytes_written;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* sendfile_it_all()
+ * send the entire file using sendfile()
+ * handle partial writes
+ * return only when all bytes have been sent or an error is encountered.
+ */
+
+#if APR_HAS_SENDFILE
+static apr_status_t sendfile_it_all(core_net_rec *c,
+ apr_file_t *fd,
+ apr_hdtr_t *hdtr,
+ apr_off_t file_offset,
+ apr_size_t file_bytes_left,
+ apr_size_t total_bytes_left,
+ apr_size_t *bytes_sent,
+ apr_int32_t flags)
+{
+ apr_status_t rv;
+#ifdef AP_DEBUG
+ apr_interval_time_t timeout = 0;
+#endif
+
+ AP_DEBUG_ASSERT((apr_socket_timeout_get(c->client_socket, &timeout)
+ == APR_SUCCESS)
+ && timeout > 0); /* socket must be in timeout mode */
+
+ /* Reset the bytes_sent field */
+ *bytes_sent = 0;
+
+ do {
+ apr_size_t tmplen = file_bytes_left;
+
+ rv = apr_socket_sendfile(c->client_socket, fd, hdtr, &file_offset, &tmplen,
+ flags);
+ *bytes_sent += tmplen;
+ total_bytes_left -= tmplen;
+ if (!total_bytes_left || rv != APR_SUCCESS) {
+ return rv; /* normal case & error exit */
+ }
+
+ AP_DEBUG_ASSERT(total_bytes_left > 0 && tmplen > 0);
+
+ /* partial write, oooh noooo...
+ * Skip over any header data which was written
+ */
+ while (tmplen && hdtr->numheaders) {
+ if (tmplen >= hdtr->headers[0].iov_len) {
+ tmplen -= hdtr->headers[0].iov_len;
+ --hdtr->numheaders;
+ ++hdtr->headers;
+ }
+ else {
+ char *iov_base = (char *)hdtr->headers[0].iov_base;
+
+ hdtr->headers[0].iov_len -= tmplen;
+ iov_base += tmplen;
+ hdtr->headers[0].iov_base = iov_base;
+ tmplen = 0;
+ }
+ }
+
+ /* Skip over any file data which was written */
+
+ if (tmplen <= file_bytes_left) {
+ file_offset += tmplen;
+ file_bytes_left -= tmplen;
+ continue;
+ }
+
+ tmplen -= file_bytes_left;
+ file_bytes_left = 0;
+ file_offset = 0;
+
+ /* Skip over any trailer data which was written */
+
+ while (tmplen && hdtr->numtrailers) {
+ if (tmplen >= hdtr->trailers[0].iov_len) {
+ tmplen -= hdtr->trailers[0].iov_len;
+ --hdtr->numtrailers;
+ ++hdtr->trailers;
+ }
+ else {
+ char *iov_base = (char *)hdtr->trailers[0].iov_base;
+
+ hdtr->trailers[0].iov_len -= tmplen;
+ iov_base += tmplen;
+ hdtr->trailers[0].iov_base = iov_base;
+ tmplen = 0;
+ }
+ }
+ } while (1);
+}
+#endif
+
+/*
+ * emulate_sendfile()
+ * Sends the contents of file fd along with header/trailer bytes, if any,
+ * to the network. emulate_sendfile will return only when all the bytes have been
+ * sent (i.e., it handles partial writes) or on a network error condition.
+ */
+static apr_status_t emulate_sendfile(core_net_rec *c, apr_file_t *fd,
+ apr_hdtr_t *hdtr, apr_off_t offset,
+ apr_size_t length, apr_size_t *nbytes)
+{
+ apr_status_t rv = APR_SUCCESS;
+ apr_size_t togo; /* Remaining number of bytes in the file to send */
+ apr_size_t sendlen = 0;
+ apr_size_t bytes_sent;
+ apr_int32_t i;
+ apr_off_t o; /* Track the file offset for partial writes */
+ char buffer[8192];
+
+ *nbytes = 0;
+
+ /* Send the headers
+ * writev_it_all handles partial writes.
+ * XXX: optimization... if headers are less than MIN_WRITE_SIZE, copy
+ * them into buffer
+ */
+ if (hdtr && hdtr->numheaders > 0 ) {
+ for (i = 0; i < hdtr->numheaders; i++) {
+ sendlen += hdtr->headers[i].iov_len;
+ }
+
+ rv = writev_it_all(c->client_socket, hdtr->headers, hdtr->numheaders,
+ sendlen, &bytes_sent);
+ *nbytes += bytes_sent; /* track total bytes sent */
+ }
+
+ /* Seek the file to 'offset' */
+ if (offset >= 0 && rv == APR_SUCCESS) {
+ rv = apr_file_seek(fd, APR_SET, &offset);
+ }
+
+ /* Send the file, making sure to handle partial writes */
+ togo = length;
+ while (rv == APR_SUCCESS && togo) {
+ sendlen = togo > sizeof(buffer) ? sizeof(buffer) : togo;
+ o = 0;
+ rv = apr_file_read(fd, buffer, &sendlen);
+ while (rv == APR_SUCCESS && sendlen) {
+ bytes_sent = sendlen;
+ rv = apr_socket_send(c->client_socket, &buffer[o], &bytes_sent);
+ *nbytes += bytes_sent;
+ if (rv == APR_SUCCESS) {
+ sendlen -= bytes_sent; /* sendlen != bytes_sent ==> partial write */
+ o += bytes_sent; /* o is where we are in the buffer */
+ togo -= bytes_sent; /* track how much of the file we've sent */
+ }
+ }
+ }
+
+ /* Send the trailers
+ * XXX: optimization... if it will fit, send this on the last send in the
+ * loop above
+ */
+ sendlen = 0;
+ if ( rv == APR_SUCCESS && hdtr && hdtr->numtrailers > 0 ) {
+ for (i = 0; i < hdtr->numtrailers; i++) {
+ sendlen += hdtr->trailers[i].iov_len;
+ }
+ rv = writev_it_all(c->client_socket, hdtr->trailers, hdtr->numtrailers,
+ sendlen, &bytes_sent);
+ *nbytes += bytes_sent;
+ }
+
+ return rv;
+}
+
+#ifndef APR_MAX_IOVEC_SIZE
+#define MAX_IOVEC_TO_WRITE 16
+#else
+#if APR_MAX_IOVEC_SIZE > 16
+#define MAX_IOVEC_TO_WRITE 16
+#else
+#define MAX_IOVEC_TO_WRITE APR_MAX_IOVEC_SIZE
+#endif
+#endif
+
+/* Optional function coming from mod_logio, used for logging of output
+ * traffic
+ */
+extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *logio_add_bytes_out;
+
+apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *b)
+{
+ apr_status_t rv;
+ apr_bucket_brigade *more;
+ conn_rec *c = f->c;
+ core_net_rec *net = f->ctx;
+ core_output_filter_ctx_t *ctx = net->out_ctx;
+ apr_read_type_e eblock = APR_NONBLOCK_READ;
+ apr_pool_t *input_pool = b->p;
+
+ if (ctx == NULL) {
+ ctx = apr_pcalloc(c->pool, sizeof(*ctx));
+ net->out_ctx = ctx;
+ }
+
+ /* If we have a saved brigade, concatenate the new brigade to it */
+ if (ctx->b) {
+ APR_BRIGADE_CONCAT(ctx->b, b);
+ b = ctx->b;
+ ctx->b = NULL;
+ }
+
+ /* Perform multiple passes over the brigade, sending batches of output
+ to the connection. */
+ while (b && !APR_BRIGADE_EMPTY(b)) {
+ apr_size_t nbytes = 0;
+ apr_bucket *last_e = NULL; /* initialized for debugging */
+ apr_bucket *e;
+
+ /* one group of iovecs per pass over the brigade */
+ apr_size_t nvec = 0;
+ apr_size_t nvec_trailers = 0;
+ struct iovec vec[MAX_IOVEC_TO_WRITE];
+ struct iovec vec_trailers[MAX_IOVEC_TO_WRITE];
+
+ /* one file per pass over the brigade */
+ apr_file_t *fd = NULL;
+ apr_size_t flen = 0;
+ apr_off_t foffset = 0;
+
+ /* keep track of buckets that we've concatenated
+ * to avoid small writes
+ */
+ apr_bucket *last_merged_bucket = NULL;
+
+ /* tail of brigade if we need another pass */
+ more = NULL;
+
+ /* Iterate over the brigade: collect iovecs and/or a file */
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ /* keep track of the last bucket processed */
+ last_e = e;
+ if (APR_BUCKET_IS_EOS(e) || AP_BUCKET_IS_EOC(e)) {
+ break;
+ }
+ else if (APR_BUCKET_IS_FLUSH(e)) {
+ if (e != APR_BRIGADE_LAST(b)) {
+ more = apr_brigade_split(b, APR_BUCKET_NEXT(e));
+ }
+ break;
+ }
+
+ /* It doesn't make any sense to use sendfile for a file bucket
+ * that represents 10 bytes.
+ */
+ else if (APR_BUCKET_IS_FILE(e)
+ && (e->length >= AP_MIN_SENDFILE_BYTES)) {
+ apr_bucket_file *a = e->data;
+
+ /* We can't handle more than one file bucket at a time
+ * so we split here and send the file we have already
+ * found.
+ */
+ if (fd) {
+ more = apr_brigade_split(b, e);
+ break;
+ }
+
+ fd = a->fd;
+ flen = e->length;
+ foffset = e->start;
+ }
+ else {
+ const char *str;
+ apr_size_t n;
+
+ rv = apr_bucket_read(e, &str, &n, eblock);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* send what we have so far since we shouldn't expect more
+ * output for a while... next time we read, block
+ */
+ more = apr_brigade_split(b, e);
+ eblock = APR_BLOCK_READ;
+ break;
+ }
+ eblock = APR_NONBLOCK_READ;
+ if (n) {
+ if (!fd) {
+ if (nvec == MAX_IOVEC_TO_WRITE) {
+ /* woah! too many. buffer them up, for use later. */
+ apr_bucket *temp, *next;
+ apr_bucket_brigade *temp_brig;
+
+ if (nbytes >= AP_MIN_BYTES_TO_WRITE) {
+ /* We have enough data in the iovec
+ * to justify doing a writev
+ */
+ more = apr_brigade_split(b, e);
+ break;
+ }
+
+ /* Create a temporary brigade as a means
+ * of concatenating a bunch of buckets together
+ */
+ if (last_merged_bucket) {
+ /* If we've concatenated together small
+ * buckets already in a previous pass,
+ * the initial buckets in this brigade
+ * are heap buckets that may have extra
+ * space left in them (because they
+ * were created by apr_brigade_write()).
+ * We can take advantage of this by
+ * building the new temp brigade out of
+ * these buckets, so that the content
+ * in them doesn't have to be copied again.
+ */
+ apr_bucket_brigade *bb;
+ bb = apr_brigade_split(b,
+ APR_BUCKET_NEXT(last_merged_bucket));
+ temp_brig = b;
+ b = bb;
+ }
+ else {
+ temp_brig = apr_brigade_create(f->c->pool,
+ f->c->bucket_alloc);
+ }
+
+ temp = APR_BRIGADE_FIRST(b);
+ while (temp != e) {
+ apr_bucket *d;
+ rv = apr_bucket_read(temp, &str, &n, APR_BLOCK_READ);
+ apr_brigade_write(temp_brig, NULL, NULL, str, n);
+ d = temp;
+ temp = APR_BUCKET_NEXT(temp);
+ apr_bucket_delete(d);
+ }
+
+ nvec = 0;
+ nbytes = 0;
+ temp = APR_BRIGADE_FIRST(temp_brig);
+ APR_BUCKET_REMOVE(temp);
+ APR_BRIGADE_INSERT_HEAD(b, temp);
+ apr_bucket_read(temp, &str, &n, APR_BLOCK_READ);
+ vec[nvec].iov_base = (char*) str;
+ vec[nvec].iov_len = n;
+ nvec++;
+
+ /* Just in case the temporary brigade has
+ * multiple buckets, recover the rest of
+ * them and put them in the brigade that
+ * we're sending.
+ */
+ for (next = APR_BRIGADE_FIRST(temp_brig);
+ next != APR_BRIGADE_SENTINEL(temp_brig);
+ next = APR_BRIGADE_FIRST(temp_brig)) {
+ APR_BUCKET_REMOVE(next);
+ APR_BUCKET_INSERT_AFTER(temp, next);
+ temp = next;
+ apr_bucket_read(next, &str, &n,
+ APR_BLOCK_READ);
+ vec[nvec].iov_base = (char*) str;
+ vec[nvec].iov_len = n;
+ nvec++;
+ }
+
+ apr_brigade_destroy(temp_brig);
+
+ last_merged_bucket = temp;
+ e = temp;
+ last_e = e;
+ }
+ else {
+ vec[nvec].iov_base = (char*) str;
+ vec[nvec].iov_len = n;
+ nvec++;
+ }
+ }
+ else {
+ /* The bucket is a trailer to a file bucket */
+
+ if (nvec_trailers == MAX_IOVEC_TO_WRITE) {
+ /* woah! too many. stop now. */
+ more = apr_brigade_split(b, e);
+ break;
+ }
+
+ vec_trailers[nvec_trailers].iov_base = (char*) str;
+ vec_trailers[nvec_trailers].iov_len = n;
+ nvec_trailers++;
+ }
+
+ nbytes += n;
+ }
+ }
+ }
+
+
+ /* Completed iterating over the brigade, now determine if we want
+ * to buffer the brigade or send the brigade out on the network.
+ *
+ * Save if we haven't accumulated enough bytes to send, the connection
+ * is not about to be closed, and:
+ *
+ * 1) we didn't see a file, we don't have more passes over the
+ * brigade to perform, AND we didn't stop at a FLUSH bucket.
+ * (IOW, we will save plain old bytes such as HTTP headers)
+ * or
+ * 2) we hit the EOS and have a keep-alive connection
+ * (IOW, this response is a bit more complex, but we save it
+ * with the hope of concatenating with another response)
+ */
+ if (nbytes + flen < AP_MIN_BYTES_TO_WRITE
+ && !AP_BUCKET_IS_EOC(last_e)
+ && ((!fd && !more && !APR_BUCKET_IS_FLUSH(last_e))
+ || (APR_BUCKET_IS_EOS(last_e)
+ && c->keepalive == AP_CONN_KEEPALIVE))) {
+
+ /* NEVER save an EOS in here. If we are saving a brigade with
+ * an EOS bucket, then we are doing keepalive connections, and
+ * we want to process to second request fully.
+ */
+ if (APR_BUCKET_IS_EOS(last_e)) {
+ apr_bucket *bucket;
+ int file_bucket_saved = 0;
+ apr_bucket_delete(last_e);
+ for (bucket = APR_BRIGADE_FIRST(b);
+ bucket != APR_BRIGADE_SENTINEL(b);
+ bucket = APR_BUCKET_NEXT(bucket)) {
+
+ /* Do a read on each bucket to pull in the
+ * data from pipe and socket buckets, so
+ * that we don't leave their file descriptors
+ * open indefinitely. Do the same for file
+ * buckets, with one exception: allow the
+ * first file bucket in the brigade to remain
+ * a file bucket, so that we don't end up
+ * doing an mmap+memcpy every time a client
+ * requests a <8KB file over a keepalive
+ * connection.
+ */
+ if (APR_BUCKET_IS_FILE(bucket) && !file_bucket_saved) {
+ file_bucket_saved = 1;
+ }
+ else {
+ const char *buf;
+ apr_size_t len = 0;
+ rv = apr_bucket_read(bucket, &buf, &len,
+ APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv,
+ c, "core_output_filter:"
+ " Error reading from bucket.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ }
+ if (!ctx->deferred_write_pool) {
+ apr_pool_create(&ctx->deferred_write_pool, c->pool);
+ apr_pool_tag(ctx->deferred_write_pool, "deferred_write");
+ }
+ ap_save_brigade(f, &ctx->b, &b, ctx->deferred_write_pool);
+
+ return APR_SUCCESS;
+ }
+
+ if (fd) {
+ apr_hdtr_t hdtr;
+ apr_size_t bytes_sent;
+
+#if APR_HAS_SENDFILE
+ apr_int32_t flags = 0;
+#endif
+
+ memset(&hdtr, '\0', sizeof(hdtr));
+ if (nvec) {
+ hdtr.numheaders = nvec;
+ hdtr.headers = vec;
+ }
+
+ if (nvec_trailers) {
+ hdtr.numtrailers = nvec_trailers;
+ hdtr.trailers = vec_trailers;
+ }
+
+#if APR_HAS_SENDFILE
+ if (apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) {
+
+ if (c->keepalive == AP_CONN_CLOSE && APR_BUCKET_IS_EOS(last_e)) {
+ /* Prepare the socket to be reused */
+ flags |= APR_SENDFILE_DISCONNECT_SOCKET;
+ }
+
+ rv = sendfile_it_all(net, /* the network information */
+ fd, /* the file to send */
+ &hdtr, /* header and trailer iovecs */
+ foffset, /* offset in the file to begin
+ sending from */
+ flen, /* length of file */
+ nbytes + flen, /* total length including
+ headers */
+ &bytes_sent, /* how many bytes were
+ sent */
+ flags); /* apr_sendfile flags */
+ }
+ else
+#endif
+ {
+ rv = emulate_sendfile(net, fd, &hdtr, foffset, flen,
+ &bytes_sent);
+ }
+
+ if (logio_add_bytes_out && bytes_sent > 0)
+ logio_add_bytes_out(c, bytes_sent);
+
+ fd = NULL;
+ }
+ else {
+ apr_size_t bytes_sent;
+
+ rv = writev_it_all(net->client_socket,
+ vec, nvec,
+ nbytes, &bytes_sent);
+
+ if (logio_add_bytes_out && bytes_sent > 0)
+ logio_add_bytes_out(c, bytes_sent);
+ }
+
+ apr_brigade_destroy(b);
+
+ /* drive cleanups for resources which were set aside
+ * this may occur before or after termination of the request which
+ * created the resource
+ */
+ if (ctx->deferred_write_pool) {
+ if (more && more->p == ctx->deferred_write_pool) {
+ /* "more" belongs to the deferred_write_pool,
+ * which is about to be cleared.
+ */
+ if (APR_BRIGADE_EMPTY(more)) {
+ more = NULL;
+ }
+ else {
+ /* uh oh... change more's lifetime
+ * to the input brigade's lifetime
+ */
+ apr_bucket_brigade *tmp_more = more;
+ more = NULL;
+ ap_save_brigade(f, &more, &tmp_more, input_pool);
+ }
+ }
+ apr_pool_clear(ctx->deferred_write_pool);
+ }
+
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c,
+ "core_output_filter: writing data to the network");
+
+ if (more)
+ apr_brigade_destroy(more);
+
+ /* No need to check for SUCCESS, we did that above. */
+ if (!APR_STATUS_IS_EAGAIN(rv)) {
+ c->aborted = 1;
+ }
+
+ /* The client has aborted, but the request was successful. We
+ * will report success, and leave it to the access and error
+ * logs to note that the connection was aborted.
+ */
+ return APR_SUCCESS;
+ }
+
+ b = more;
+ more = NULL;
+ } /* end while () */
+
+ return APR_SUCCESS;
+}
diff --git a/server/eoc_bucket.c b/server/eoc_bucket.c
new file mode 100644
index 00000000..42b4e518
--- /dev/null
+++ b/server/eoc_bucket.c
@@ -0,0 +1,55 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "httpd.h"
+#include "http_connection.h"
+
+static apr_status_t eoc_bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ *str = NULL;
+ *len = 0;
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(apr_bucket *) ap_bucket_eoc_make(apr_bucket *b)
+{
+ b->length = 0;
+ b->start = 0;
+ b->data = NULL;
+ b->type = &ap_bucket_type_eoc;
+
+ return b;
+}
+
+AP_DECLARE(apr_bucket *) ap_bucket_eoc_create(apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ return ap_bucket_eoc_make(b);
+}
+
+AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_eoc = {
+ "EOC", 5, APR_BUCKET_METADATA,
+ apr_bucket_destroy_noop,
+ eoc_bucket_read,
+ apr_bucket_setaside_noop,
+ apr_bucket_split_notimpl,
+ apr_bucket_simple_copy
+};
diff --git a/server/error_bucket.c b/server/error_bucket.c
new file mode 100644
index 00000000..d113c171
--- /dev/null
+++ b/server/error_bucket.c
@@ -0,0 +1,74 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "http_protocol.h"
+#include "apr_buckets.h"
+#include "apr_strings.h"
+#if APR_HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+static apr_status_t error_bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ *str = NULL;
+ *len = 0;
+ return APR_SUCCESS;
+}
+
+static void error_bucket_destroy(void *data)
+{
+ ap_bucket_error *h = data;
+
+ if (apr_bucket_shared_destroy(h)) {
+ apr_bucket_free(h);
+ }
+}
+
+AP_DECLARE(apr_bucket *) ap_bucket_error_make(apr_bucket *b, int error,
+ const char *buf, apr_pool_t *p)
+{
+ ap_bucket_error *h;
+
+ h = apr_bucket_alloc(sizeof(*h), b->list);
+ h->status = error;
+ h->data = (buf) ? apr_pstrdup(p, buf) : NULL;
+
+ b = apr_bucket_shared_make(b, h, 0, 0);
+ b->type = &ap_bucket_type_error;
+ return b;
+}
+
+AP_DECLARE(apr_bucket *) ap_bucket_error_create(int error, const char *buf,
+ apr_pool_t *p,
+ apr_bucket_alloc_t *list)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ return ap_bucket_error_make(b, error, buf, p);
+}
+
+AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_error = {
+ "ERROR", 5, APR_BUCKET_METADATA,
+ error_bucket_destroy,
+ error_bucket_read,
+ apr_bucket_setaside_notimpl,
+ apr_bucket_split_notimpl,
+ apr_bucket_shared_copy
+};
diff --git a/server/gen_test_char.c b/server/gen_test_char.c
new file mode 100644
index 00000000..587583ae
--- /dev/null
+++ b/server/gen_test_char.c
@@ -0,0 +1,121 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_lib.h"
+
+#if APR_HAVE_STDIO_H
+#include <stdio.h>
+#endif
+#if APR_HAVE_STRING_H
+#include <string.h>
+#endif
+
+/* A bunch of functions in util.c scan strings looking for certain characters.
+ * To make that more efficient we encode a lookup table.
+ */
+#define T_ESCAPE_SHELL_CMD (0x01)
+#define T_ESCAPE_PATH_SEGMENT (0x02)
+#define T_OS_ESCAPE_PATH (0x04)
+#define T_HTTP_TOKEN_STOP (0x08)
+#define T_ESCAPE_LOGITEM (0x10)
+#define T_ESCAPE_FORENSIC (0x20)
+
+int main(int argc, char *argv[])
+{
+ unsigned c;
+ unsigned char flags;
+
+ printf("/* this file is automatically generated by gen_test_char, "
+ "do not edit */\n"
+ "#define T_ESCAPE_SHELL_CMD (%u)\n"
+ "#define T_ESCAPE_PATH_SEGMENT (%u)\n"
+ "#define T_OS_ESCAPE_PATH (%u)\n"
+ "#define T_HTTP_TOKEN_STOP (%u)\n"
+ "#define T_ESCAPE_LOGITEM (%u)\n"
+ "#define T_ESCAPE_FORENSIC (%u)\n"
+ "\n"
+ "static const unsigned char test_char_table[256] = {",
+ T_ESCAPE_SHELL_CMD,
+ T_ESCAPE_PATH_SEGMENT,
+ T_OS_ESCAPE_PATH,
+ T_HTTP_TOKEN_STOP,
+ T_ESCAPE_LOGITEM,
+ T_ESCAPE_FORENSIC);
+
+ for (c = 0; c < 256; ++c) {
+ flags = 0;
+ if (c % 20 == 0)
+ printf("\n ");
+
+ /* escape_shell_cmd */
+#if defined(WIN32) || defined(OS2)
+ /* Win32/OS2 have many of the same vulnerable characters
+ * as Unix sh, plus the carriage return and percent char.
+ * The proper escaping of these characters varies from unix
+ * since Win32/OS2 use carets or doubled-double quotes,
+ * and neither lf nor cr can be escaped. We escape unix
+ * specific as well, to assure that cross-compiled unix
+ * applications behave similiarly when invoked on win32/os2.
+ *
+ * Rem please keep in-sync with apr's list in win32/filesys.c
+ */
+ if (c && strchr("&;`'\"|*?~<>^()[]{}$\\\n\r%", c)) {
+ flags |= T_ESCAPE_SHELL_CMD;
+ }
+#else
+ if (c && strchr("&;`'\"|*?~<>^()[]{}$\\\n", c)) {
+ flags |= T_ESCAPE_SHELL_CMD;
+ }
+#endif
+
+ if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=~", c)) {
+ flags |= T_ESCAPE_PATH_SEGMENT;
+ }
+
+ if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) {
+ flags |= T_OS_ESCAPE_PATH;
+ }
+
+ /* these are the "tspecials" from RFC2068 */
+ if (c && (apr_iscntrl(c) || strchr(" \t()<>@,;:\\/[]?={}", c))) {
+ flags |= T_HTTP_TOKEN_STOP;
+ }
+
+ /* For logging, escape all control characters,
+ * double quotes (because they delimit the request in the log file)
+ * backslashes (because we use backslash for escaping)
+ * and 8-bit chars with the high bit set
+ */
+ if (c && (!apr_isprint(c) || c == '"' || c == '\\' || apr_iscntrl(c))) {
+ flags |= T_ESCAPE_LOGITEM;
+ }
+
+ /* For forensic logging, escape all control characters, top bit set,
+ * :, | (used as delimiters) and % (used for escaping).
+ */
+ if (!apr_isprint(c) || c == ':' || c == '|' || c == '%'
+ || apr_iscntrl(c) || !c) {
+ flags |= T_ESCAPE_FORENSIC;
+ }
+
+ printf("%u%c", flags, (c < 255) ? ',' : ' ');
+ }
+
+ printf("\n};\n");
+
+ return 0;
+}
diff --git a/server/gen_test_char.dsp b/server/gen_test_char.dsp
new file mode 100644
index 00000000..cc0b943c
--- /dev/null
+++ b/server/gen_test_char.dsp
@@ -0,0 +1,94 @@
+# Microsoft Developer Studio Project File - Name="gen_test_char" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=gen_test_char - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "gen_test_char.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "gen_test_char.mak" CFG="gen_test_char - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "gen_test_char - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "gen_test_char - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "gen_test_char - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ""
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir ""
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Fd"Release\gen_test_char" /FD /c
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x809 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:console /pdb:"Release\gen_test_char.pdb"
+# SUBTRACT BASE LINK32 /pdb:none
+# ADD LINK32 kernel32.lib /nologo /subsystem:console /pdb:"Release\gen_test_char.pdb" /opt:ref
+# SUBTRACT LINK32 /pdb:none
+
+!ELSEIF "$(CFG)" == "gen_test_char - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir ""
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir ""
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Fd"Debug\gen_test_char" /FD /c
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x809 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"Debug\gen_test_char.pdb" /debug /pdbtype:sept
+# SUBTRACT BASE LINK32 /pdb:none
+# ADD LINK32 kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"Debug\gen_test_char.pdb" /debug
+# SUBTRACT LINK32 /pdb:none
+
+!ENDIF
+
+# Begin Target
+
+# Name "gen_test_char - Win32 Release"
+# Name "gen_test_char - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\gen_test_char.c
+# End Source File
+# End Target
+# End Project
diff --git a/server/listen.c b/server/listen.c
new file mode 100644
index 00000000..36ad50b3
--- /dev/null
+++ b/server/listen.c
@@ -0,0 +1,683 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_network_io.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "ap_listen.h"
+#include "http_log.h"
+#include "mpm.h"
+#include "mpm_common.h"
+
+AP_DECLARE_DATA ap_listen_rec *ap_listeners = NULL;
+
+static ap_listen_rec *old_listeners;
+static int ap_listenbacklog;
+static int send_buffer_size;
+static int receive_buffer_size;
+
+/* TODO: make_sock is just begging and screaming for APR abstraction */
+static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server)
+{
+ apr_socket_t *s = server->sd;
+ int one = 1;
+#if APR_HAVE_IPV6
+#ifdef AP_ENABLE_V4_MAPPED
+ int v6only_setting = 0;
+#else
+ int v6only_setting = 1;
+#endif
+#endif
+ apr_status_t stat;
+
+#ifndef WIN32
+ stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one);
+ if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
+ "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)",
+ server->bind_addr);
+ apr_socket_close(s);
+ return stat;
+ }
+#endif
+
+ stat = apr_socket_opt_set(s, APR_SO_KEEPALIVE, one);
+ if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
+ "make_sock: for address %pI, apr_socket_opt_set: (SO_KEEPALIVE)",
+ server->bind_addr);
+ apr_socket_close(s);
+ return stat;
+ }
+
+#if APR_HAVE_IPV6
+ if (server->bind_addr->family == APR_INET6) {
+ stat = apr_socket_opt_set(s, APR_IPV6_V6ONLY, v6only_setting);
+ if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
+ "make_sock: for address %pI, apr_socket_opt_set: "
+ "(IPV6_V6ONLY)",
+ server->bind_addr);
+ apr_socket_close(s);
+ return stat;
+ }
+ }
+#endif
+
+ /*
+ * To send data over high bandwidth-delay connections at full
+ * speed we must force the TCP window to open wide enough to keep the
+ * pipe full. The default window size on many systems
+ * is only 4kB. Cross-country WAN connections of 100ms
+ * at 1Mb/s are not impossible for well connected sites.
+ * If we assume 100ms cross-country latency,
+ * a 4kB buffer limits throughput to 40kB/s.
+ *
+ * To avoid this problem I've added the SendBufferSize directive
+ * to allow the web master to configure send buffer size.
+ *
+ * The trade-off of larger buffers is that more kernel memory
+ * is consumed. YMMV, know your customers and your network!
+ *
+ * -John Heidemann <johnh@isi.edu> 25-Oct-96
+ *
+ * If no size is specified, use the kernel default.
+ */
+ if (send_buffer_size) {
+ stat = apr_socket_opt_set(s, APR_SO_SNDBUF, send_buffer_size);
+ if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p,
+ "make_sock: failed to set SendBufferSize for "
+ "address %pI, using default",
+ server->bind_addr);
+ /* not a fatal error */
+ }
+ }
+ if (receive_buffer_size) {
+ stat = apr_socket_opt_set(s, APR_SO_RCVBUF, receive_buffer_size);
+ if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p,
+ "make_sock: failed to set ReceiveBufferSize for "
+ "address %pI, using default",
+ server->bind_addr);
+ /* not a fatal error */
+ }
+ }
+
+#if APR_TCP_NODELAY_INHERITED
+ ap_sock_disable_nagle(s);
+#endif
+
+ if ((stat = apr_socket_bind(s, server->bind_addr)) != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p,
+ "make_sock: could not bind to address %pI",
+ server->bind_addr);
+ apr_socket_close(s);
+ return stat;
+ }
+
+ if ((stat = apr_socket_listen(s, ap_listenbacklog)) != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, stat, p,
+ "make_sock: unable to listen for connections "
+ "on address %pI",
+ server->bind_addr);
+ apr_socket_close(s);
+ return stat;
+ }
+
+#ifdef WIN32
+ /* I seriously doubt that this would work on Unix; I have doubts that
+ * it entirely solves the problem on Win32. However, since setting
+ * reuseaddr on the listener -prior- to binding the socket has allowed
+ * us to attach to the same port as an already running instance of
+ * Apache, or even another web server, we cannot identify that this
+ * port was exclusively granted to this instance of Apache.
+ *
+ * So set reuseaddr, but do not attempt to do so until we have the
+ * parent listeners successfully bound.
+ */
+ stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one);
+ if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
+ "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)",
+ server->bind_addr);
+ apr_socket_close(s);
+ return stat;
+ }
+#endif
+
+ server->sd = s;
+ server->active = 1;
+
+#ifdef MPM_ACCEPT_FUNC
+ server->accept_func = MPM_ACCEPT_FUNC;
+#else
+ server->accept_func = NULL;
+#endif
+
+ return APR_SUCCESS;
+}
+
+static const char* find_accf_name(server_rec *s, const char *proto)
+{
+ const char* accf;
+ core_server_config *conf = ap_get_module_config(s->module_config,
+ &core_module);
+ if (!proto) {
+ return NULL;
+ }
+
+ accf = apr_table_get(conf->accf_map, proto);
+
+ if (accf && !strcmp("none", accf)) {
+ return NULL;
+ }
+
+ return accf;
+}
+
+static void ap_apply_accept_filter(apr_pool_t *p, ap_listen_rec *lis,
+ server_rec *server)
+{
+ apr_socket_t *s = lis->sd;
+ const char *accf;
+ apr_status_t rv;
+ const char *proto;
+
+ proto = lis->protocol;
+
+ if (!proto) {
+ proto = ap_get_server_protocol(server);
+ }
+
+
+ accf = find_accf_name(server, proto);
+
+ if (accf) {
+#if APR_HAS_SO_ACCEPTFILTER
+ rv = apr_socket_accept_filter(s, apr_pstrdup(p, accf),
+ apr_pstrdup(p,""));
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p,
+ "Failed to enable the '%s' Accept Filter",
+ accf);
+ }
+#else
+#ifdef APR_TCP_DEFER_ACCEPT
+ rv = apr_socket_opt_set(s, APR_TCP_DEFER_ACCEPT, 1);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p,
+ "Failed to enable APR_TCP_DEFER_ACCEPT");
+ }
+#endif
+#endif
+ }
+}
+
+static apr_status_t close_listeners_on_exec(void *v)
+{
+ ap_close_listeners();
+ return APR_SUCCESS;
+}
+
+static const char *alloc_listener(process_rec *process, char *addr,
+ apr_port_t port, const char* proto)
+{
+ ap_listen_rec **walk, *last;
+ apr_status_t status;
+ apr_sockaddr_t *sa;
+ int found_listener = 0;
+
+ /* see if we've got an old listener for this address:port */
+ for (walk = &old_listeners; *walk;) {
+ sa = (*walk)->bind_addr;
+ /* Some listeners are not real so they will not have a bind_addr. */
+ if (sa) {
+ ap_listen_rec *new;
+ apr_port_t oldport;
+
+ oldport = sa->port;
+ /* If both ports are equivalent, then if their names are equivalent,
+ * then we will re-use the existing record.
+ */
+ if (port == oldport &&
+ ((!addr && !sa->hostname) ||
+ ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) {
+ new = *walk;
+ *walk = new->next;
+ new->next = ap_listeners;
+ ap_listeners = new;
+ found_listener = 1;
+ continue;
+ }
+ }
+
+ walk = &(*walk)->next;
+ }
+
+ if (found_listener) {
+ return NULL;
+ }
+
+ if ((status = apr_sockaddr_info_get(&sa, addr, APR_UNSPEC, port, 0,
+ process->pool))
+ != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool,
+ "alloc_listener: failed to set up sockaddr for %s",
+ addr);
+ return "Listen setup failed";
+ }
+
+ /* Initialize to our last configured ap_listener. */
+ last = ap_listeners;
+ while (last && last->next) {
+ last = last->next;
+ }
+
+ while (sa) {
+ ap_listen_rec *new;
+
+ /* this has to survive restarts */
+ new = apr_palloc(process->pool, sizeof(ap_listen_rec));
+ new->active = 0;
+ new->next = 0;
+ new->bind_addr = sa;
+ new->protocol = apr_pstrdup(process->pool, proto);
+
+ /* Go to the next sockaddr. */
+ sa = sa->next;
+
+ status = apr_socket_create(&new->sd, new->bind_addr->family,
+ SOCK_STREAM, 0, process->pool);
+
+#if APR_HAVE_IPV6
+ /* What could happen is that we got an IPv6 address, but this system
+ * doesn't actually support IPv6. Try the next address.
+ */
+ if (status != APR_SUCCESS && !addr &&
+ new->bind_addr->family == APR_INET6) {
+ continue;
+ }
+#endif
+ if (status != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool,
+ "alloc_listener: failed to get a socket for %s",
+ addr);
+ return "Listen setup failed";
+ }
+
+ /* We need to preserve the order returned by getaddrinfo() */
+ if (last == NULL) {
+ ap_listeners = last = new;
+ } else {
+ last->next = new;
+ last = new;
+ }
+ }
+
+ return NULL;
+}
+/* Evaluates to true if the (apr_sockaddr_t *) addr argument is the
+ * IPv4 match-any-address, 0.0.0.0. */
+#define IS_INADDR_ANY(addr) ((addr)->family == APR_INET \
+ && (addr)->sa.sin.sin_addr.s_addr == INADDR_ANY)
+
+/* Evaluates to true if the (apr_sockaddr_t *) addr argument is the
+ * IPv6 match-any-address, [::]. */
+#define IS_IN6ADDR_ANY(addr) ((addr)->family == APR_INET6 \
+ && IN6_IS_ADDR_UNSPECIFIED(&(addr)->sa.sin6.sin6_addr))
+
+/**
+ * Create, open, listen, and bind all sockets.
+ * @param process The process record for the currently running server
+ * @return The number of open sockets
+ */
+static int open_listeners(apr_pool_t *pool)
+{
+ ap_listen_rec *lr;
+ ap_listen_rec *next;
+ ap_listen_rec *previous;
+ int num_open;
+ const char *userdata_key = "ap_open_listeners";
+ void *data;
+
+ /* Don't allocate a default listener. If we need to listen to a
+ * port, then the user needs to have a Listen directive in their
+ * config file.
+ */
+ num_open = 0;
+ previous = NULL;
+ for (lr = ap_listeners; lr; previous = lr, lr = lr->next) {
+ if (lr->active) {
+ ++num_open;
+ }
+ else {
+#if APR_HAVE_IPV6
+ int v6only_setting;
+
+ /* If we have the unspecified IPv4 address (0.0.0.0) and
+ * the unspecified IPv6 address (::) is next, we need to
+ * swap the order of these in the list. We always try to
+ * bind to IPv6 first, then IPv4, since an IPv6 socket
+ * might be able to receive IPv4 packets if V6ONLY is not
+ * enabled, but never the other way around. */
+ if (lr->next != NULL
+ && IS_INADDR_ANY(lr->bind_addr)
+ && lr->bind_addr->port == lr->next->bind_addr->port
+ && IS_IN6ADDR_ANY(lr->next->bind_addr)) {
+ /* Exchange lr and lr->next */
+ next = lr->next;
+ lr->next = next->next;
+ next->next = lr;
+ if (previous) {
+ previous->next = next;
+ }
+ else {
+ ap_listeners = next;
+ }
+ lr = next;
+ }
+
+ /* If we are trying to bind to 0.0.0.0 and the previous listener
+ * was :: on the same port and in turn that socket does not have
+ * the IPV6_V6ONLY flag set; we must skip the current attempt to
+ * listen (which would generate an error). IPv4 will be handled
+ * on the established IPv6 socket.
+ */
+ if (previous != NULL
+ && IS_INADDR_ANY(lr->bind_addr)
+ && lr->bind_addr->port == previous->bind_addr->port
+ && IS_IN6ADDR_ANY(previous->bind_addr)
+ && apr_socket_opt_get(previous->sd, APR_IPV6_V6ONLY,
+ &v6only_setting) == APR_SUCCESS
+ && v6only_setting == 0) {
+
+ /* Remove the current listener from the list */
+ previous->next = lr->next;
+ continue;
+ }
+#endif
+ if (make_sock(pool, lr) == APR_SUCCESS) {
+ ++num_open;
+ lr->active = 1;
+ }
+ else {
+#if APR_HAVE_IPV6
+ /* If we tried to bind to ::, and the next listener is
+ * on 0.0.0.0 with the same port, don't give a fatal
+ * error. The user will still get a warning from make_sock
+ * though.
+ */
+ if (lr->next != NULL
+ && IS_IN6ADDR_ANY(lr->bind_addr)
+ && lr->bind_addr->port == lr->next->bind_addr->port
+ && IS_INADDR_ANY(lr->next->bind_addr)) {
+
+ /* Remove the current listener from the list */
+ if (previous) {
+ previous->next = lr->next;
+ }
+ else {
+ ap_listeners = lr->next;
+ }
+
+ /* Although we've removed ourselves from the list,
+ * we need to make sure that the next iteration won't
+ * consider "previous" a working IPv6 '::' socket.
+ * Changing the family is enough to make sure the
+ * conditions before make_sock() fail.
+ */
+ lr->bind_addr->family = AF_INET;
+
+ continue;
+ }
+#endif
+ /* fatal error */
+ return -1;
+ }
+ }
+ }
+
+ /* close the old listeners */
+ for (lr = old_listeners; lr; lr = next) {
+ apr_socket_close(lr->sd);
+ lr->active = 0;
+ next = lr->next;
+ }
+ old_listeners = NULL;
+
+#if AP_NONBLOCK_WHEN_MULTI_LISTEN
+ /* if multiple listening sockets, make them non-blocking so that
+ * if select()/poll() reports readability for a reset connection that
+ * is already forgotten about by the time we call accept, we won't
+ * be hung until another connection arrives on that port
+ */
+ if (ap_listeners && ap_listeners->next) {
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ apr_status_t status;
+
+ status = apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, 1);
+ if (status != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, pool,
+ "unable to make listening socket non-blocking");
+ return -1;
+ }
+ }
+ }
+#endif /* AP_NONBLOCK_WHEN_MULTI_LISTEN */
+
+ /* we come through here on both passes of the open logs phase
+ * only register the cleanup once... otherwise we try to close
+ * listening sockets twice when cleaning up prior to exec
+ */
+ apr_pool_userdata_get(&data, userdata_key, pool);
+ if (!data) {
+ apr_pool_userdata_set((const void *)1, userdata_key,
+ apr_pool_cleanup_null, pool);
+ apr_pool_cleanup_register(pool, NULL, apr_pool_cleanup_null,
+ close_listeners_on_exec);
+ }
+
+ return num_open ? 0 : -1;
+}
+
+AP_DECLARE(int) ap_setup_listeners(server_rec *s)
+{
+ server_rec *ls;
+ server_addr_rec *addr;
+ ap_listen_rec *lr;
+ int num_listeners = 0;
+ const char* proto;
+ int found;
+
+ for (ls = s; ls; ls = ls->next) {
+ proto = ap_get_server_protocol(ls);
+ if (!proto) {
+ found = 0;
+ /* No protocol was set for this vhost,
+ * use the default for this listener.
+ */
+ for (addr = ls->addrs; addr && !found; addr = addr->next) {
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) &&
+ lr->bind_addr->port == addr->host_port) {
+ ap_set_server_protocol(ls, lr->protocol);
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ /* TODO: set protocol defaults per-Port, eg 25=smtp */
+ ap_set_server_protocol(ls, "http");
+ }
+ }
+ }
+
+ if (open_listeners(s->process->pool)) {
+ return 0;
+ }
+
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ num_listeners++;
+ found = 0;
+ for (ls = s; ls && !found; ls = ls->next) {
+ for (addr = ls->addrs; addr && !found; addr = addr->next) {
+ if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) &&
+ lr->bind_addr->port == addr->host_port) {
+ found = 1;
+ ap_apply_accept_filter(s->process->pool, lr, ls);
+ }
+ }
+ }
+
+ if (!found) {
+ ap_apply_accept_filter(s->process->pool, lr, s);
+ }
+ }
+
+ return num_listeners;
+}
+
+AP_DECLARE_NONSTD(void) ap_close_listeners(void)
+{
+ ap_listen_rec *lr;
+
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ apr_socket_close(lr->sd);
+ lr->active = 0;
+ }
+}
+
+AP_DECLARE(void) ap_listen_pre_config(void)
+{
+ old_listeners = ap_listeners;
+ ap_listeners = NULL;
+ ap_listenbacklog = DEFAULT_LISTENBACKLOG;
+}
+
+
+AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy,
+ int argc, char *const argv[])
+{
+ char *host, *scope_id, *proto;
+ apr_port_t port;
+ apr_status_t rv;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (argc < 1 || argc > 2) {
+ return "Listen requires 1 or 2 arguments.";
+ }
+
+ rv = apr_parse_addr_port(&host, &scope_id, &port, argv[0], cmd->pool);
+ if (rv != APR_SUCCESS) {
+ return "Invalid address or port";
+ }
+
+ if (host && !strcmp(host, "*")) {
+ host = NULL;
+ }
+
+ if (scope_id) {
+ /* XXX scope id support is useful with link-local IPv6 addresses */
+ return "Scope id is not supported";
+ }
+
+ if (!port) {
+ return "Port must be specified";
+ }
+
+ if (argc != 2) {
+ proto = "http";
+ }
+ else {
+ proto = apr_pstrdup(cmd->pool, argv[1]);
+ ap_str_tolower(proto);
+ }
+
+ return alloc_listener(cmd->server->process, host, port, proto);
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_listenbacklog(cmd_parms *cmd,
+ void *dummy,
+ const char *arg)
+{
+ int b;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ b = atoi(arg);
+ if (b < 1) {
+ return "ListenBacklog must be > 0";
+ }
+
+ ap_listenbacklog = b;
+ return NULL;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_send_buffer_size(cmd_parms *cmd,
+ void *dummy,
+ const char *arg)
+{
+ int s = atoi(arg);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (s < 512 && s != 0) {
+ return "SendBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+
+ send_buffer_size = s;
+ return NULL;
+}
+
+AP_DECLARE_NONSTD(const char *) ap_set_receive_buffer_size(cmd_parms *cmd,
+ void *dummy,
+ const char *arg)
+{
+ int s = atoi(arg);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ if (s < 512 && s != 0) {
+ return "ReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
+ }
+
+ receive_buffer_size = s;
+ return NULL;
+}
diff --git a/server/log.c b/server/log.c
new file mode 100644
index 00000000..057672f2
--- /dev/null
+++ b/server/log.c
@@ -0,0 +1,987 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_log.c: Dealing with the logs and errors
+ *
+ * Rob McCool
+ *
+ */
+
+#include "apr.h"
+#include "apr_general.h" /* for signal stuff */
+#include "apr_strings.h"
+#include "apr_errno.h"
+#include "apr_thread_proc.h"
+#include "apr_lib.h"
+#include "apr_signal.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_time.h"
+#include "ap_mpm.h"
+
+typedef struct {
+ char *t_name;
+ int t_val;
+} TRANS;
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(error_log)
+)
+
+int AP_DECLARE_DATA ap_default_loglevel = DEFAULT_LOGLEVEL;
+
+#ifdef HAVE_SYSLOG
+
+static const TRANS facilities[] = {
+ {"auth", LOG_AUTH},
+#ifdef LOG_AUTHPRIV
+ {"authpriv",LOG_AUTHPRIV},
+#endif
+#ifdef LOG_CRON
+ {"cron", LOG_CRON},
+#endif
+#ifdef LOG_DAEMON
+ {"daemon", LOG_DAEMON},
+#endif
+#ifdef LOG_FTP
+ {"ftp", LOG_FTP},
+#endif
+#ifdef LOG_KERN
+ {"kern", LOG_KERN},
+#endif
+#ifdef LOG_LPR
+ {"lpr", LOG_LPR},
+#endif
+#ifdef LOG_MAIL
+ {"mail", LOG_MAIL},
+#endif
+#ifdef LOG_NEWS
+ {"news", LOG_NEWS},
+#endif
+#ifdef LOG_SYSLOG
+ {"syslog", LOG_SYSLOG},
+#endif
+#ifdef LOG_USER
+ {"user", LOG_USER},
+#endif
+#ifdef LOG_UUCP
+ {"uucp", LOG_UUCP},
+#endif
+#ifdef LOG_LOCAL0
+ {"local0", LOG_LOCAL0},
+#endif
+#ifdef LOG_LOCAL1
+ {"local1", LOG_LOCAL1},
+#endif
+#ifdef LOG_LOCAL2
+ {"local2", LOG_LOCAL2},
+#endif
+#ifdef LOG_LOCAL3
+ {"local3", LOG_LOCAL3},
+#endif
+#ifdef LOG_LOCAL4
+ {"local4", LOG_LOCAL4},
+#endif
+#ifdef LOG_LOCAL5
+ {"local5", LOG_LOCAL5},
+#endif
+#ifdef LOG_LOCAL6
+ {"local6", LOG_LOCAL6},
+#endif
+#ifdef LOG_LOCAL7
+ {"local7", LOG_LOCAL7},
+#endif
+ {NULL, -1},
+};
+#endif
+
+static const TRANS priorities[] = {
+ {"emerg", APLOG_EMERG},
+ {"alert", APLOG_ALERT},
+ {"crit", APLOG_CRIT},
+ {"error", APLOG_ERR},
+ {"warn", APLOG_WARNING},
+ {"notice", APLOG_NOTICE},
+ {"info", APLOG_INFO},
+ {"debug", APLOG_DEBUG},
+ {NULL, -1},
+};
+
+static apr_file_t *stderr_log = NULL;
+
+/* track pipe handles to close in child process */
+typedef struct read_handle_t {
+ struct read_handle_t *next;
+ apr_file_t *handle;
+} read_handle_t;
+
+static read_handle_t *read_handles;
+
+/* clear_handle_list() is called when plog is cleared; at that
+ * point we need to forget about our old list of pipe read
+ * handles
+ */
+static apr_status_t clear_handle_list(void *v)
+{
+ read_handles = NULL;
+ return APR_SUCCESS;
+}
+
+/* remember to close this handle in the child process */
+static void close_handle_in_child(apr_pool_t *p, apr_file_t *f)
+{
+ read_handle_t *new_handle;
+
+ new_handle = apr_pcalloc(p, sizeof(read_handle_t));
+ new_handle->next = read_handles;
+ new_handle->handle = f;
+ read_handles = new_handle;
+}
+
+void ap_logs_child_init(apr_pool_t *p, server_rec *s)
+{
+ read_handle_t *cur = read_handles;
+
+ while (cur) {
+ apr_file_close(cur->handle);
+ cur = cur->next;
+ }
+}
+
+AP_DECLARE(void) ap_open_stderr_log(apr_pool_t *p)
+{
+ apr_file_open_stderr(&stderr_log, p);
+}
+
+AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p,
+ const char *fname)
+{
+ apr_file_t *stderr_file;
+ apr_status_t rc;
+ char *filename = ap_server_root_relative(p, fname);
+ if (!filename) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT,
+ APR_EBADPATH, NULL, "Invalid -E error log file %s",
+ fname);
+ return APR_EBADPATH;
+ }
+ if ((rc = apr_file_open(&stderr_file, filename,
+ APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL,
+ "%s: could not open error log file %s.",
+ ap_server_argv0, fname);
+ return rc;
+ }
+ if ((rc = apr_file_open_stderr(&stderr_log, p)) == APR_SUCCESS) {
+ apr_file_flush(stderr_log);
+ if ((rc = apr_file_dup2(stderr_log, stderr_file, p)) == APR_SUCCESS) {
+ apr_file_close(stderr_file);
+ }
+ }
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc, NULL,
+ "unable to replace stderr with error_log");
+ }
+ return rc;
+}
+
+static void log_child_errfn(apr_pool_t *pool, apr_status_t err,
+ const char *description)
+{
+ ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL,
+ "%s", description);
+}
+
+static int log_child(apr_pool_t *p, const char *progname,
+ apr_file_t **fpin)
+{
+ /* Child process code for 'ErrorLog "|..."';
+ * may want a common framework for this, since I expect it will
+ * be common for other foo-loggers to want this sort of thing...
+ */
+ apr_status_t rc;
+ apr_procattr_t *procattr;
+ apr_proc_t *procnew;
+
+ if (((rc = apr_procattr_create(&procattr, p)) == APR_SUCCESS)
+ && ((rc = apr_procattr_cmdtype_set(procattr,
+ APR_SHELLCMD_ENV)) == APR_SUCCESS)
+ && ((rc = apr_procattr_io_set(procattr,
+ APR_FULL_BLOCK,
+ APR_NO_PIPE,
+ APR_NO_PIPE)) == APR_SUCCESS)
+ && ((rc = apr_procattr_error_check_set(procattr, 1)) == APR_SUCCESS)
+ && ((rc = apr_procattr_child_errfn_set(procattr, log_child_errfn)) == APR_SUCCESS)) {
+ char **args;
+ const char *pname;
+
+ apr_tokenize_to_argv(progname, &args, p);
+ pname = apr_pstrdup(p, args[0]);
+ procnew = (apr_proc_t *)apr_pcalloc(p, sizeof(*procnew));
+ rc = apr_proc_create(procnew, pname, (const char * const *)args,
+ NULL, procattr, p);
+
+ if (rc == APR_SUCCESS) {
+ apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+ (*fpin) = procnew->in;
+ /* read handle to pipe not kept open, so no need to call
+ * close_handle_in_child()
+ */
+ }
+ }
+
+ return rc;
+}
+
+static int open_error_log(server_rec *s, apr_pool_t *p)
+{
+ const char *fname;
+ int rc;
+
+ if (*s->error_fname == '|') {
+ apr_file_t *dummy = NULL;
+
+ /* This starts a new process... */
+ rc = log_child (p, s->error_fname + 1, &dummy);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL,
+ "Couldn't start ErrorLog process");
+ return DONE;
+ }
+
+ s->error_log = dummy;
+ }
+
+#ifdef HAVE_SYSLOG
+ else if (!strncasecmp(s->error_fname, "syslog", 6)) {
+ if ((fname = strchr(s->error_fname, ':'))) {
+ const TRANS *fac;
+
+ fname++;
+ for (fac = facilities; fac->t_name; fac++) {
+ if (!strcasecmp(fname, fac->t_name)) {
+ openlog(ap_server_argv0, LOG_NDELAY|LOG_CONS|LOG_PID,
+ fac->t_val);
+ s->error_log = NULL;
+ return OK;
+ }
+ }
+ }
+ else {
+ openlog(ap_server_argv0, LOG_NDELAY|LOG_CONS|LOG_PID, LOG_LOCAL7);
+ }
+
+ s->error_log = NULL;
+ }
+#endif
+ else {
+ fname = ap_server_root_relative(p, s->error_fname);
+ if (!fname) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, APR_EBADPATH, NULL,
+ "%s: Invalid error log path %s.",
+ ap_server_argv0, s->error_fname);
+ return DONE;
+ }
+ if ((rc = apr_file_open(&s->error_log, fname,
+ APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE,
+ APR_OS_DEFAULT, p)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL,
+ "%s: could not open error log file %s.",
+ ap_server_argv0, fname);
+ return DONE;
+ }
+ }
+
+ return OK;
+}
+
+int ap_open_logs(apr_pool_t *pconf, apr_pool_t *p /* plog */,
+ apr_pool_t *ptemp, server_rec *s_main)
+{
+ apr_status_t rc = APR_SUCCESS;
+ server_rec *virt, *q;
+ int replace_stderr;
+ apr_file_t *errfile = NULL;
+
+ apr_pool_cleanup_register(p, NULL, clear_handle_list,
+ apr_pool_cleanup_null);
+ if (open_error_log(s_main, p) != OK) {
+ return DONE;
+ }
+
+ replace_stderr = 1;
+ if (s_main->error_log) {
+ /* replace stderr with this new log */
+ apr_file_flush(s_main->error_log);
+ if ((rc = apr_file_open_stderr(&errfile, p)) == APR_SUCCESS) {
+ rc = apr_file_dup2(errfile, s_main->error_log, p);
+ }
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s_main,
+ "unable to replace stderr with error_log");
+ }
+ else {
+ replace_stderr = 0;
+ }
+ }
+ /* note that stderr may still need to be replaced with something
+ * because it points to the old error log, or back to the tty
+ * of the submitter.
+ * XXX: This is BS - /dev/null is non-portable
+ */
+ if (replace_stderr && freopen("/dev/null", "w", stderr) == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s_main,
+ "unable to replace stderr with /dev/null");
+ }
+
+ for (virt = s_main->next; virt; virt = virt->next) {
+ if (virt->error_fname) {
+ for (q=s_main; q != virt; q = q->next) {
+ if (q->error_fname != NULL
+ && strcmp(q->error_fname, virt->error_fname) == 0) {
+ break;
+ }
+ }
+
+ if (q == virt) {
+ if (open_error_log(virt, p) != OK) {
+ return DONE;
+ }
+ }
+ else {
+ virt->error_log = q->error_log;
+ }
+ }
+ else {
+ virt->error_log = s_main->error_log;
+ }
+ }
+ return OK;
+}
+
+AP_DECLARE(void) ap_error_log2stderr(server_rec *s) {
+ apr_file_t *errfile = NULL;
+
+ apr_file_open_stderr(&errfile, s->process->pool);
+ if (s->error_log != NULL) {
+ apr_file_dup2(s->error_log, errfile, s->process->pool);
+ }
+}
+
+static void log_error_core(const char *file, int line, int level,
+ apr_status_t status, const server_rec *s,
+ const conn_rec *c,
+ const request_rec *r, apr_pool_t *pool,
+ const char *fmt, va_list args)
+{
+ char errstr[MAX_STRING_LEN];
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+ char scratch[MAX_STRING_LEN];
+#endif
+ apr_size_t len, errstrlen;
+ apr_file_t *logf = NULL;
+ const char *referer;
+ int level_and_mask = level & APLOG_LEVELMASK;
+
+ if (r && r->connection) {
+ c = r->connection;
+ }
+
+ if (s == NULL) {
+ /*
+ * If we are doing stderr logging (startup), don't log messages that are
+ * above the default server log level unless it is a startup/shutdown
+ * notice
+ */
+ if ((level_and_mask != APLOG_NOTICE)
+ && (level_and_mask > ap_default_loglevel)) {
+ return;
+ }
+
+ logf = stderr_log;
+ }
+ else if (s->error_log) {
+ /*
+ * If we are doing normal logging, don't log messages that are
+ * above the server log level unless it is a startup/shutdown notice
+ */
+ if ((level_and_mask != APLOG_NOTICE)
+ && (level_and_mask > s->loglevel)) {
+ return;
+ }
+
+ logf = s->error_log;
+ }
+#ifdef TPF
+ else if (tpf_child) {
+ /*
+ * If we are doing normal logging, don't log messages that are
+ * above the server log level unless it is a startup/shutdown notice
+ */
+ if ((level_and_mask != APLOG_NOTICE)
+ && (level_and_mask > s->loglevel)) {
+ return;
+ }
+
+ logf = stderr;
+ }
+#endif /* TPF */
+ else {
+ /*
+ * If we are doing syslog logging, don't log messages that are
+ * above the server log level (including a startup/shutdown notice)
+ */
+ if (level_and_mask > s->loglevel) {
+ return;
+ }
+ }
+
+ if (logf && ((level & APLOG_STARTUP) != APLOG_STARTUP)) {
+ errstr[0] = '[';
+ ap_recent_ctime(errstr + 1, apr_time_now());
+ errstr[1 + APR_CTIME_LEN - 1] = ']';
+ errstr[1 + APR_CTIME_LEN ] = ' ';
+ len = 1 + APR_CTIME_LEN + 1;
+ } else {
+ len = 0;
+ }
+
+ if ((level & APLOG_STARTUP) != APLOG_STARTUP) {
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "[%s] ", priorities[level_and_mask].t_name);
+ }
+
+#ifndef TPF
+ if (file && level_and_mask == APLOG_DEBUG) {
+#if defined(_OSD_POSIX) || defined(WIN32) || defined(__MVS__)
+ char tmp[256];
+ char *e = strrchr(file, '/');
+#ifdef WIN32
+ if (!e) {
+ e = strrchr(file, '\\');
+ }
+#endif
+
+ /* In OSD/POSIX, the compiler returns for __FILE__
+ * a string like: __FILE__="*POSIX(/usr/include/stdio.h)"
+ * (it even returns an absolute path for sources in
+ * the current directory). Here we try to strip this
+ * down to the basename.
+ */
+ if (e != NULL && e[1] != '\0') {
+ apr_snprintf(tmp, sizeof(tmp), "%s", &e[1]);
+ e = &tmp[strlen(tmp)-1];
+ if (*e == ')') {
+ *e = '\0';
+ }
+ file = tmp;
+ }
+#else /* _OSD_POSIX || WIN32 */
+ const char *p;
+ /* On Unix, __FILE__ may be an absolute path in a
+ * VPATH build. */
+ if (file[0] == '/' && (p = ap_strrchr_c(file, '/')) != NULL) {
+ file = p + 1;
+ }
+#endif /*_OSD_POSIX || WIN32 */
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "%s(%d): ", file, line);
+ }
+#endif /* TPF */
+
+ if (c) {
+ /* XXX: TODO: add a method of selecting whether logged client
+ * addresses are in dotted quad or resolved form... dotted
+ * quad is the most secure, which is why I'm implementing it
+ * first. -djg
+ */
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "[client %s] ", c->remote_ip);
+ }
+ if (status != 0) {
+ if (status < APR_OS_START_EAIERR) {
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "(%d)", status);
+ }
+ else if (status < APR_OS_START_SYSERR) {
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "(EAI %d)", status - APR_OS_START_EAIERR);
+ }
+ else if (status < 100000 + APR_OS_START_SYSERR) {
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "(OS %d)", status - APR_OS_START_SYSERR);
+ }
+ else {
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ "(os 0x%08x)", status - APR_OS_START_SYSERR);
+ }
+ apr_strerror(status, errstr + len, MAX_STRING_LEN - len);
+ len += strlen(errstr + len);
+ if (MAX_STRING_LEN - len > 2) {
+ errstr[len++] = ':';
+ errstr[len++] = ' ';
+ errstr[len] = '\0';
+ }
+ }
+
+ errstrlen = len;
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+ if (apr_vsnprintf(scratch, MAX_STRING_LEN - len, fmt, args)) {
+ len += ap_escape_errorlog_item(errstr + len, scratch,
+ MAX_STRING_LEN - len);
+ }
+#else
+ len += apr_vsnprintf(errstr + len, MAX_STRING_LEN - len, fmt, args);
+#endif
+
+ if ( r && (referer = apr_table_get(r->headers_in, "Referer"))
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+ && ap_escape_errorlog_item(scratch, referer, MAX_STRING_LEN - len)
+#endif
+ ) {
+ len += apr_snprintf(errstr + len, MAX_STRING_LEN - len,
+ ", referer: %s",
+#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED
+ scratch
+#else
+ referer
+#endif
+ );
+ }
+
+ /* NULL if we are logging to syslog */
+ if (logf) {
+ /* Truncate for the terminator (as apr_snprintf does) */
+ if (len > MAX_STRING_LEN - sizeof(APR_EOL_STR)) {
+ len = MAX_STRING_LEN - sizeof(APR_EOL_STR);
+ }
+ strcpy(errstr + len, APR_EOL_STR);
+ apr_file_puts(errstr, logf);
+ apr_file_flush(logf);
+ }
+#ifdef HAVE_SYSLOG
+ else {
+ syslog(level_and_mask, "%s", errstr);
+ }
+#endif
+
+ ap_run_error_log(file, line, level, status, s, r, pool, errstr + errstrlen);
+}
+
+AP_DECLARE(void) ap_log_error(const char *file, int line, int level,
+ apr_status_t status, const server_rec *s,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ log_error_core(file, line, level, status, s, NULL, NULL, NULL, fmt, args);
+ va_end(args);
+}
+
+AP_DECLARE(void) ap_log_perror(const char *file, int line, int level,
+ apr_status_t status, apr_pool_t *p,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ log_error_core(file, line, level, status, NULL, NULL, NULL, p, fmt, args);
+ va_end(args);
+}
+
+AP_DECLARE(void) ap_log_rerror(const char *file, int line, int level,
+ apr_status_t status, const request_rec *r,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ log_error_core(file, line, level, status, r->server, NULL, r, NULL, fmt,
+ args);
+
+ /*
+ * IF APLOG_TOCLIENT is set,
+ * AND the error level is 'warning' or more severe,
+ * AND there isn't already error text associated with this request,
+ * THEN make the message text available to ErrorDocument and
+ * other error processors.
+ */
+ va_end(args);
+ va_start(args,fmt);
+ if ((level & APLOG_TOCLIENT)
+ && ((level & APLOG_LEVELMASK) <= APLOG_WARNING)
+ && (apr_table_get(r->notes, "error-notes") == NULL)) {
+ apr_table_setn(r->notes, "error-notes",
+ ap_escape_html(r->pool, apr_pvsprintf(r->pool, fmt,
+ args)));
+ }
+ va_end(args);
+}
+
+AP_DECLARE(void) ap_log_cerror(const char *file, int line, int level,
+ apr_status_t status, const conn_rec *c,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ log_error_core(file, line, level, status, c->base_server, c, NULL, NULL,
+ fmt, args);
+ va_end(args);
+}
+
+AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename)
+{
+ apr_file_t *pid_file = NULL;
+ apr_finfo_t finfo;
+ static pid_t saved_pid = -1;
+ pid_t mypid;
+ apr_status_t rv;
+ const char *fname;
+
+ if (!filename) {
+ return;
+ }
+
+ fname = ap_server_root_relative(p, filename);
+ if (!fname) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, APR_EBADPATH,
+ NULL, "Invalid PID file path %s, ignoring.", filename);
+ return;
+ }
+
+ mypid = getpid();
+ if (mypid != saved_pid
+ && apr_stat(&finfo, fname, APR_FINFO_MTIME, p) == APR_SUCCESS) {
+ /* AP_SIG_GRACEFUL and HUP call this on each restart.
+ * Only warn on first time through for this pid.
+ *
+ * XXX: Could just write first time through too, although
+ * that may screw up scripts written to do something
+ * based on the last modification time of the pid file.
+ */
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p,
+ "pid file %s overwritten -- Unclean "
+ "shutdown of previous Apache run?",
+ fname);
+ }
+
+ if ((rv = apr_file_open(&pid_file, fname,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE,
+ APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD, p))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "could not create %s", fname);
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "%s: could not log pid to file %s",
+ ap_server_argv0, fname);
+ exit(1);
+ }
+ apr_file_printf(pid_file, "%ld" APR_EOL_STR, (long)mypid);
+ apr_file_close(pid_file);
+ saved_pid = mypid;
+}
+
+AP_DECLARE(apr_status_t) ap_read_pid(apr_pool_t *p, const char *filename,
+ pid_t *mypid)
+{
+ const apr_size_t BUFFER_SIZE = sizeof(long) * 3 + 2; /* see apr_ltoa */
+ apr_file_t *pid_file = NULL;
+ apr_status_t rv;
+ const char *fname;
+ char *buf, *endptr;
+ apr_size_t bytes_read;
+
+ if (!filename) {
+ return APR_EGENERAL;
+ }
+
+ fname = ap_server_root_relative(p, filename);
+ if (!fname) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, APR_EBADPATH,
+ NULL, "Invalid PID file path %s, ignoring.", filename);
+ return APR_EGENERAL;
+ }
+
+ rv = apr_file_open(&pid_file, fname, APR_READ, APR_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ buf = apr_palloc(p, BUFFER_SIZE);
+
+ rv = apr_file_read_full(pid_file, buf, BUFFER_SIZE - 1, &bytes_read);
+ if (rv != APR_SUCCESS && rv != APR_EOF) {
+ return rv;
+ }
+
+ /* If we fill the buffer, we're probably reading a corrupt pid file.
+ * To be nice, let's also ensure the first char is a digit. */
+ if (bytes_read == 0 || bytes_read == BUFFER_SIZE - 1 || !apr_isdigit(*buf)) {
+ return APR_EGENERAL;
+ }
+
+ buf[bytes_read] = '\0';
+ *mypid = strtol(buf, &endptr, 10);
+
+ apr_file_close(pid_file);
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(void) ap_log_assert(const char *szExp, const char *szFile,
+ int nLine)
+{
+ char time_str[APR_CTIME_LEN];
+
+ apr_ctime(time_str, apr_time_now());
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
+ "[%s] file %s, line %d, assertion \"%s\" failed",
+ time_str, szFile, nLine, szExp);
+#if defined(WIN32)
+ DebugBreak();
+#else
+ /* unix assert does an abort leading to a core dump */
+ abort();
+#endif
+}
+
+/* piped log support */
+
+#ifdef AP_HAVE_RELIABLE_PIPED_LOGS
+/* forward declaration */
+static void piped_log_maintenance(int reason, void *data, apr_wait_t status);
+
+/* Spawn the piped logger process pl->program. */
+static apr_status_t piped_log_spawn(piped_log *pl)
+{
+ apr_procattr_t *procattr;
+ apr_proc_t *procnew = NULL;
+ apr_status_t status;
+
+ if (((status = apr_procattr_create(&procattr, pl->p)) != APR_SUCCESS) ||
+ ((status = apr_procattr_cmdtype_set(procattr,
+ APR_SHELLCMD_ENV)) != APR_SUCCESS) ||
+ ((status = apr_procattr_child_in_set(procattr,
+ ap_piped_log_read_fd(pl),
+ ap_piped_log_write_fd(pl)))
+ != APR_SUCCESS) ||
+ ((status = apr_procattr_child_errfn_set(procattr, log_child_errfn))
+ != APR_SUCCESS) ||
+ ((status = apr_procattr_error_check_set(procattr, 1)) != APR_SUCCESS)) {
+ char buf[120];
+ /* Something bad happened, give up and go away. */
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "piped_log_spawn: unable to setup child process '%s': %s",
+ pl->program, apr_strerror(status, buf, sizeof(buf)));
+ }
+ else {
+ char **args;
+ const char *pname;
+
+ apr_tokenize_to_argv(pl->program, &args, pl->p);
+ pname = apr_pstrdup(pl->p, args[0]);
+ procnew = apr_pcalloc(pl->p, sizeof(apr_proc_t));
+ status = apr_proc_create(procnew, pname, (const char * const *) args,
+ NULL, procattr, pl->p);
+
+ if (status == APR_SUCCESS) {
+ pl->pid = procnew;
+ /* procnew->in was dup2'd from ap_piped_log_write_fd(pl);
+ * since the original fd is still valid, close the copy to
+ * avoid a leak. */
+ apr_file_close(procnew->in);
+ procnew->in = NULL;
+ apr_proc_other_child_register(procnew, piped_log_maintenance, pl,
+ ap_piped_log_write_fd(pl), pl->p);
+ close_handle_in_child(pl->p, ap_piped_log_read_fd(pl));
+ }
+ else {
+ char buf[120];
+ /* Something bad happened, give up and go away. */
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "unable to start piped log program '%s': %s",
+ pl->program, apr_strerror(status, buf, sizeof(buf)));
+ }
+ }
+
+ return status;
+}
+
+
+static void piped_log_maintenance(int reason, void *data, apr_wait_t status)
+{
+ piped_log *pl = data;
+ apr_status_t stats;
+ int mpm_state;
+
+ switch (reason) {
+ case APR_OC_REASON_DEATH:
+ case APR_OC_REASON_LOST:
+ pl->pid = NULL; /* in case we don't get it going again, this
+ * tells other logic not to try to kill it
+ */
+ apr_proc_other_child_unregister(pl);
+ stats = ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state);
+ if (stats != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "can't query MPM state; not restarting "
+ "piped log program '%s'",
+ pl->program);
+ }
+ else if (mpm_state != AP_MPMQ_STOPPING) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "piped log program '%s' failed unexpectedly",
+ pl->program);
+ if ((stats = piped_log_spawn(pl)) != APR_SUCCESS) {
+ /* what can we do? This could be the error log we're having
+ * problems opening up... */
+ char buf[120];
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "piped_log_maintenance: unable to respawn '%s': %s",
+ pl->program, apr_strerror(stats, buf, sizeof(buf)));
+ }
+ }
+ break;
+
+ case APR_OC_REASON_UNWRITABLE:
+ /* We should not kill off the pipe here, since it may only be full.
+ * If it really is locked, we should kill it off manually. */
+ break;
+
+ case APR_OC_REASON_RESTART:
+ if (pl->pid != NULL) {
+ apr_proc_kill(pl->pid, SIGTERM);
+ pl->pid = NULL;
+ }
+ break;
+
+ case APR_OC_REASON_UNREGISTER:
+ break;
+ }
+}
+
+
+static apr_status_t piped_log_cleanup_for_exec(void *data)
+{
+ piped_log *pl = data;
+
+ apr_file_close(ap_piped_log_read_fd(pl));
+ apr_file_close(ap_piped_log_write_fd(pl));
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t piped_log_cleanup(void *data)
+{
+ piped_log *pl = data;
+
+ if (pl->pid != NULL) {
+ apr_proc_kill(pl->pid, SIGTERM);
+ }
+ return piped_log_cleanup_for_exec(data);
+}
+
+
+AP_DECLARE(piped_log *) ap_open_piped_log(apr_pool_t *p, const char *program)
+{
+ piped_log *pl;
+
+ pl = apr_palloc(p, sizeof (*pl));
+ pl->p = p;
+ pl->program = apr_pstrdup(p, program);
+ pl->pid = NULL;
+ if (apr_file_pipe_create(&ap_piped_log_read_fd(pl),
+ &ap_piped_log_write_fd(pl), p) != APR_SUCCESS) {
+ return NULL;
+ }
+ apr_pool_cleanup_register(p, pl, piped_log_cleanup,
+ piped_log_cleanup_for_exec);
+ if (piped_log_spawn(pl) != APR_SUCCESS) {
+ apr_pool_cleanup_kill(p, pl, piped_log_cleanup);
+ apr_file_close(ap_piped_log_read_fd(pl));
+ apr_file_close(ap_piped_log_write_fd(pl));
+ return NULL;
+ }
+ return pl;
+}
+
+#else /* !AP_HAVE_RELIABLE_PIPED_LOGS */
+
+static apr_status_t piped_log_cleanup(void *data)
+{
+ piped_log *pl = data;
+
+ apr_file_close(ap_piped_log_write_fd(pl));
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(piped_log *) ap_open_piped_log(apr_pool_t *p, const char *program)
+{
+ piped_log *pl;
+ apr_file_t *dummy = NULL;
+ int rc;
+
+ rc = log_child(p, program, &dummy);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL,
+ "Couldn't start piped log process");
+ return NULL;
+ }
+
+ pl = apr_palloc(p, sizeof (*pl));
+ pl->p = p;
+ ap_piped_log_read_fd(pl) = NULL;
+ ap_piped_log_write_fd(pl) = dummy;
+ apr_pool_cleanup_register(p, pl, piped_log_cleanup, piped_log_cleanup);
+
+ return pl;
+}
+
+#endif
+
+AP_DECLARE(void) ap_close_piped_log(piped_log *pl)
+{
+ apr_pool_cleanup_run(pl->p, pl, piped_log_cleanup);
+}
+
+AP_IMPLEMENT_HOOK_VOID(error_log,
+ (const char *file, int line, int level,
+ apr_status_t status, const server_rec *s,
+ const request_rec *r, apr_pool_t *pool,
+ const char *errstr), (file, line, level,
+ status, s, r, pool, errstr))
+
diff --git a/server/main.c b/server/main.c
new file mode 100644
index 00000000..8b9fc816
--- /dev/null
+++ b/server/main.c
@@ -0,0 +1,742 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_getopt.h"
+#include "apr_general.h"
+#include "apr_lib.h"
+#include "apr_md5.h"
+#include "apr_version.h"
+#include "apu_version.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_vhost.h"
+#include "apr_uri.h"
+#include "util_ebcdic.h"
+#include "ap_mpm.h"
+#include "mpm_common.h"
+
+/* WARNING: Win32 binds http_main.c dynamically to the server. Please place
+ * extern functions and global data in another appropriate module.
+ *
+ * Most significant main() global data can be found in http_config.c
+ */
+
+static void show_mpm_settings(void)
+{
+ int mpm_query_info;
+ apr_status_t retval;
+
+ printf("Server MPM: %s\n", ap_show_mpm());
+
+ retval = ap_mpm_query(AP_MPMQ_IS_THREADED, &mpm_query_info);
+
+ if (retval == APR_SUCCESS) {
+ printf(" threaded: ");
+
+ if (mpm_query_info == AP_MPMQ_DYNAMIC) {
+ printf("yes (variable thread count)\n");
+ }
+ else if (mpm_query_info == AP_MPMQ_STATIC) {
+ printf("yes (fixed thread count)\n");
+ }
+ else {
+ printf("no\n");
+ }
+ }
+
+ retval = ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_query_info);
+
+ if (retval == APR_SUCCESS) {
+ printf(" forked: ");
+
+ if (mpm_query_info == AP_MPMQ_DYNAMIC) {
+ printf("yes (variable process count)\n");
+ }
+ else if (mpm_query_info == AP_MPMQ_STATIC) {
+ printf("yes (fixed process count)\n");
+ }
+ else {
+ printf("no\n");
+ }
+ }
+}
+
+static void show_compile_settings(void)
+{
+ printf("Server version: %s\n", ap_get_server_version());
+ printf("Server built: %s\n", ap_get_server_built());
+ printf("Server's Module Magic Number: %u:%u\n",
+ MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
+ printf("Server loaded: APR %s, APR-Util %s\n",
+ apr_version_string(), apu_version_string());
+ printf("Compiled using: APR %s, APR-Util %s\n",
+ APR_VERSION_STRING, APU_VERSION_STRING);
+ /* sizeof(foo) is long on some platforms so we might as well
+ * make it long everywhere to keep the printf format
+ * consistent
+ */
+ printf("Architecture: %ld-bit\n", 8 * (long)sizeof(void *));
+
+ show_mpm_settings();
+
+ printf("Server compiled with....\n");
+#ifdef BIG_SECURITY_HOLE
+ printf(" -D BIG_SECURITY_HOLE\n");
+#endif
+
+#ifdef SECURITY_HOLE_PASS_AUTHORIZATION
+ printf(" -D SECURITY_HOLE_PASS_AUTHORIZATION\n");
+#endif
+
+#ifdef OS
+ printf(" -D OS=\"" OS "\"\n");
+#endif
+
+#ifdef APACHE_MPM_DIR
+ printf(" -D APACHE_MPM_DIR=\"" APACHE_MPM_DIR "\"\n");
+#endif
+
+#ifdef HAVE_SHMGET
+ printf(" -D HAVE_SHMGET\n");
+#endif
+
+#if APR_FILE_BASED_SHM
+ printf(" -D APR_FILE_BASED_SHM\n");
+#endif
+
+#if APR_HAS_SENDFILE
+ printf(" -D APR_HAS_SENDFILE\n");
+#endif
+
+#if APR_HAS_MMAP
+ printf(" -D APR_HAS_MMAP\n");
+#endif
+
+#ifdef NO_WRITEV
+ printf(" -D NO_WRITEV\n");
+#endif
+
+#ifdef NO_LINGCLOSE
+ printf(" -D NO_LINGCLOSE\n");
+#endif
+
+#if APR_HAVE_IPV6
+ printf(" -D APR_HAVE_IPV6 (IPv4-mapped addresses ");
+#ifdef AP_ENABLE_V4_MAPPED
+ printf("enabled)\n");
+#else
+ printf("disabled)\n");
+#endif
+#endif
+
+#if APR_USE_FLOCK_SERIALIZE
+ printf(" -D APR_USE_FLOCK_SERIALIZE\n");
+#endif
+
+#if APR_USE_SYSVSEM_SERIALIZE
+ printf(" -D APR_USE_SYSVSEM_SERIALIZE\n");
+#endif
+
+#if APR_USE_POSIXSEM_SERIALIZE
+ printf(" -D APR_USE_POSIXSEM_SERIALIZE\n");
+#endif
+
+#if APR_USE_FCNTL_SERIALIZE
+ printf(" -D APR_USE_FCNTL_SERIALIZE\n");
+#endif
+
+#if APR_USE_PROC_PTHREAD_SERIALIZE
+ printf(" -D APR_USE_PROC_PTHREAD_SERIALIZE\n");
+#endif
+
+#if APR_USE_PTHREAD_SERIALIZE
+ printf(" -D APR_USE_PTHREAD_SERIALIZE\n");
+#endif
+
+#if APR_PROCESS_LOCK_IS_GLOBAL
+ printf(" -D APR_PROCESS_LOCK_IS_GLOBAL\n");
+#endif
+
+#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
+ printf(" -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n");
+#endif
+
+#if APR_HAS_OTHER_CHILD
+ printf(" -D APR_HAS_OTHER_CHILD\n");
+#endif
+
+#ifdef AP_HAVE_RELIABLE_PIPED_LOGS
+ printf(" -D AP_HAVE_RELIABLE_PIPED_LOGS\n");
+#endif
+
+#ifdef BUFFERED_LOGS
+ printf(" -D BUFFERED_LOGS\n");
+#ifdef PIPE_BUF
+ printf(" -D PIPE_BUF=%ld\n",(long)PIPE_BUF);
+#endif
+#endif
+
+ printf(" -D DYNAMIC_MODULE_LIMIT=%ld\n",(long)DYNAMIC_MODULE_LIMIT);
+
+#if APR_CHARSET_EBCDIC
+ printf(" -D APR_CHARSET_EBCDIC\n");
+#endif
+
+#ifdef NEED_HASHBANG_EMUL
+ printf(" -D NEED_HASHBANG_EMUL\n");
+#endif
+
+#ifdef SHARED_CORE
+ printf(" -D SHARED_CORE\n");
+#endif
+
+/* This list displays the compiled in default paths: */
+#ifdef HTTPD_ROOT
+ printf(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n");
+#endif
+
+#ifdef SUEXEC_BIN
+ printf(" -D SUEXEC_BIN=\"" SUEXEC_BIN "\"\n");
+#endif
+
+#if defined(SHARED_CORE) && defined(SHARED_CORE_DIR)
+ printf(" -D SHARED_CORE_DIR=\"" SHARED_CORE_DIR "\"\n");
+#endif
+
+#ifdef DEFAULT_PIDLOG
+ printf(" -D DEFAULT_PIDLOG=\"" DEFAULT_PIDLOG "\"\n");
+#endif
+
+#ifdef DEFAULT_SCOREBOARD
+ printf(" -D DEFAULT_SCOREBOARD=\"" DEFAULT_SCOREBOARD "\"\n");
+#endif
+
+#ifdef DEFAULT_LOCKFILE
+ printf(" -D DEFAULT_LOCKFILE=\"" DEFAULT_LOCKFILE "\"\n");
+#endif
+
+#ifdef DEFAULT_ERRORLOG
+ printf(" -D DEFAULT_ERRORLOG=\"" DEFAULT_ERRORLOG "\"\n");
+#endif
+
+#ifdef AP_TYPES_CONFIG_FILE
+ printf(" -D AP_TYPES_CONFIG_FILE=\"" AP_TYPES_CONFIG_FILE "\"\n");
+#endif
+
+#ifdef SERVER_CONFIG_FILE
+ printf(" -D SERVER_CONFIG_FILE=\"" SERVER_CONFIG_FILE "\"\n");
+#endif
+}
+
+static void destroy_and_exit_process(process_rec *process,
+ int process_exit_value)
+{
+ apr_pool_destroy(process->pool); /* and destroy all descendent pools */
+ apr_terminate();
+ exit(process_exit_value);
+}
+
+static process_rec *create_process(int argc, const char * const *argv)
+{
+ process_rec *process;
+ apr_pool_t *cntx;
+ apr_status_t stat;
+
+ stat = apr_pool_create(&cntx, NULL);
+ if (stat != APR_SUCCESS) {
+ /* XXX From the time that we took away the NULL pool->malloc mapping
+ * we have been unable to log here without segfaulting.
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, stat, NULL,
+ "apr_pool_create() failed to create "
+ "initial context");
+ apr_terminate();
+ exit(1);
+ }
+
+ apr_pool_tag(cntx, "process");
+ ap_open_stderr_log(cntx);
+
+ process = apr_palloc(cntx, sizeof(process_rec));
+ process->pool = cntx;
+
+ apr_pool_create(&process->pconf, process->pool);
+ apr_pool_tag(process->pconf, "pconf");
+ process->argc = argc;
+ process->argv = argv;
+ process->short_name = apr_filepath_name_get(argv[0]);
+ return process;
+}
+
+static void usage(process_rec *process)
+{
+ const char *bin = process->argv[0];
+ char pad[MAX_STRING_LEN];
+ unsigned i;
+
+ for (i = 0; i < strlen(bin); i++) {
+ pad[i] = ' ';
+ }
+
+ pad[i] = '\0';
+
+#ifdef SHARED_CORE
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL ,
+ "Usage: %s [-R directory] [-D name] [-d directory] [-f file]",
+ bin);
+#else
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Usage: %s [-D name] [-d directory] [-f file]", bin);
+#endif
+
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " %s [-C \"directive\"] [-c \"directive\"]", pad);
+
+#ifdef WIN32
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " %s [-w] [-k start|restart|stop|shutdown]", pad);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " %s [-k install|config|uninstall] [-n service_name]",
+ pad);
+#endif
+#ifdef AP_MPM_WANT_SIGNAL_SERVER
+#ifdef AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " %s [-k start|restart|graceful|graceful-stop|stop]",
+ pad);
+#else
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " %s [-k start|restart|graceful|stop]",
+ pad);
+#endif /* AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN */
+#endif
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " %s [-v] [-V] [-h] [-l] [-L] [-t] [-S]", pad);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Options:");
+
+#ifdef SHARED_CORE
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -R directory : specify an alternate location for "
+ "shared object files");
+#endif
+
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -D name : define a name for use in "
+ "<IfDefine name> directives");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -d directory : specify an alternate initial "
+ "ServerRoot");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -f file : specify an alternate ServerConfigFile");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -C \"directive\" : process directive before reading "
+ "config files");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -c \"directive\" : process directive after reading "
+ "config files");
+
+#ifdef NETWARE
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -n name : set screen name");
+#endif
+#ifdef WIN32
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -n name : set service name and use its "
+ "ServerConfigFile");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -k start : tell Apache to start");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -k restart : tell running Apache to do a graceful "
+ "restart");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -k stop|shutdown : tell running Apache to shutdown");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -k install : install an Apache service");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -k config : change startup Options of an Apache "
+ "service");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -k uninstall : uninstall an Apache service");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -w : hold open the console window on error");
+#endif
+
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -e level : show startup errors of level "
+ "(see LogLevel)");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -E file : log startup errors to file");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -v : show version number");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -V : show compile settings");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -h : list available command line options "
+ "(this page)");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -l : list compiled in modules");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -L : list available configuration "
+ "directives");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -t -D DUMP_VHOSTS : show parsed settings (currently only "
+ "vhost settings)");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -S : a synonym for -t -D DUMP_VHOSTS");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -t -D DUMP_MODULES : show all loaded modules ");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -M : a synonym for -t -D DUMP_MODULES");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " -t : run syntax check for config files");
+
+ destroy_and_exit_process(process, 1);
+}
+
+int main(int argc, const char * const argv[])
+{
+ char c;
+ int configtestonly = 0;
+ const char *confname = SERVER_CONFIG_FILE;
+ const char *def_server_root = HTTPD_ROOT;
+ const char *temp_error_log = NULL;
+ const char *error;
+ process_rec *process;
+ server_rec *server_conf;
+ apr_pool_t *pglobal;
+ apr_pool_t *pconf;
+ apr_pool_t *plog; /* Pool of log streams, reset _after_ each read of conf */
+ apr_pool_t *ptemp; /* Pool for temporary config stuff, reset often */
+ apr_pool_t *pcommands; /* Pool for -D, -C and -c switches */
+ apr_getopt_t *opt;
+ apr_status_t rv;
+ module **mod;
+ const char *optarg;
+ APR_OPTIONAL_FN_TYPE(ap_signal_server) *signal_server;
+
+ AP_MONCONTROL(0); /* turn off profiling of startup */
+
+ apr_app_initialize(&argc, &argv, NULL);
+
+ process = create_process(argc, argv);
+ pglobal = process->pool;
+ pconf = process->pconf;
+ ap_server_argv0 = process->short_name;
+
+#if APR_CHARSET_EBCDIC
+ if (ap_init_ebcdic(pglobal) != APR_SUCCESS) {
+ destroy_and_exit_process(process, 1);
+ }
+#endif
+
+ apr_pool_create(&pcommands, pglobal);
+ apr_pool_tag(pcommands, "pcommands");
+ ap_server_pre_read_config = apr_array_make(pcommands, 1, sizeof(char *));
+ ap_server_post_read_config = apr_array_make(pcommands, 1, sizeof(char *));
+ ap_server_config_defines = apr_array_make(pcommands, 1, sizeof(char *));
+
+ error = ap_setup_prelinked_modules(process);
+ if (error) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0, NULL, "%s: %s",
+ ap_server_argv0, error);
+ destroy_and_exit_process(process, 1);
+ }
+
+ ap_run_rewrite_args(process);
+
+ /* Maintain AP_SERVER_BASEARGS list in http_main.h to allow the MPM
+ * to safely pass on our args from its rewrite_args() handler.
+ */
+ apr_getopt_init(&opt, pcommands, process->argc, process->argv);
+
+ while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg))
+ == APR_SUCCESS) {
+ char **new;
+
+ switch (c) {
+ case 'c':
+ new = (char **)apr_array_push(ap_server_post_read_config);
+ *new = apr_pstrdup(pcommands, optarg);
+ break;
+
+ case 'C':
+ new = (char **)apr_array_push(ap_server_pre_read_config);
+ *new = apr_pstrdup(pcommands, optarg);
+ break;
+
+ case 'd':
+ def_server_root = optarg;
+ break;
+
+ case 'D':
+ new = (char **)apr_array_push(ap_server_config_defines);
+ *new = apr_pstrdup(pcommands, optarg);
+ /* Setting -D DUMP_VHOSTS is equivalent to setting -S */
+ if (strcmp(optarg, "DUMP_VHOSTS") == 0)
+ configtestonly = 1;
+ /* Setting -D DUMP_MODULES is equivalent to setting -M */
+ if (strcmp(optarg, "DUMP_MODULES") == 0)
+ configtestonly = 1;
+ break;
+
+ case 'e':
+ if (strcasecmp(optarg, "emerg") == 0) {
+ ap_default_loglevel = APLOG_EMERG;
+ }
+ else if (strcasecmp(optarg, "alert") == 0) {
+ ap_default_loglevel = APLOG_ALERT;
+ }
+ else if (strcasecmp(optarg, "crit") == 0) {
+ ap_default_loglevel = APLOG_CRIT;
+ }
+ else if (strncasecmp(optarg, "err", 3) == 0) {
+ ap_default_loglevel = APLOG_ERR;
+ }
+ else if (strncasecmp(optarg, "warn", 4) == 0) {
+ ap_default_loglevel = APLOG_WARNING;
+ }
+ else if (strcasecmp(optarg, "notice") == 0) {
+ ap_default_loglevel = APLOG_NOTICE;
+ }
+ else if (strcasecmp(optarg, "info") == 0) {
+ ap_default_loglevel = APLOG_INFO;
+ }
+ else if (strcasecmp(optarg, "debug") == 0) {
+ ap_default_loglevel = APLOG_DEBUG;
+ }
+ else {
+ usage(process);
+ }
+ break;
+
+ case 'E':
+ temp_error_log = apr_pstrdup(process->pool, optarg);
+ break;
+
+ case 'X':
+ new = (char **)apr_array_push(ap_server_config_defines);
+ *new = "DEBUG";
+ break;
+
+ case 'f':
+ confname = optarg;
+ break;
+
+ case 'v':
+ printf("Server version: %s\n", ap_get_server_version());
+ printf("Server built: %s\n", ap_get_server_built());
+ destroy_and_exit_process(process, 0);
+
+ case 'V':
+ show_compile_settings();
+ destroy_and_exit_process(process, 0);
+
+ case 'l':
+ ap_show_modules();
+ destroy_and_exit_process(process, 0);
+
+ case 'L':
+ ap_show_directives();
+ destroy_and_exit_process(process, 0);
+
+ case 't':
+ configtestonly = 1;
+ break;
+
+ case 'S':
+ configtestonly = 1;
+ new = (char **)apr_array_push(ap_server_config_defines);
+ *new = "DUMP_VHOSTS";
+ break;
+
+ case 'M':
+ configtestonly = 1;
+ new = (char **)apr_array_push(ap_server_config_defines);
+ *new = "DUMP_MODULES";
+ break;
+
+ case 'h':
+ case '?':
+ usage(process);
+ }
+ }
+
+ /* bad cmdline option? then we die */
+ if (rv != APR_EOF || opt->ind < opt->argc) {
+ usage(process);
+ }
+
+ apr_pool_create(&plog, pglobal);
+ apr_pool_tag(plog, "plog");
+ apr_pool_create(&ptemp, pconf);
+ apr_pool_tag(ptemp, "ptemp");
+
+ /* Note that we preflight the config file once
+ * before reading it _again_ in the main loop.
+ * This allows things, log files configuration
+ * for example, to settle down.
+ */
+
+ ap_server_root = def_server_root;
+ if (temp_error_log) {
+ ap_replace_stderr_log(process->pool, temp_error_log);
+ }
+ server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
+ if (!server_conf) {
+ destroy_and_exit_process(process, 1);
+ }
+
+ if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0,
+ NULL, "Pre-configuration failed");
+ destroy_and_exit_process(process, 1);
+ }
+
+ rv = ap_process_config_tree(server_conf, ap_conftree,
+ process->pconf, ptemp);
+ if (rv == OK) {
+ ap_fixup_virtual_hosts(pconf, server_conf);
+ ap_fini_vhost_config(pconf, server_conf);
+ apr_hook_sort_all();
+
+ if (configtestonly) {
+ ap_run_test_config(pconf, server_conf);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK");
+ destroy_and_exit_process(process, 0);
+ }
+ }
+
+ signal_server = APR_RETRIEVE_OPTIONAL_FN(ap_signal_server);
+ if (signal_server) {
+ int exit_status;
+
+ if (signal_server(&exit_status, pconf) != 0) {
+ destroy_and_exit_process(process, exit_status);
+ }
+ }
+
+ /* If our config failed, deal with that here. */
+ if (rv != OK) {
+ destroy_and_exit_process(process, 1);
+ }
+
+ apr_pool_clear(plog);
+
+ if ( ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
+ 0, NULL, "Unable to open logs");
+ destroy_and_exit_process(process, 1);
+ }
+
+ if ( ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0,
+ NULL, "Configuration Failed");
+ destroy_and_exit_process(process, 1);
+ }
+
+ apr_pool_destroy(ptemp);
+
+ for (;;) {
+ apr_hook_deregister_all();
+ apr_pool_clear(pconf);
+
+ for (mod = ap_prelinked_modules; *mod != NULL; mod++) {
+ ap_register_hooks(*mod, pconf);
+ }
+
+ /* This is a hack until we finish the code so that it only reads
+ * the config file once and just operates on the tree already in
+ * memory. rbb
+ */
+ ap_conftree = NULL;
+ apr_pool_create(&ptemp, pconf);
+ apr_pool_tag(ptemp, "ptemp");
+ ap_server_root = def_server_root;
+ server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
+ if (!server_conf) {
+ destroy_and_exit_process(process, 1);
+ }
+
+ if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
+ 0, NULL, "Pre-configuration failed");
+ destroy_and_exit_process(process, 1);
+ }
+
+ if (ap_process_config_tree(server_conf, ap_conftree, process->pconf,
+ ptemp) != OK) {
+ destroy_and_exit_process(process, 1);
+ }
+ ap_fixup_virtual_hosts(pconf, server_conf);
+ ap_fini_vhost_config(pconf, server_conf);
+ apr_hook_sort_all();
+ apr_pool_clear(plog);
+ if (ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
+ 0, NULL, "Unable to open logs");
+ destroy_and_exit_process(process, 1);
+ }
+
+ if (ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
+ 0, NULL, "Configuration Failed");
+ destroy_and_exit_process(process, 1);
+ }
+
+ apr_pool_destroy(ptemp);
+ apr_pool_lock(pconf, 1);
+
+ ap_run_optional_fn_retrieve();
+
+ if (ap_mpm_run(pconf, plog, server_conf))
+ break;
+
+ apr_pool_lock(pconf, 0);
+ }
+
+ apr_pool_lock(pconf, 0);
+ destroy_and_exit_process(process, 0);
+
+ return 0; /* Termination 'ok' */
+}
+
+#ifdef AP_USING_AUTOCONF
+/* This ugly little hack pulls any function referenced in exports.c into
+ * the web server. exports.c is generated during the build, and it
+ * has all of the APR functions specified by the apr/apr.exports and
+ * apr-util/aprutil.exports files.
+ */
+const void *suck_in_APR(void);
+const void *suck_in_APR(void)
+{
+ extern const void *ap_ugly_hack;
+
+ return ap_ugly_hack;
+}
+#endif
diff --git a/server/mpm/MPM.NAMING b/server/mpm/MPM.NAMING
new file mode 100644
index 00000000..83c0694d
--- /dev/null
+++ b/server/mpm/MPM.NAMING
@@ -0,0 +1,15 @@
+
+The following MPMs currently exist:
+
+ prefork ....... Multi Process Model with Preforking (Apache 1.3)
+ perchild ...... Multi Process Model with Threading.
+ Constant number of processes, variable number of threads
+ each child process can have a different uid/gid.
+ mpmt_os2 ...... Multi Process Model with Threading on OS/2
+ Constant number of processes, variable number of threads.
+ One acceptor thread per process, multiple workers threads.
+ winnt ......... Single Process Model with Threading on Windows NT
+ worker ........ Multi Process model with threads. One acceptor thread,
+ multiple worker threads.
+ netware ....... Multi-threaded MPM for Netware
+ beos .......... Single Process Model with Threading on BeOS
diff --git a/server/mpm/Makefile.in b/server/mpm/Makefile.in
new file mode 100644
index 00000000..2decbde6
--- /dev/null
+++ b/server/mpm/Makefile.in
@@ -0,0 +1,4 @@
+
+SUBDIRS = $(MPM_SUBDIR_NAME)
+
+include $(top_builddir)/build/rules.mk
diff --git a/server/mpm/beos/Makefile.in b/server/mpm/beos/Makefile.in
new file mode 100644
index 00000000..3f88b041
--- /dev/null
+++ b/server/mpm/beos/Makefile.in
@@ -0,0 +1,5 @@
+
+LTLIBRARY_NAME = libbeos.la
+LTLIBRARY_SOURCES = beos.c
+
+include $(top_srcdir)/build/ltlib.mk
diff --git a/server/mpm/beos/beos.c b/server/mpm/beos/beos.c
new file mode 100644
index 00000000..b7e4671b
--- /dev/null
+++ b/server/mpm/beos/beos.c
@@ -0,0 +1,1207 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* The BeOS MPM!
+ *
+ * This is a single process, with multiple worker threads.
+ *
+ * Under testing I found that given the inability of BeOS to handle threads
+ * and forks it didn't make sense to try and have a set of "children" threads
+ * that spawned the "worker" threads, so just missed out the middle mand and
+ * somehow arrived here.
+ *
+ * For 2.1 this has been rewritten to have simpler logic, though there is still
+ * some simplification that can be done. It's still a work in progress!
+ *
+ * TODO Items
+ *
+ * - on exit most worker threads segfault trying to access a kernel page.
+ */
+
+#define CORE_PRIVATE
+
+#include <kernel/OS.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <signal.h>
+
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h" /* for read_config */
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "ap_mpm.h"
+#include "beosd.h"
+#include "ap_listen.h"
+#include "scoreboard.h"
+#include "mpm_common.h"
+#include "mpm.h"
+#include "mpm_default.h"
+#include "apr_thread_mutex.h"
+#include "apr_poll.h"
+
+extern int _kset_fd_limit_(int num);
+
+/* Limit on the total --- clients will be locked out if more servers than
+ * this are needed. It is intended solely to keep the server from crashing
+ * when things get out of hand.
+ *
+ * We keep a hard maximum number of servers, for two reasons:
+ * 1) in case something goes seriously wrong, we want to stop the server starting
+ * threads ad infinitum and crashing the server (remember that BeOS has a 192
+ * thread per team limit).
+ * 2) it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+
+/* we only ever have 1 main process running... */
+#define HARD_SERVER_LIMIT 1
+
+/* Limit on the threads per process. Clients will be locked out if more than
+ * this * HARD_SERVER_LIMIT are needed.
+ *
+ * We keep this for one reason it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifdef NO_THREADS
+#define HARD_THREAD_LIMIT 1
+#endif
+#ifndef HARD_THREAD_LIMIT
+#define HARD_THREAD_LIMIT 50
+#endif
+
+/*
+ * Actual definitions of config globals
+ */
+
+static int ap_threads_to_start=0;
+static int ap_max_requests_per_thread = 0;
+static int min_spare_threads=0;
+static int max_spare_threads=0;
+static int ap_thread_limit=0;
+static int num_listening_sockets = 0;
+static int mpm_state = AP_MPMQ_STARTING;
+apr_thread_mutex_t *accept_mutex = NULL;
+
+static apr_pool_t *pconf; /* Pool for config stuff */
+
+static int server_pid;
+
+
+/*
+ * The max child slot ever assigned, preserved across restarts. Necessary
+ * to deal with MaxClients changes across AP_SIG_GRACEFUL restarts. We use
+ * this value to optimize routines that have to scan the entire scoreboard.
+ */
+int ap_max_child_assigned = -1;
+int ap_max_threads_limit = -1;
+
+static apr_socket_t *udp_sock;
+static apr_sockaddr_t *udp_sa;
+
+server_rec *ap_server_conf;
+
+/* one_process */
+static int one_process = 0;
+
+#ifdef DEBUG_SIGSTOP
+int raise_sigstop_flags;
+#endif
+
+static void check_restart(void *data);
+
+/* When a worker thread gets to the end of it's life it dies with an
+ * exit value of the code supplied to this function. The thread has
+ * already had check_restart() registered to be called when dying, so
+ * we don't concern ourselves with restarting at all here. We do however
+ * mark the scoreboard slot as belonging to a dead server and zero out
+ * it's thread_id.
+ *
+ * TODO - use the status we set to determine if we need to restart the
+ * thread.
+ */
+static void clean_child_exit(int code, int slot)
+{
+ (void) ap_update_child_status_from_indexes(0, slot, SERVER_DEAD,
+ (request_rec*)NULL);
+ ap_scoreboard_image->servers[0][slot].tid = 0;
+ exit_thread(code);
+}
+
+
+/*****************************************************************
+ * Connection structures and accounting...
+ */
+
+/* volatile just in case */
+static int volatile shutdown_pending;
+static int volatile restart_pending;
+static int volatile is_graceful;
+static int volatile child_fatal;
+ap_generation_t volatile ap_my_generation = 0;
+
+/*
+ * ap_start_shutdown() and ap_start_restart(), below, are a first stab at
+ * functions to initiate shutdown or restart without relying on signals.
+ * Previously this was initiated in sig_term() and restart() signal handlers,
+ * but we want to be able to start a shutdown/restart from other sources --
+ * e.g. on Win32, from the service manager. Now the service manager can
+ * call ap_start_shutdown() or ap_start_restart() as appropiate. Note that
+ * these functions can also be called by the child processes, since global
+ * variables are no longer used to pass on the required action to the parent.
+ *
+ * These should only be called from the parent process itself, since the
+ * parent process will use the shutdown_pending and restart_pending variables
+ * to determine whether to shutdown or restart. The child process should
+ * call signal_parent() directly to tell the parent to die -- this will
+ * cause neither of those variable to be set, which the parent will
+ * assume means something serious is wrong (which it will be, for the
+ * child to force an exit) and so do an exit anyway.
+ */
+
+static void ap_start_shutdown(void)
+{
+ /* If the user tries to shut us down twice in quick succession then we
+ * may well get triggered while we are working through previous attempt
+ * to shutdown. We won't worry about even reporting it as it seems a little
+ * pointless.
+ */
+ if (shutdown_pending == 1)
+ return;
+
+ shutdown_pending = 1;
+}
+
+/* do a graceful restart if graceful == 1 */
+static void ap_start_restart(int graceful)
+{
+ if (restart_pending == 1) {
+ /* Probably not an error - don't bother reporting it */
+ return;
+ }
+ restart_pending = 1;
+ is_graceful = graceful;
+}
+
+/* sig_coredump attempts to handle all the potential signals we
+ * may get that should result in a core dump. This is called from
+ * the signal handler routine, so when we enter we are essentially blocked
+ * on the signal. Once we exit we will allow the signal to be processed by
+ * system, which may or may not produce a .core file. All this function does
+ * is try and respect the users wishes about where that file should be
+ * located (chdir) and then signal the parent with the signal.
+ *
+ * If we called abort() the parent would only see SIGABRT which doesn't provide
+ * as much information.
+ */
+static void sig_coredump(int sig)
+{
+ chdir(ap_coredump_dir);
+ signal(sig, SIG_DFL);
+ kill(server_pid, sig);
+}
+
+static void sig_term(int sig)
+{
+ ap_start_shutdown();
+}
+
+static void restart(int sig)
+{
+ ap_start_restart(sig == AP_SIG_GRACEFUL);
+}
+
+/* Handle queries about our inner workings... */
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch(query_code){
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = ap_max_child_assigned;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_DYNAMIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_NOT_SUPPORTED;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = HARD_SERVER_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = HARD_THREAD_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_THREADS:
+ *result = HARD_THREAD_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_THREADS:
+ *result = max_spare_threads;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_THREADS:
+ *result = min_spare_threads;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_thread;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_DAEMONS:
+ *result = HARD_SERVER_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_MPM_STATE:
+ *result = mpm_state;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+/* This accepts a connection and allows us to handle the error codes better than
+ * the previous code, while also making it more obvious.
+ */
+static apr_status_t beos_accept(void **accepted, ap_listen_rec *lr, apr_pool_t *ptrans)
+{
+ apr_socket_t *csd;
+ apr_status_t status;
+ int sockdes;
+
+ *accepted = NULL;
+ status = apr_socket_accept(&csd, lr->sd, ptrans);
+ if (status == APR_SUCCESS) {
+ *accepted = csd;
+ apr_os_sock_get(&sockdes, csd);
+ return status;
+ }
+
+ if (APR_STATUS_IS_EINTR(status)) {
+ return status;
+ }
+ /* This switch statement provides us with better error details. */
+ switch (status) {
+#ifdef ECONNABORTED
+ case ECONNABORTED:
+#endif
+#ifdef ETIMEDOUT
+ case ETIMEDOUT:
+#endif
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH:
+#endif
+#ifdef ENETUNREACH
+ case ENETUNREACH:
+#endif
+ break;
+#ifdef ENETDOWN
+ case ENETDOWN:
+ /*
+ * When the network layer has been shut down, there
+ * is not much use in simply exiting: the parent
+ * would simply re-create us (and we'd fail again).
+ * Use the CHILDFATAL code to tear the server down.
+ * @@@ Martin's idea for possible improvement:
+ * A different approach would be to define
+ * a new APEXIT_NETDOWN exit code, the reception
+ * of which would make the parent shutdown all
+ * children, then idle-loop until it detected that
+ * the network is up again, and restart the children.
+ * Ben Hyde noted that temporary ENETDOWN situations
+ * occur in mobile IP.
+ */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf,
+ "apr_socket_accept: giving up.");
+ return APR_EGENERAL;
+#endif /*ENETDOWN*/
+
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf,
+ "apr_socket_accept: (client socket)");
+ return APR_EGENERAL;
+ }
+ return status;
+}
+
+static void tell_workers_to_exit(void)
+{
+ apr_size_t len;
+ int i = 0;
+ for (i = 0 ; i < ap_max_child_assigned; i++){
+ len = 4;
+ if (apr_socket_sendto(udp_sock, udp_sa, 0, "die!", &len) != APR_SUCCESS)
+ break;
+ }
+}
+
+static void set_signals(void)
+{
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ /* The first batch get handled by sig_coredump */
+ if (!one_process) {
+ sa.sa_handler = sig_coredump;
+
+ if (sigaction(SIGSEGV, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGSEGV)");
+ if (sigaction(SIGBUS, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGBUS)");
+ if (sigaction(SIGABRT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGABRT)");
+ if (sigaction(SIGILL, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGILL)");
+ sa.sa_flags = 0;
+ }
+
+ /* These next two are handled by sig_term */
+ sa.sa_handler = sig_term;
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)");
+
+ /* We ignore SIGPIPE */
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGPIPE)");
+
+ /* we want to ignore HUPs and AP_SIG_GRACEFUL while we're busy
+ * processing one */
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, AP_SIG_GRACEFUL);
+ sa.sa_handler = restart;
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
+ if (sigaction(AP_SIG_GRACEFUL, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(" AP_SIG_GRACEFUL_STRING ")");
+}
+
+/*****************************************************************
+ * Here follows a long bunch of generic server bookkeeping stuff...
+ */
+
+int ap_graceful_stop_signalled(void)
+{
+ return is_graceful;
+}
+
+/* This is the thread that actually does all the work. */
+static int32 worker_thread(void *dummy)
+{
+ int worker_slot = (int)dummy;
+ apr_allocator_t *allocator;
+ apr_bucket_alloc_t *bucket_alloc;
+ apr_status_t rv = APR_EINIT;
+ int last_poll_idx = 0;
+ sigset_t sig_mask;
+ int requests_this_child = 0;
+ apr_pollset_t *pollset = NULL;
+ ap_listen_rec *lr = NULL;
+ ap_sb_handle_t *sbh = NULL;
+ int i;
+ /* each worker thread is in control of its own destiny...*/
+ int this_worker_should_exit = 0;
+ /* We have 2 pools that we create/use throughout the lifetime of this
+ * worker. The first and longest lived is the pworker pool. From
+ * this we create the ptrans pool, the lifetime of which is the same
+ * as each connection and is reset prior to each attempt to
+ * process a connection.
+ */
+ apr_pool_t *ptrans = NULL;
+ apr_pool_t *pworker = NULL;
+
+ mpm_state = AP_MPMQ_STARTING; /* for benefit of any hooks that run as this
+ * child initializes
+ */
+
+ on_exit_thread(check_restart, (void*)worker_slot);
+
+ /* block the signals for this thread only if we're not running as a
+ * single process.
+ */
+ if (!one_process) {
+ sigfillset(&sig_mask);
+ sigprocmask(SIG_BLOCK, &sig_mask, NULL);
+ }
+
+ /* Each worker thread is fully in control of it's destinay and so
+ * to allow each thread to handle the lifetime of it's own resources
+ * we create and use a subcontext for every thread.
+ * The subcontext is a child of the pconf pool.
+ */
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ apr_pool_create_ex(&pworker, pconf, NULL, allocator);
+ apr_allocator_owner_set(allocator, pworker);
+
+ apr_pool_create(&ptrans, pworker);
+ apr_pool_tag(ptrans, "transaction");
+
+ ap_create_sb_handle(&sbh, pworker, 0, worker_slot);
+ (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL);
+
+ /* We add an extra socket here as we add the udp_sock we use for signalling
+ * death. This gets added after the others.
+ */
+ apr_pollset_create(&pollset, num_listening_sockets + 1, pworker, 0);
+
+ for (lr = ap_listeners, i = num_listening_sockets; i--; lr = lr->next) {
+ apr_pollfd_t pfd = {0};
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = lr->sd;
+ pfd.reqevents = APR_POLLIN;
+ pfd.client_data = lr;
+
+ apr_pollset_add(pollset, &pfd);
+ }
+ {
+ apr_pollfd_t pfd = {0};
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = udp_sock;
+ pfd.reqevents = APR_POLLIN;
+
+ apr_pollset_add(pollset, &pfd);
+ }
+
+ bucket_alloc = apr_bucket_alloc_create(pworker);
+
+ mpm_state = AP_MPMQ_RUNNING;
+
+ while (!this_worker_should_exit) {
+ conn_rec *current_conn;
+ void *csd;
+
+ /* (Re)initialize this child to a pre-connection state. */
+ apr_pool_clear(ptrans);
+
+ if ((ap_max_requests_per_thread > 0
+ && requests_this_child++ >= ap_max_requests_per_thread))
+ clean_child_exit(0, worker_slot);
+
+ (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL);
+
+ apr_thread_mutex_lock(accept_mutex);
+
+ /* We always (presently) have at least 2 sockets we listen on, so
+ * we don't have the ability for a fast path for a single socket
+ * as some MPM's allow :(
+ */
+ for (;;) {
+ apr_int32_t numdesc = 0;
+ const apr_pollfd_t *pdesc = NULL;
+
+ rv = apr_pollset_poll(pollset, -1, &numdesc, &pdesc);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(rv)) {
+ if (one_process && shutdown_pending)
+ return;
+ continue;
+ }
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv,
+ ap_server_conf, "apr_pollset_poll: (listen)");
+ clean_child_exit(1, worker_slot);
+ }
+ /* We can always use pdesc[0], but sockets at position N
+ * could end up completely starved of attention in a very
+ * busy server. Therefore, we round-robin across the
+ * returned set of descriptors. While it is possible that
+ * the returned set of descriptors might flip around and
+ * continue to starve some sockets, we happen to know the
+ * internal pollset implementation retains ordering
+ * stability of the sockets. Thus, the round-robin should
+ * ensure that a socket will eventually be serviced.
+ */
+ if (last_poll_idx >= numdesc)
+ last_poll_idx = 0;
+
+ /* Grab a listener record from the client_data of the poll
+ * descriptor, and advance our saved index to round-robin
+ * the next fetch.
+ *
+ * ### hmm... this descriptor might have POLLERR rather
+ * ### than POLLIN
+ */
+
+ lr = pdesc[last_poll_idx++].client_data;
+
+ /* The only socket we add without client_data is the first, the UDP socket
+ * we listen on for restart signals. If we've therefore gotten a hit on that
+ * listener lr will be NULL here and we know we've been told to die.
+ * Before we jump to the end of the while loop with this_worker_should_exit
+ * set to 1 (causing us to exit normally we hope) we release the accept_mutex
+ * as we want every thread to go through this same routine :)
+ * Bit of a hack, but compared to what I had before...
+ */
+ if (lr == NULL) {
+ this_worker_should_exit = 1;
+ apr_thread_mutex_unlock(accept_mutex);
+ goto got_a_black_spot;
+ }
+ goto got_fd;
+ }
+got_fd:
+ /* Run beos_accept to accept the connection and set things up to
+ * allow us to process it. We always release the accept_lock here,
+ * even if we failt o accept as otherwise we'll starve other workers
+ * which would be bad.
+ */
+ rv = beos_accept(&csd, lr, ptrans);
+ apr_thread_mutex_unlock(accept_mutex);
+
+ if (rv == APR_EGENERAL) {
+ /* resource shortage or should-not-occur occured */
+ clean_child_exit(1, worker_slot);
+ } else if (rv != APR_SUCCESS)
+ continue;
+
+ current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, worker_slot, sbh, bucket_alloc);
+ if (current_conn) {
+ ap_process_connection(current_conn, csd);
+ ap_lingering_close(current_conn);
+ }
+
+ if (ap_my_generation !=
+ ap_scoreboard_image->global->running_generation) { /* restart? */
+ /* yeah, this could be non-graceful restart, in which case the
+ * parent will kill us soon enough, but why bother checking?
+ */
+ this_worker_should_exit = 1;
+ }
+got_a_black_spot:
+ }
+
+ apr_pool_destroy(ptrans);
+ apr_pool_destroy(pworker);
+
+ clean_child_exit(0, worker_slot);
+}
+
+static int make_worker(int slot)
+{
+ thread_id tid;
+
+ if (slot + 1 > ap_max_child_assigned)
+ ap_max_child_assigned = slot + 1;
+
+ (void) ap_update_child_status_from_indexes(0, slot, SERVER_STARTING, (request_rec*)NULL);
+
+ if (one_process) {
+ set_signals();
+ ap_scoreboard_image->parent[0].pid = getpid();
+ ap_scoreboard_image->servers[0][slot].tid = find_thread(NULL);
+ return 0;
+ }
+
+ tid = spawn_thread(worker_thread, "apache_worker", B_NORMAL_PRIORITY,
+ (void *)slot);
+ if (tid < B_NO_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, NULL,
+ "spawn_thread: Unable to start a new thread");
+ /* In case system resources are maxed out, we don't want
+ * Apache running away with the CPU trying to fork over and
+ * over and over again.
+ */
+ (void) ap_update_child_status_from_indexes(0, slot, SERVER_DEAD,
+ (request_rec*)NULL);
+
+ sleep(10);
+ return -1;
+ }
+ resume_thread(tid);
+
+ ap_scoreboard_image->servers[0][slot].tid = tid;
+ return 0;
+}
+
+/* When a worker thread exits, this function is called. If we are not in
+ * a shutdown situation then we restart the worker in the slot that was
+ * just vacated.
+ */
+static void check_restart(void *data)
+{
+ if (!restart_pending && !shutdown_pending) {
+ int slot = (int)data;
+ make_worker(slot);
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
+ "spawning a new worker thread in slot %d", slot);
+ }
+}
+
+/* Start number_to_start children. This is used to start both the
+ * initial 'pool' of workers but also to replace existing workers who
+ * have reached the end of their time. It walks through the scoreboard to find
+ * an empty slot and starts the worker thread in that slot.
+ */
+static void startup_threads(int number_to_start)
+{
+ int i;
+
+ for (i = 0; number_to_start && i < ap_thread_limit; ++i) {
+ if (ap_scoreboard_image->servers[0][i].tid)
+ continue;
+
+ if (make_worker(i) < 0)
+ break;
+
+ --number_to_start;
+ }
+}
+
+
+/*
+ * spawn_rate is the number of children that will be spawned on the
+ * next maintenance cycle if there aren't enough idle servers. It is
+ * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
+ * without the need to spawn.
+ */
+static int spawn_rate = 1;
+#ifndef MAX_SPAWN_RATE
+#define MAX_SPAWN_RATE (32)
+#endif
+static int hold_off_on_exponential_spawning;
+
+static void perform_idle_server_maintenance(void)
+{
+ int i;
+ int free_length;
+ int free_slots[MAX_SPAWN_RATE];
+ int last_non_dead = -1;
+
+ /* initialize the free_list */
+ free_length = 0;
+
+ for (i = 0; i < ap_thread_limit; ++i) {
+ if (ap_scoreboard_image->servers[0][i].tid == 0) {
+ if (free_length < spawn_rate) {
+ free_slots[free_length] = i;
+ ++free_length;
+ }
+ }
+ else {
+ last_non_dead = i;
+ }
+
+ if (i >= ap_max_child_assigned && free_length >= spawn_rate) {
+ break;
+ }
+ }
+ ap_max_child_assigned = last_non_dead + 1;
+
+ if (free_length > 0) {
+ for (i = 0; i < free_length; ++i) {
+ make_worker(free_slots[i]);
+ }
+ /* the next time around we want to spawn twice as many if this
+ * wasn't good enough, but not if we've just done a graceful
+ */
+ if (hold_off_on_exponential_spawning) {
+ --hold_off_on_exponential_spawning;
+ } else if (spawn_rate < MAX_SPAWN_RATE) {
+ spawn_rate *= 2;
+ }
+ } else {
+ spawn_rate = 1;
+ }
+}
+
+static void server_main_loop(int remaining_threads_to_start)
+{
+ int child_slot;
+ apr_exit_why_e exitwhy;
+ int status;
+ apr_proc_t pid;
+ int i;
+
+ while (!restart_pending && !shutdown_pending) {
+
+ ap_wait_or_timeout(&exitwhy, &status, &pid, pconf);
+
+ if (pid.pid >= 0) {
+ if (ap_process_child_status(&pid, exitwhy, status) == APEXIT_CHILDFATAL) {
+ shutdown_pending = 1;
+ child_fatal = 1;
+ return;
+ }
+ /* non-fatal death... note that it's gone in the scoreboard. */
+ child_slot = -1;
+ for (i = 0; i < ap_max_child_assigned; ++i) {
+ if (ap_scoreboard_image->servers[0][i].tid == pid.pid) {
+ child_slot = i;
+ break;
+ }
+ }
+ if (child_slot >= 0) {
+ ap_scoreboard_image->servers[0][child_slot].tid = 0;
+ (void) ap_update_child_status_from_indexes(0, child_slot,
+ SERVER_DEAD,
+ (request_rec*)NULL);
+
+ if (remaining_threads_to_start
+ && child_slot < ap_thread_limit) {
+ /* we're still doing a 1-for-1 replacement of dead
+ * children with new children
+ */
+ make_worker(child_slot);
+ --remaining_threads_to_start;
+ }
+/* TODO
+#if APR_HAS_OTHER_CHILD
+ }
+ else if (apr_proc_other_child_refresh(&pid, status) == 0) {
+#endif
+*/
+ }
+ else if (is_graceful) {
+ /* Great, we've probably just lost a slot in the
+ * scoreboard. Somehow we don't know about this
+ * child.
+ */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf,
+ "long lost child came home! (pid %ld)", pid.pid);
+ }
+
+ /* Don't perform idle maintenance when a child dies,
+ * only do it when there's a timeout. Remember only a
+ * finite number of children can die, and it's pretty
+ * pathological for a lot to die suddenly.
+ */
+ continue;
+ }
+ else if (remaining_threads_to_start) {
+ /* we hit a 1 second timeout in which none of the previous
+ * generation of children needed to be reaped... so assume
+ * they're all done, and pick up the slack if any is left.
+ */
+ startup_threads(remaining_threads_to_start);
+ remaining_threads_to_start = 0;
+ /* In any event we really shouldn't do the code below because
+ * few of the servers we just started are in the IDLE state
+ * yet, so we'd mistakenly create an extra server.
+ */
+ continue;
+ }
+ perform_idle_server_maintenance();
+ }
+}
+
+/* This is called to not only setup and run for the initial time, but also
+ * when we've asked for a restart. This means it must be able to handle both
+ * situations. It also means that when we exit here we should have tidied
+ * up after ourselves fully.
+ */
+int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
+{
+ int remaining_threads_to_start, i,j;
+ apr_status_t rv;
+ ap_listen_rec *lr;
+ pconf = _pconf;
+ ap_server_conf = s;
+
+ /* Increase the available pool of fd's. This code from
+ * Joe Kloss <joek@be.com>
+ */
+ if( FD_SETSIZE > 128 && (i = _kset_fd_limit_( 128 )) < 0 ){
+ ap_log_error(APLOG_MARK, APLOG_ERR, i, s,
+ "could not set FD_SETSIZE (_kset_fd_limit_ failed)");
+ }
+
+ /* BeOS R5 doesn't support pipes on select() calls, so we use a
+ * UDP socket as these are supported in both R5 and BONE. If we only cared
+ * about BONE we'd use a pipe, but there it is.
+ * As we have UDP support in APR, now use the APR functions and check all the
+ * return values...
+ */
+ if (apr_sockaddr_info_get(&udp_sa, "127.0.0.1", APR_UNSPEC, 7772, 0, _pconf)
+ != APR_SUCCESS){
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, s,
+ "couldn't create control socket information, shutting down");
+ return 1;
+ }
+ if (apr_socket_create(&udp_sock, udp_sa->family, SOCK_DGRAM, 0,
+ _pconf) != APR_SUCCESS){
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, s,
+ "couldn't create control socket, shutting down");
+ return 1;
+ }
+ if (apr_socket_bind(udp_sock, udp_sa) != APR_SUCCESS){
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, s,
+ "couldn't bind UDP socket!");
+ return 1;
+ }
+
+ if ((num_listening_sockets = ap_setup_listeners(ap_server_conf)) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
+ "no listening sockets available, shutting down");
+ return 1;
+ }
+
+ ap_log_pid(pconf, ap_pid_fname);
+
+ /*
+ * Create our locks...
+ */
+
+ /* accept_mutex
+ * used to lock around select so we only have one thread
+ * in select at a time
+ */
+ rv = apr_thread_mutex_create(&accept_mutex, 0, pconf);
+ if (rv != APR_SUCCESS) {
+ /* tsch tsch, can't have more than one thread in the accept loop
+ at a time so we need to fall on our sword... */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
+ "Couldn't create accept lock");
+ return 1;
+ }
+
+ /*
+ * Startup/shutdown...
+ */
+
+ if (!is_graceful) {
+ /* setup the scoreboard shared memory */
+ if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
+ return 1;
+ }
+
+ for (i = 0; i < HARD_SERVER_LIMIT; i++) {
+ ap_scoreboard_image->parent[i].pid = 0;
+ for (j = 0;j < HARD_THREAD_LIMIT; j++)
+ ap_scoreboard_image->servers[i][j].tid = 0;
+ }
+ }
+
+ if (HARD_SERVER_LIMIT == 1)
+ ap_scoreboard_image->parent[0].pid = getpid();
+
+ set_signals();
+
+ /* Sanity checks to avoid thrashing... */
+ if (max_spare_threads < min_spare_threads )
+ max_spare_threads = min_spare_threads;
+
+ /* If we're doing a graceful_restart then we're going to see a lot
+ * of threads exiting immediately when we get into the main loop
+ * below (because we just sent them AP_SIG_GRACEFUL). This happens
+ * pretty rapidly... and for each one that exits we'll start a new one
+ * until we reach at least threads_min_free. But we may be permitted to
+ * start more than that, so we'll just keep track of how many we're
+ * supposed to start up without the 1 second penalty between each fork.
+ */
+ remaining_threads_to_start = ap_threads_to_start;
+ /* sanity check on the number to start... */
+ if (remaining_threads_to_start > ap_thread_limit) {
+ remaining_threads_to_start = ap_thread_limit;
+ }
+
+ /* If we're doing the single process thing or we're in a graceful_restart
+ * then we don't start threads here.
+ * if we're in one_process mode we don't want to start threads
+ * do we??
+ */
+ if (!is_graceful && !one_process) {
+ startup_threads(remaining_threads_to_start);
+ remaining_threads_to_start = 0;
+ } else {
+ /* give the system some time to recover before kicking into
+ * exponential mode */
+ hold_off_on_exponential_spawning = 10;
+ }
+
+ /*
+ * record that we've entered the world !
+ */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+
+ restart_pending = shutdown_pending = 0;
+
+ mpm_state = AP_MPMQ_RUNNING;
+
+ /* We sit in the server_main_loop() until we somehow manage to exit. When
+ * we do, we need to kill the workers we have, so we start by using the
+ * tell_workers_to_exit() function, but as it sometimes takes a short while
+ * to accomplish this we have a pause builtin to allow them the chance to
+ * gracefully exit.
+ */
+ if (!one_process) {
+ server_main_loop(remaining_threads_to_start);
+ tell_workers_to_exit();
+ snooze(1000000);
+ } else {
+ worker_thread((void*)0);
+ }
+ mpm_state = AP_MPMQ_STOPPING;
+
+ /* close the UDP socket we've been using... */
+ apr_socket_close(udp_sock);
+
+ if ((one_process || shutdown_pending) && !child_fatal) {
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+ if ( pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "removed PID file %s (pid=%ld)", pidfile,
+ (long)getpid());
+ }
+
+ if (one_process) {
+ return 1;
+ }
+
+ /*
+ * If we get here we're shutting down...
+ */
+ if (shutdown_pending) {
+ /* Time to gracefully shut down:
+ * Kill child processes, tell them to call child_exit, etc...
+ */
+ if (beosd_killpg(getpgrp(), SIGTERM) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "killpg SIGTERM");
+
+ /* use ap_reclaim_child_processes starting with SIGTERM */
+ ap_reclaim_child_processes(1);
+
+ if (!child_fatal) { /* already recorded */
+ /* record the shutdown in the log */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught SIGTERM, shutting down");
+ }
+
+ return 1;
+ }
+
+ /* we've been told to restart */
+ signal(SIGHUP, SIG_IGN);
+
+ if (is_graceful) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ AP_SIG_GRACEFUL_STRING " received. Doing graceful restart");
+ } else {
+ /* Kill 'em all. Since the child acts the same on the parents SIGTERM
+ * and a SIGHUP, we may as well use the same signal, because some user
+ * pthreads are stealing signals from us left and right.
+ */
+
+ ap_reclaim_child_processes(1); /* Start with SIGTERM */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "SIGHUP received. Attempting to restart");
+ }
+
+ /* just before we go, tidy up the lock we created to prevent a
+ * potential leak of semaphores...
+ */
+ apr_thread_mutex_destroy(accept_mutex);
+
+ return 0;
+}
+
+static int beos_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ static int restart_num = 0;
+ int no_detach, debug, foreground;
+ apr_status_t rv;
+
+ mpm_state = AP_MPMQ_STARTING;
+
+ debug = ap_exists_config_define("DEBUG");
+
+ if (debug) {
+ foreground = one_process = 1;
+ no_detach = 0;
+ }
+ else
+ {
+ one_process = ap_exists_config_define("ONE_PROCESS");
+ no_detach = ap_exists_config_define("NO_DETACH");
+ foreground = ap_exists_config_define("FOREGROUND");
+ }
+
+ /* sigh, want this only the second time around */
+ if (restart_num++ == 1) {
+ is_graceful = 0;
+
+ if (!one_process && !foreground) {
+ rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND
+ : APR_PROC_DETACH_DAEMONIZE);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "apr_proc_detach failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ server_pid = getpid();
+ }
+
+ beosd_pre_config();
+ ap_listen_pre_config();
+ ap_threads_to_start = DEFAULT_START_THREADS;
+ min_spare_threads = DEFAULT_MIN_FREE_THREADS;
+ max_spare_threads = DEFAULT_MAX_FREE_THREADS;
+ ap_thread_limit = HARD_THREAD_LIMIT;
+ ap_pid_fname = DEFAULT_PIDLOG;
+ ap_max_requests_per_thread = DEFAULT_MAX_REQUESTS_PER_THREAD;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+
+ apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir));
+
+ return OK;
+}
+
+static void beos_hooks(apr_pool_t *p)
+{
+ one_process = 0;
+
+ ap_hook_pre_config(beos_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const char *set_threads_to_start(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_to_start = atoi(arg);
+ if (ap_threads_to_start < 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "StartThreads set to a value less than 0, reset to 1");
+ ap_threads_to_start = 1;
+ }
+ return NULL;
+}
+
+static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ min_spare_threads = atoi(arg);
+ if (min_spare_threads <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: detected MinSpareThreads set to non-positive.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Resetting to 1 to avoid almost certain Apache failure.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Please read the documentation.");
+ min_spare_threads = 1;
+ }
+
+ return NULL;
+}
+
+static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ max_spare_threads = atoi(arg);
+ return NULL;
+}
+
+static const char *set_threads_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_thread_limit = atoi(arg);
+ if (ap_thread_limit > HARD_THREAD_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients of %d exceeds compile time limit "
+ "of %d servers,", ap_thread_limit, HARD_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering MaxClients to %d. To increase, please "
+ "see the", HARD_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " HARD_THREAD_LIMIT define in server/mpm/beos/mpm_default.h.");
+ ap_thread_limit = HARD_THREAD_LIMIT;
+ }
+ else if (ap_thread_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require MaxClients > 0, setting to %d", HARD_THREAD_LIMIT);
+ ap_thread_limit = HARD_THREAD_LIMIT;
+ }
+ return NULL;
+}
+
+static const char *set_max_requests_per_thread (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_max_requests_per_thread = atoi(arg);
+ if (ap_max_requests_per_thread < 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxRequestsPerThread was set below 0"
+ "reset to 0, but this may not be what you want.");
+ ap_max_requests_per_thread = 0;
+ }
+
+ return NULL;
+}
+
+static const command_rec beos_cmds[] = {
+BEOS_DAEMON_COMMANDS,
+LISTEN_COMMANDS,
+AP_INIT_TAKE1( "StartThreads", set_threads_to_start, NULL, RSRC_CONF,
+ "Number of threads to launch at server startup"),
+AP_INIT_TAKE1( "MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
+ "Minimum number of idle children, to handle request spikes"),
+AP_INIT_TAKE1( "MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
+ "Maximum number of idle children" ),
+AP_INIT_TAKE1( "MaxClients", set_threads_limit, NULL, RSRC_CONF,
+ "Maximum number of children alive at the same time (max threads)" ),
+AP_INIT_TAKE1( "MaxRequestsPerThread", set_max_requests_per_thread, NULL, RSRC_CONF,
+ "Maximum number of requests served by a thread" ),
+{ NULL }
+};
+
+module AP_MODULE_DECLARE_DATA mpm_beos_module = {
+ MPM20_MODULE_STUFF,
+ NULL, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ beos_cmds, /* command apr_table_t */
+ beos_hooks /* register_hooks */
+};
+
diff --git a/server/mpm/beos/beos.h b/server/mpm/beos/beos.h
new file mode 100644
index 00000000..8b7aed7d
--- /dev/null
+++ b/server/mpm/beos/beos.h
@@ -0,0 +1,34 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file beos/beos.h
+ * @brief Extern functions/values for BEOS MPM
+ *
+ * @addtogroup APACHE_MPM_BEOS
+ * @{
+ */
+#ifndef APACHE_MPM_BEOS_H
+#define APACHE_MPM_BEOS_H
+
+extern int ap_threads_per_child;
+extern int ap_pipe_of_death[2];
+extern int ap_extended_status;
+extern void clean_child_exit(int);
+extern int max_daemons_limit;
+
+#endif /* APACHE_MPM_BEOS_H */
+/** @} */
diff --git a/server/mpm/beos/config5.m4 b/server/mpm/beos/config5.m4
new file mode 100644
index 00000000..4f201408
--- /dev/null
+++ b/server/mpm/beos/config5.m4
@@ -0,0 +1,7 @@
+dnl ## XXX - Need a more thorough check of the proper flags to use
+
+if test "$MPM_NAME" = "beos" ; then
+ apache_apr_flags="--enable-threads"
+
+ APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile)
+fi
diff --git a/server/mpm/beos/mpm.h b/server/mpm/beos/mpm.h
new file mode 100644
index 00000000..d61594b0
--- /dev/null
+++ b/server/mpm/beos/mpm.h
@@ -0,0 +1,50 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file beos/mpm.h
+ * @brief BEOS MPM
+ *
+ * @defgroup APACHE_MPM_BEOS BEOS MPM
+ * @ingroup APACHE_MPM APACHE_OS_BEOS
+ * @{
+ */
+
+#ifndef APACHE_MPM_BEOS_H
+#define APACHE_MPM_BEOS_H
+
+#define BEOS_MPM
+#include "scoreboard.h"
+
+#define MPM_NAME "Beos"
+#define MPM_CHILD_PID(i) (ap_scoreboard_image->servers[0][i].tid)
+#define MPM_NOTE_CHILD_KILLED(i) (MPM_CHILD_PID(i) = 0)
+
+#define AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+#define AP_MPM_WANT_WAIT_OR_TIMEOUT
+#define AP_MPM_WANT_PROCESS_CHILD_STATUS
+#define AP_MPM_WANT_SET_PIDFILE
+#define AP_MPM_WANT_SET_SCOREBOARD
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_WANT_SET_COREDUMPDIR
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+
+extern int ap_max_child_assigned;
+extern server_rec *ap_server_conf;
+extern int ap_threads_per_child;
+
+#endif /* APACHE_MPM_BEOS_H */
+/** @} */
diff --git a/server/mpm/beos/mpm_default.h b/server/mpm/beos/mpm_default.h
new file mode 100644
index 00000000..316a4c09
--- /dev/null
+++ b/server/mpm/beos/mpm_default.h
@@ -0,0 +1,84 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file beos/mpm_default.h
+ * @brief beos MPM defaults
+ *
+ * @addtogroup APACHE_MPM_BEOS
+ * @{
+ */
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* we use the child (c) as zero in our code... */
+#define AP_ID_FROM_CHILD_THREAD(c, t) t
+/* as the child is always zero, just return the id... */
+#define AP_CHILD_THREAD_FROM_ID(i) 0 , i
+
+/* Number of threads to spawn off by default --- also, if fewer than
+ * this free when the caretaker checks, it will spawn more.
+ */
+#ifndef DEFAULT_START_THREADS
+#define DEFAULT_START_THREADS 10
+#endif
+
+#ifdef NO_THREADS
+#define DEFAULT_THREADS 1
+#endif
+#ifndef DEFAULT_THREADS
+#define DEFAULT_THREADS 10
+#endif
+
+/* The following 2 settings are used to control the number of threads
+ * we have available. Normally the DEFAULT_MAX_FREE_THREADS is set
+ * to the same as the HARD_THREAD_LIMIT to avoid churning of starting
+ * new threads to replace threads killed off...
+ */
+
+/* Maximum number of *free* threads --- more than this, and
+ * they will die off.
+ */
+#ifndef DEFAULT_MAX_FREE_THREADS
+#define DEFAULT_MAX_FREE_THREADS HARD_THREAD_LIMIT
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+#ifndef DEFAULT_MIN_FREE_THREADS
+#define DEFAULT_MIN_FREE_THREADS 1
+#endif
+
+/* Where the main/parent process's pid is logged */
+#ifndef DEFAULT_PIDLOG
+#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+#endif
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If == 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_THREAD
+#define DEFAULT_MAX_REQUESTS_PER_THREAD 0
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/config.m4 b/server/mpm/config.m4
new file mode 100644
index 00000000..6ea447d1
--- /dev/null
+++ b/server/mpm/config.m4
@@ -0,0 +1,79 @@
+AC_MSG_CHECKING(which MPM to use)
+AC_ARG_WITH(mpm,
+APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use.
+ MPM={beos|event|worker|prefork|mpmt_os2}),[
+ APACHE_MPM=$withval
+],[
+ if test "x$APACHE_MPM" = "x"; then
+ APACHE_MPM=prefork
+ fi
+])
+AC_MSG_RESULT($APACHE_MPM)
+
+apache_cv_mpm=$APACHE_MPM
+
+ap_mpm_is_threaded ()
+{
+ if test "$apache_cv_mpm" = "worker" -o "$apache_cv_mpm" = "event" ; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+ap_mpm_is_experimental ()
+{
+ if test "$apache_cv_mpm" = "event" ; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+if ap_mpm_is_threaded; then
+ APR_CHECK_APR_DEFINE(APR_HAS_THREADS)
+
+ if test "x$ac_cv_define_APR_HAS_THREADS" = "xno"; then
+ AC_MSG_RESULT(The currently selected MPM requires threads which your system seems to lack)
+ AC_MSG_CHECKING(checking for replacement)
+ AC_MSG_RESULT(prefork selected)
+ apache_cv_mpm=prefork
+ else
+ case $host in
+ *-linux-*)
+ case `uname -r` in
+ 2.0* )
+ dnl Threaded MPM's are not supported on Linux 2.0
+ dnl as on 2.0 the linuxthreads library uses SIGUSR1
+ dnl and SIGUSR2 internally
+ echo "Threaded MPM's are not supported on this platform"
+ AC_MSG_CHECKING(checking for replacement)
+ AC_MSG_RESULT(prefork selected)
+ apache_cv_mpm=prefork
+ ;;
+ esac
+ ;;
+ esac
+ fi
+fi
+
+APACHE_FAST_OUTPUT(server/mpm/Makefile)
+
+MPM_NAME=$apache_cv_mpm
+if ap_mpm_is_experimental; then
+ AC_MSG_WARN(You have selected an EXPERIMENTAL MPM. Be warned!)
+ MPM_SUBDIR_NAME=experimental/$MPM_NAME
+else
+ MPM_SUBDIR_NAME=$MPM_NAME
+fi
+MPM_DIR=server/mpm/$MPM_SUBDIR_NAME
+MPM_LIB=$MPM_DIR/lib${MPM_NAME}.la
+
+if test ! -f "$abs_srcdir/$MPM_DIR/mpm.h"; then
+ AC_MSG_ERROR(the selected mpm -- $apache_cv_mpm -- is not supported)
+fi
+
+APACHE_SUBST(MPM_NAME)
+APACHE_SUBST(MPM_SUBDIR_NAME)
+MODLIST="$MODLIST mpm_${MPM_NAME}"
+
diff --git a/server/mpm/experimental/event/Makefile.in b/server/mpm/experimental/event/Makefile.in
new file mode 100644
index 00000000..7c2a1a7a
--- /dev/null
+++ b/server/mpm/experimental/event/Makefile.in
@@ -0,0 +1,5 @@
+
+LTLIBRARY_NAME = libevent.la
+LTLIBRARY_SOURCES = event.c fdqueue.c pod.c
+
+include $(top_srcdir)/build/ltlib.mk
diff --git a/server/mpm/experimental/event/config5.m4 b/server/mpm/experimental/event/config5.m4
new file mode 100644
index 00000000..5e1db539
--- /dev/null
+++ b/server/mpm/experimental/event/config5.m4
@@ -0,0 +1,6 @@
+dnl ## XXX - Need a more thorough check of the proper flags to use
+
+if test "$MPM_NAME" = "event" ; then
+ AC_CHECK_FUNCS(pthread_kill)
+ APACHE_FAST_OUTPUT(server/mpm/$MPM_SUBDIR_NAME/Makefile)
+fi
diff --git a/server/mpm/experimental/event/event.c b/server/mpm/experimental/event/event.c
new file mode 100644
index 00000000..c47e857d
--- /dev/null
+++ b/server/mpm/experimental/event/event.c
@@ -0,0 +1,2456 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This MPM tries to fix the 'keep alive problem' in HTTP.
+ *
+ * After a client completes the first request, the client can keep the
+ * connection open to send more requests with the same socket. This can save
+ * signifigant overhead in creating TCP connections. However, the major
+ * disadvantage is that Apache traditionally keeps an entire child
+ * process/thread waiting for data from the client. To solve this problem,
+ * this MPM has a dedicated thread for handling both the Listenting sockets,
+ * and all sockets that are in a Keep Alive status.
+ *
+ * The MPM assumes the underlying apr_pollset implmentation is somewhat
+ * threadsafe. This currently is only compatible with KQueue and EPoll. This
+ * enables the MPM to avoid extra high level locking or having to wake up the
+ * listener thread when a keep-alive socket needs to be sent to it.
+ *
+ * This MPM not preform well on older platforms that do not have very good
+ * threading, like Linux with a 2.4 kernel, but this does not matter, since we
+ * require EPoll or KQueue.
+ *
+ * For FreeBSD, use 5.3. It is possible to run this MPM on FreeBSD 5.2.1, if
+ * you use libkse (see `man libmap.conf`).
+ *
+ * For NetBSD, use at least 2.0.
+ *
+ * For Linux, you should use a 2.6 kernel, and make sure your glibc has epoll
+ * support compiled in.
+ *
+ */
+
+#include "apr.h"
+#include "apr_portable.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_thread_proc.h"
+#include "apr_signal.h"
+#include "apr_thread_mutex.h"
+#include "apr_proc_mutex.h"
+#include "apr_poll.h"
+#include "apr_ring.h"
+#include "apr_queue.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#if APR_HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#ifdef HAVE_SYS_PROCESSOR_H
+#include <sys/processor.h> /* for bindprocessor() */
+#endif
+
+#if !APR_HAS_THREADS
+#error The Event MPM requires APR threads, but they are unavailable.
+#endif
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h" /* for read_config */
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "ap_mpm.h"
+#include "pod.h"
+#include "mpm_common.h"
+#include "ap_listen.h"
+#include "scoreboard.h"
+#include "fdqueue.h"
+#include "mpm_default.h"
+#include "http_vhost.h"
+
+#include <signal.h>
+#include <limits.h> /* for INT_MAX */
+
+/* Limit on the total --- clients will be locked out if more servers than
+ * this are needed. It is intended solely to keep the server from crashing
+ * when things get out of hand.
+ *
+ * We keep a hard maximum number of servers, for two reasons --- first off,
+ * in case something goes seriously wrong, we want to stop the fork bomb
+ * short of actually crashing the machine we're running on by filling some
+ * kernel table. Secondly, it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef DEFAULT_SERVER_LIMIT
+#define DEFAULT_SERVER_LIMIT 16
+#endif
+
+/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want
+ * some sort of compile-time limit to help catch typos.
+ */
+#ifndef MAX_SERVER_LIMIT
+#define MAX_SERVER_LIMIT 20000
+#endif
+
+/* Limit on the threads per process. Clients will be locked out if more than
+ * this are needed.
+ *
+ * We keep this for one reason it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef DEFAULT_THREAD_LIMIT
+#define DEFAULT_THREAD_LIMIT 64
+#endif
+
+/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT. We want
+ * some sort of compile-time limit to help catch typos.
+ */
+#ifndef MAX_THREAD_LIMIT
+#define MAX_THREAD_LIMIT 100000
+#endif
+
+/*
+ * Actual definitions of config globals
+ */
+
+int ap_threads_per_child = 0; /* Worker threads per child */
+static int ap_daemons_to_start = 0;
+static int min_spare_threads = 0;
+static int max_spare_threads = 0;
+static int ap_daemons_limit = 0;
+static int server_limit = DEFAULT_SERVER_LIMIT;
+static int first_server_limit = 0;
+static int thread_limit = DEFAULT_THREAD_LIMIT;
+static int first_thread_limit = 0;
+static int changed_limit_at_restart;
+static int dying = 0;
+static int workers_may_exit = 0;
+static int start_thread_may_exit = 0;
+static int listener_may_exit = 0;
+static int requests_this_child;
+static int num_listensocks = 0;
+static int resource_shortage = 0;
+static fd_queue_t *worker_queue;
+static fd_queue_info_t *worker_queue_info;
+static int mpm_state = AP_MPMQ_STARTING;
+static int sick_child_detected;
+
+apr_thread_mutex_t *timeout_mutex;
+APR_RING_HEAD(timeout_head_t, conn_state_t);
+static struct timeout_head_t timeout_head;
+
+static apr_pollset_t *event_pollset;
+
+/* The structure used to pass unique initialization info to each thread */
+typedef struct
+{
+ int pid;
+ int tid;
+ int sd;
+} proc_info;
+
+/* Structure used to pass information to the thread responsible for
+ * creating the rest of the threads.
+ */
+typedef struct
+{
+ apr_thread_t **threads;
+ apr_thread_t *listener;
+ int child_num_arg;
+ apr_threadattr_t *threadattr;
+} thread_starter;
+
+typedef enum
+{
+ PT_CSD,
+ PT_ACCEPT
+} poll_type_e;
+
+typedef struct
+{
+ poll_type_e type;
+ int status; /*XXX what is this for? 0 and 1 don't make it clear */
+ void *baton;
+} listener_poll_type;
+
+#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t)
+
+/*
+ * The max child slot ever assigned, preserved across restarts. Necessary
+ * to deal with MaxClients changes across AP_SIG_GRACEFUL restarts. We
+ * use this value to optimize routines that have to scan the entire
+ * scoreboard.
+ */
+int ap_max_daemons_limit = -1;
+
+static ap_pod_t *pod;
+
+/* *Non*-shared http_main globals... */
+
+server_rec *ap_server_conf;
+
+/* The worker MPM respects a couple of runtime flags that can aid
+ * in debugging. Setting the -DNO_DETACH flag will prevent the root process
+ * from detaching from its controlling terminal. Additionally, setting
+ * the -DONE_PROCESS flag (which implies -DNO_DETACH) will get you the
+ * child_main loop running in the process which originally started up.
+ * This gives you a pretty nice debugging environment. (You'll get a SIGHUP
+ * early in standalone_main; just continue through. This is the server
+ * trying to kill off any child processes which it might have lying
+ * around --- Apache doesn't keep track of their pids, it just sends
+ * SIGHUP to the process group, ignoring it in the root process.
+ * Continue through and you'll be fine.).
+ */
+
+static int one_process = 0;
+
+#ifdef DEBUG_SIGSTOP
+int raise_sigstop_flags;
+#endif
+
+static apr_pool_t *pconf; /* Pool for config stuff */
+static apr_pool_t *pchild; /* Pool for httpd child stuff */
+
+static pid_t ap_my_pid; /* Linux getpid() doesn't work except in main
+ thread. Use this instead */
+static pid_t parent_pid;
+static apr_os_thread_t *listener_os_thread;
+
+/* The LISTENER_SIGNAL signal will be sent from the main thread to the
+ * listener thread to wake it up for graceful termination (what a child
+ * process from an old generation does when the admin does "apachectl
+ * graceful"). This signal will be blocked in all threads of a child
+ * process except for the listener thread.
+ */
+#define LISTENER_SIGNAL SIGHUP
+
+/* An array of socket descriptors in use by each thread used to
+ * perform a non-graceful (forced) shutdown of the server.
+ */
+static apr_socket_t **worker_sockets;
+
+static void close_worker_sockets(void)
+{
+ int i;
+ for (i = 0; i < ap_threads_per_child; i++) {
+ if (worker_sockets[i]) {
+ apr_socket_close(worker_sockets[i]);
+ worker_sockets[i] = NULL;
+ }
+ }
+}
+
+static void wakeup_listener(void)
+{
+ listener_may_exit = 1;
+ if (!listener_os_thread) {
+ /* XXX there is an obscure path that this doesn't handle perfectly:
+ * right after listener thread is created but before
+ * listener_os_thread is set, the first worker thread hits an
+ * error and starts graceful termination
+ */
+ return;
+ }
+ /*
+ * we should just be able to "kill(ap_my_pid, LISTENER_SIGNAL)" on all
+ * platforms and wake up the listener thread since it is the only thread
+ * with SIGHUP unblocked, but that doesn't work on Linux
+ */
+#ifdef HAVE_PTHREAD_KILL
+ pthread_kill(*listener_os_thread, LISTENER_SIGNAL);
+#else
+ kill(ap_my_pid, LISTENER_SIGNAL);
+#endif
+}
+
+#define ST_INIT 0
+#define ST_GRACEFUL 1
+#define ST_UNGRACEFUL 2
+
+static int terminate_mode = ST_INIT;
+
+static void signal_threads(int mode)
+{
+ if (terminate_mode == mode) {
+ return;
+ }
+ terminate_mode = mode;
+ mpm_state = AP_MPMQ_STOPPING;
+
+ /* in case we weren't called from the listener thread, wake up the
+ * listener thread
+ */
+ wakeup_listener();
+
+ /* for ungraceful termination, let the workers exit now;
+ * for graceful termination, the listener thread will notify the
+ * workers to exit once it has stopped accepting new connections
+ */
+ if (mode == ST_UNGRACEFUL) {
+ workers_may_exit = 1;
+ ap_queue_interrupt_all(worker_queue);
+ ap_queue_info_term(worker_queue_info);
+ close_worker_sockets(); /* forcefully kill all current connections */
+ }
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch (query_code) {
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = ap_max_daemons_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_STATIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_DYNAMIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_ASYNC:
+ *result = 1;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = server_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = thread_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_THREADS:
+ *result = ap_threads_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_THREADS:
+ *result = min_spare_threads;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_THREADS:
+ *result = max_spare_threads;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_DAEMONS:
+ *result = ap_daemons_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MPM_STATE:
+ *result = mpm_state;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+/* a clean exit from a child with proper cleanup */
+static void clean_child_exit(int code) __attribute__ ((noreturn));
+static void clean_child_exit(int code)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+ if (pchild) {
+ apr_pool_destroy(pchild);
+ }
+ exit(code);
+}
+
+static void just_die(int sig)
+{
+ clean_child_exit(0);
+}
+
+/*****************************************************************
+ * Connection structures and accounting...
+ */
+
+/* volatile just in case */
+static int volatile shutdown_pending;
+static int volatile restart_pending;
+static int volatile is_graceful;
+static volatile int child_fatal;
+ap_generation_t volatile ap_my_generation;
+
+/*
+ * ap_start_shutdown() and ap_start_restart(), below, are a first stab at
+ * functions to initiate shutdown or restart without relying on signals.
+ * Previously this was initiated in sig_term() and restart() signal handlers,
+ * but we want to be able to start a shutdown/restart from other sources --
+ * e.g. on Win32, from the service manager. Now the service manager can
+ * call ap_start_shutdown() or ap_start_restart() as appropiate. Note that
+ * these functions can also be called by the child processes, since global
+ * variables are no longer used to pass on the required action to the parent.
+ *
+ * These should only be called from the parent process itself, since the
+ * parent process will use the shutdown_pending and restart_pending variables
+ * to determine whether to shutdown or restart. The child process should
+ * call signal_parent() directly to tell the parent to die -- this will
+ * cause neither of those variable to be set, which the parent will
+ * assume means something serious is wrong (which it will be, for the
+ * child to force an exit) and so do an exit anyway.
+ */
+
+static void ap_start_shutdown(int graceful)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+ if (shutdown_pending == 1) {
+ /* Um, is this _probably_ not an error, if the user has
+ * tried to do a shutdown twice quickly, so we won't
+ * worry about reporting it.
+ */
+ return;
+ }
+ shutdown_pending = 1;
+ is_graceful = graceful;
+}
+
+/* do a graceful restart if graceful == 1 */
+static void ap_start_restart(int graceful)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+ if (restart_pending == 1) {
+ /* Probably not an error - don't bother reporting it */
+ return;
+ }
+ restart_pending = 1;
+ is_graceful = graceful;
+}
+
+static void sig_term(int sig)
+{
+ ap_start_shutdown(sig == AP_SIG_GRACEFUL_STOP);
+}
+
+static void restart(int sig)
+{
+ ap_start_restart(sig == AP_SIG_GRACEFUL);
+}
+
+static void set_signals(void)
+{
+#ifndef NO_USE_SIGACTION
+ struct sigaction sa;
+#endif
+
+ if (!one_process) {
+ ap_fatal_signal_setup(ap_server_conf, pconf);
+ }
+
+#ifndef NO_USE_SIGACTION
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ sa.sa_handler = sig_term;
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGTERM)");
+#ifdef AP_SIG_GRACEFUL_STOP
+ if (sigaction(AP_SIG_GRACEFUL_STOP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(" AP_SIG_GRACEFUL_STOP_STRING ")");
+#endif
+#ifdef SIGINT
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGINT)");
+#endif
+#ifdef SIGXCPU
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGXCPU, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGXCPU)");
+#endif
+#ifdef SIGXFSZ
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGXFSZ, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGXFSZ)");
+#endif
+#ifdef SIGPIPE
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGPIPE)");
+#endif
+
+ /* we want to ignore HUPs and AP_SIG_GRACEFUL while we're busy
+ * processing one */
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, AP_SIG_GRACEFUL);
+ sa.sa_handler = restart;
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGHUP)");
+ if (sigaction(AP_SIG_GRACEFUL, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(" AP_SIG_GRACEFUL_STRING ")");
+#else
+ if (!one_process) {
+#ifdef SIGXCPU
+ apr_signal(SIGXCPU, SIG_DFL);
+#endif /* SIGXCPU */
+#ifdef SIGXFSZ
+ apr_signal(SIGXFSZ, SIG_DFL);
+#endif /* SIGXFSZ */
+ }
+
+ apr_signal(SIGTERM, sig_term);
+#ifdef SIGHUP
+ apr_signal(SIGHUP, restart);
+#endif /* SIGHUP */
+#ifdef AP_SIG_GRACEFUL
+ apr_signal(AP_SIG_GRACEFUL, restart);
+#endif /* AP_SIG_GRACEFUL */
+#ifdef AP_SIG_GRACEFUL_STOP
+ apr_signal(AP_SIG_GRACEFUL_STOP, sig_term);
+#endif /* AP_SIG_GRACEFUL_STOP */
+#ifdef SIGPIPE
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif /* SIGPIPE */
+
+#endif
+}
+
+/*****************************************************************
+ * Here follows a long bunch of generic server bookkeeping stuff...
+ */
+
+int ap_graceful_stop_signalled(void)
+ /* XXX this is really a bad confusing obsolete name
+ * maybe it should be ap_mpm_process_exiting?
+ */
+{
+ /* note: for a graceful termination, listener_may_exit will be set before
+ * workers_may_exit, so check listener_may_exit
+ */
+ return listener_may_exit;
+}
+
+/*****************************************************************
+ * Child process main loop.
+ */
+
+static int process_socket(apr_pool_t * p, apr_socket_t * sock,
+ conn_state_t * cs, int my_child_num,
+ int my_thread_num)
+{
+ conn_rec *c;
+ listener_poll_type *pt;
+ long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num);
+ int csd;
+ int rc;
+ apr_time_t time_now = 0;
+ ap_sb_handle_t *sbh;
+
+ ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num);
+ apr_os_sock_get(&csd, sock);
+
+ time_now = apr_time_now();
+
+ if (cs == NULL) { /* This is a new connection */
+
+ cs = apr_pcalloc(p, sizeof(conn_state_t));
+
+ pt = apr_pcalloc(p, sizeof(*pt));
+
+ cs->bucket_alloc = apr_bucket_alloc_create(p);
+ c = ap_run_create_connection(p, ap_server_conf, sock,
+ conn_id, sbh, cs->bucket_alloc);
+ cs->c = c;
+ c->cs = cs;
+ cs->p = p;
+ cs->pfd.desc_type = APR_POLL_SOCKET;
+ cs->pfd.reqevents = APR_POLLIN;
+ cs->pfd.desc.s = sock;
+ pt->type = PT_CSD;
+ pt->status = 1;
+ pt->baton = cs;
+ cs->pfd.client_data = pt;
+
+ ap_update_vhost_given_ip(c);
+
+ rc = ap_run_pre_connection(c, sock);
+ if (rc != OK && rc != DONE) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "process_socket: connection aborted");
+ c->aborted = 1;
+ }
+
+ /**
+ * XXX If the platform does not have a usable way of bundling
+ * accept() with a socket readability check, like Win32,
+ * and there are measurable delays before the
+ * socket is readable due to the first data packet arriving,
+ * it might be better to create the cs on the listener thread
+ * with the state set to CONN_STATE_CHECK_REQUEST_LINE_READABLE
+ *
+ * FreeBSD users will want to enable the HTTP accept filter
+ * module in their kernel for the highest performance
+ * When the accept filter is active, sockets are kept in the
+ * kernel until a HTTP request is received.
+ */
+ cs->state = CONN_STATE_READ_REQUEST_LINE;
+
+ }
+ else {
+ c = cs->c;
+ c->sbh = sbh;
+ }
+
+ if (cs->state == CONN_STATE_READ_REQUEST_LINE) {
+ if (!c->aborted) {
+ ap_run_process_connection(c);
+
+ /* state will be updated upon return
+ * fall thru to either wait for readability/timeout or
+ * do lingering close
+ */
+ }
+ else {
+ cs->state = CONN_STATE_LINGER;
+ }
+ }
+
+ if (cs->state == CONN_STATE_LINGER) {
+ ap_lingering_close(c);
+ apr_bucket_alloc_destroy(cs->bucket_alloc);
+ apr_pool_clear(p);
+ ap_push_pool(worker_queue_info, p);
+ return 1;
+ }
+ else if (cs->state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) {
+ apr_status_t rc;
+ listener_poll_type *pt = (listener_poll_type *) cs->pfd.client_data;
+
+ /* It greatly simplifies the logic to use a single timeout value here
+ * because the new element can just be added to the end of the list and
+ * it will stay sorted in expiration time sequence. If brand new
+ * sockets are sent to the event thread for a readability check, this
+ * will be a slight behavior change - they use the non-keepalive
+ * timeout today. With a normal client, the socket will be readable in
+ * a few milliseconds anyway.
+ */
+ cs->expiration_time = ap_server_conf->keep_alive_timeout + time_now;
+ apr_thread_mutex_lock(timeout_mutex);
+ APR_RING_INSERT_TAIL(&timeout_head, cs, conn_state_t, timeout_list);
+
+ pt->status = 0;
+ /* Add work to pollset. These are always read events */
+ rc = apr_pollset_add(event_pollset, &cs->pfd);
+
+ apr_thread_mutex_unlock(timeout_mutex);
+
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
+ "process_socket: apr_pollset_add failure");
+ AP_DEBUG_ASSERT(rc == APR_SUCCESS);
+ }
+ }
+ return 0;
+}
+
+/* requests_this_child has gone to zero or below. See if the admin coded
+ "MaxRequestsPerChild 0", and keep going in that case. Doing it this way
+ simplifies the hot path in worker_thread */
+static void check_infinite_requests(void)
+{
+ if (ap_max_requests_per_child) {
+ signal_threads(ST_GRACEFUL);
+ }
+ else {
+ /* wow! if you're executing this code, you may have set a record.
+ * either this child process has served over 2 billion requests, or
+ * you're running a threaded 2.0 on a 16 bit machine.
+ *
+ * I'll buy pizza and beers at Apachecon for the first person to do
+ * the former without cheating (dorking with INT_MAX, or running with
+ * uncommitted performance patches, for example).
+ *
+ * for the latter case, you probably deserve a beer too. Greg Ames
+ */
+
+ requests_this_child = INT_MAX; /* keep going */
+ }
+}
+
+static void unblock_signal(int sig)
+{
+ sigset_t sig_mask;
+
+ sigemptyset(&sig_mask);
+ sigaddset(&sig_mask, sig);
+#if defined(SIGPROCMASK_SETS_THREAD_MASK)
+ sigprocmask(SIG_UNBLOCK, &sig_mask, NULL);
+#else
+ pthread_sigmask(SIG_UNBLOCK, &sig_mask, NULL);
+#endif
+}
+
+static void dummy_signal_handler(int sig)
+{
+ /* XXX If specifying SIG_IGN is guaranteed to unblock a syscall,
+ * then we don't need this goofy function.
+ */
+}
+
+static apr_status_t push2worker(const apr_pollfd_t * pfd,
+ apr_pollset_t * pollset)
+{
+ listener_poll_type *pt = (listener_poll_type *) pfd->client_data;
+ conn_state_t *cs = (conn_state_t *) pt->baton;
+ apr_status_t rc;
+
+ if (pt->status == 1) {
+ return 0;
+ }
+
+ pt->status = 1;
+
+ rc = apr_pollset_remove(pollset, pfd);
+
+ /*
+ * Some of the pollset backends, like KQueue or Epoll
+ * automagically remove the FD if the socket is closed,
+ * therefore, we can accept _SUCCESS or _NOTFOUND,
+ * and we still want to keep going
+ */
+ if (rc != APR_SUCCESS && rc != APR_NOTFOUND) {
+ cs->state = CONN_STATE_LINGER;
+ }
+
+ rc = ap_queue_push(worker_queue, cs->pfd.desc.s, cs, cs->p);
+ if (rc != APR_SUCCESS) {
+ /* trash the connection; we couldn't queue the connected
+ * socket to a worker
+ */
+ apr_bucket_alloc_destroy(cs->bucket_alloc);
+ apr_socket_close(cs->pfd.desc.s);
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
+ ap_server_conf, "push2worker: ap_queue_push failed");
+ apr_pool_clear(cs->p);
+ ap_push_pool(worker_queue_info, cs->p);
+ }
+
+ return APR_SUCCESS;
+}
+
+/* get_worker:
+ * reserve a worker thread, block if all are currently busy.
+ * this prevents the worker queue from overflowing and lets
+ * other processes accept new connections in the mean time.
+ */
+static int get_worker(int *have_idle_worker_p)
+{
+ apr_status_t rc;
+
+ if (!*have_idle_worker_p) {
+ rc = ap_queue_info_wait_for_idler(worker_queue_info);
+
+ if (rc == APR_SUCCESS) {
+ *have_idle_worker_p = 1;
+ return 1;
+ }
+ else {
+ if (!APR_STATUS_IS_EOF(rc)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
+ "ap_queue_info_wait_for_idler failed. "
+ "Attempting to shutdown process gracefully");
+ signal_threads(ST_GRACEFUL);
+ }
+ return 0;
+ }
+ }
+ else {
+ /* already reserved a worker thread - must have hit a
+ * transient error on a previous pass
+ */
+ return 1;
+ }
+}
+
+static void *listener_thread(apr_thread_t * thd, void *dummy)
+{
+ apr_status_t rc;
+ proc_info *ti = dummy;
+ int process_slot = ti->pid;
+ apr_pool_t *tpool = apr_thread_pool_get(thd);
+ void *csd = NULL;
+ apr_pool_t *ptrans; /* Pool for per-transaction stuff */
+ ap_listen_rec *lr;
+ int have_idle_worker = 0;
+ conn_state_t *cs;
+ const apr_pollfd_t *out_pfd;
+ apr_int32_t num = 0;
+ apr_time_t time_now = 0;
+ apr_interval_time_t timeout_interval;
+ apr_time_t timeout_time;
+ listener_poll_type *pt;
+
+ free(ti);
+
+ /* We set this to force apr_pollset to wakeup if there hasn't been any IO
+ * on any of its sockets. This allows sockets to have been added
+ * when no other keepalive operations where going on.
+ *
+ * current value is 1 second
+ */
+ timeout_interval = 1000000;
+
+ /* the following times out events that are really close in the future
+ * to prevent extra poll calls
+ *
+ * current value is .1 second
+ */
+#define TIMEOUT_FUDGE_FACTOR 100000
+
+ /* POLLSET_SCALE_FACTOR * ap_threads_per_child sets the size of
+ * the pollset. I've seen 15 connections per active worker thread
+ * running SPECweb99.
+ *
+ * However, with the newer apr_pollset, this is the number of sockets that
+ * we will return to any *one* call to poll(). Therefore, there is no
+ * reason to make it more than ap_threads_per_child.
+ */
+#define POLLSET_SCALE_FACTOR 1
+
+ rc = apr_thread_mutex_create(&timeout_mutex, APR_THREAD_MUTEX_DEFAULT,
+ tpool);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
+ "creation of the timeout mutex failed. Attempting to "
+ "shutdown process gracefully");
+ signal_threads(ST_GRACEFUL);
+ return NULL;
+ }
+
+ APR_RING_INIT(&timeout_head, conn_state_t, timeout_list);
+
+ /* Create the main pollset */
+ rc = apr_pollset_create(&event_pollset,
+ ap_threads_per_child * POLLSET_SCALE_FACTOR,
+ tpool, APR_POLLSET_THREADSAFE);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
+ "apr_pollset_create with Thread Safety failed. "
+ "Attempting to shutdown process gracefully");
+ signal_threads(ST_GRACEFUL);
+ return NULL;
+ }
+
+ for (lr = ap_listeners; lr != NULL; lr = lr->next) {
+ apr_pollfd_t pfd = { 0 };
+ pt = apr_pcalloc(tpool, sizeof(*pt));
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = lr->sd;
+ pfd.reqevents = APR_POLLIN;
+
+ pt->type = PT_ACCEPT;
+ pt->baton = lr;
+
+ pfd.client_data = pt;
+
+ apr_socket_opt_set(pfd.desc.s, APR_SO_NONBLOCK, 1);
+ apr_pollset_add(event_pollset, &pfd);
+ }
+
+ /* Unblock the signal used to wake this thread up, and set a handler for
+ * it.
+ */
+ unblock_signal(LISTENER_SIGNAL);
+ apr_signal(LISTENER_SIGNAL, dummy_signal_handler);
+
+ while (!listener_may_exit) {
+
+ if (requests_this_child <= 0) {
+ check_infinite_requests();
+ }
+
+ rc = apr_pollset_poll(event_pollset, timeout_interval, &num,
+ &out_pfd);
+
+ if (rc != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(rc)) {
+ continue;
+ }
+ if (!APR_STATUS_IS_TIMEUP(rc)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
+ "apr_pollset_poll failed. Attempting to "
+ "shutdown process gracefully");
+ signal_threads(ST_GRACEFUL);
+ }
+ }
+
+ if (listener_may_exit)
+ break;
+
+ while (num && get_worker(&have_idle_worker)) {
+ pt = (listener_poll_type *) out_pfd->client_data;
+ if (pt->type == PT_CSD) {
+ /* one of the sockets is readable */
+ cs = (conn_state_t *) pt->baton;
+ switch (cs->state) {
+ case CONN_STATE_CHECK_REQUEST_LINE_READABLE:
+ cs->state = CONN_STATE_READ_REQUEST_LINE;
+ break;
+ default:
+ ap_log_error(APLOG_MARK, APLOG_ERR, rc,
+ ap_server_conf,
+ "event_loop: unexpected state %d",
+ cs->state);
+ AP_DEBUG_ASSERT(0);
+ }
+
+ apr_thread_mutex_lock(timeout_mutex);
+ APR_RING_REMOVE(cs, timeout_list);
+ apr_thread_mutex_unlock(timeout_mutex);
+
+ rc = push2worker(out_pfd, event_pollset);
+ if (rc != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
+ ap_server_conf, "push2worker failed");
+ }
+ else {
+ have_idle_worker = 0;
+ }
+ }
+ else {
+ /* A Listener Socket is ready for an accept() */
+ apr_pool_t *recycled_pool = NULL;
+
+ lr = (ap_listen_rec *) pt->baton;
+
+ ap_pop_pool(&recycled_pool, worker_queue_info);
+
+ if (recycled_pool == NULL) {
+ /* create a new transaction pool for each accepted socket */
+ apr_allocator_t *allocator;
+
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator,
+ ap_max_mem_free);
+ apr_pool_create_ex(&ptrans, pconf, NULL, allocator);
+ apr_allocator_owner_set(allocator, ptrans);
+ if (ptrans == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
+ ap_server_conf,
+ "Failed to create transaction pool");
+ signal_threads(ST_GRACEFUL);
+ return NULL;
+ }
+ }
+ else {
+ ptrans = recycled_pool;
+ }
+
+ apr_pool_tag(ptrans, "transaction");
+
+ rc = lr->accept_func(&csd, lr, ptrans);
+
+ /* later we trash rv and rely on csd to indicate
+ * success/failure
+ */
+ AP_DEBUG_ASSERT(rc == APR_SUCCESS || !csd);
+
+ if (rc == APR_EGENERAL) {
+ /* E[NM]FILE, ENOMEM, etc */
+ resource_shortage = 1;
+ signal_threads(ST_GRACEFUL);
+ }
+
+ if (csd != NULL) {
+ rc = ap_queue_push(worker_queue, csd, NULL, ptrans);
+ if (rc != APR_SUCCESS) {
+ /* trash the connection; we couldn't queue the connected
+ * socket to a worker
+ */
+ apr_socket_close(csd);
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
+ ap_server_conf,
+ "ap_queue_push failed");
+ apr_pool_clear(ptrans);
+ ap_push_pool(worker_queue_info, ptrans);
+ }
+ else {
+ have_idle_worker = 0;
+ }
+ }
+ else {
+ apr_pool_clear(ptrans);
+ ap_push_pool(worker_queue_info, ptrans);
+ }
+ } /* if:else on pt->type */
+ out_pfd++;
+ num--;
+ } /* while for processing poll */
+
+ /* XXX possible optimization: stash the current time for use as
+ * r->request_time for new requests
+ */
+ time_now = apr_time_now();
+
+ /* handle timed out sockets */
+ apr_thread_mutex_lock(timeout_mutex);
+
+ cs = APR_RING_FIRST(&timeout_head);
+ timeout_time = time_now + TIMEOUT_FUDGE_FACTOR;
+ while (!APR_RING_EMPTY(&timeout_head, conn_state_t, timeout_list)
+ && cs->expiration_time < timeout_time
+ && get_worker(&have_idle_worker)) {
+
+ cs->state = CONN_STATE_LINGER;
+
+ APR_RING_REMOVE(cs, timeout_list);
+
+ rc = push2worker(&cs->pfd, event_pollset);
+
+ if (rc != APR_SUCCESS) {
+ return NULL;
+ /* XXX return NULL looks wrong - not an init failure
+ * that bypasses all the cleanup outside the main loop
+ * break seems more like it
+ * need to evaluate seriousness of push2worker failures
+ */
+ }
+ have_idle_worker = 0;
+ cs = APR_RING_FIRST(&timeout_head);
+ }
+ apr_thread_mutex_unlock(timeout_mutex);
+
+ } /* listener main loop */
+
+ ap_close_listeners();
+ ap_queue_term(worker_queue);
+ dying = 1;
+ ap_scoreboard_image->parent[process_slot].quiescing = 1;
+
+ /* wake up the main thread */
+ kill(ap_my_pid, SIGTERM);
+
+ apr_thread_exit(thd, APR_SUCCESS);
+ return NULL;
+}
+
+/* XXX For ungraceful termination/restart, we definitely don't want to
+ * wait for active connections to finish but we may want to wait
+ * for idle workers to get out of the queue code and release mutexes,
+ * since those mutexes are cleaned up pretty soon and some systems
+ * may not react favorably (i.e., segfault) if operations are attempted
+ * on cleaned-up mutexes.
+ */
+static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy)
+{
+ proc_info *ti = dummy;
+ int process_slot = ti->pid;
+ int thread_slot = ti->tid;
+ apr_socket_t *csd = NULL;
+ conn_state_t *cs;
+ apr_pool_t *ptrans; /* Pool for per-transaction stuff */
+ apr_status_t rv;
+ int is_idle = 0;
+
+ free(ti);
+
+ ap_scoreboard_image->servers[process_slot][thread_slot].pid = ap_my_pid;
+ ap_scoreboard_image->servers[process_slot][thread_slot].generation = ap_my_generation;
+ ap_update_child_status_from_indexes(process_slot, thread_slot,
+ SERVER_STARTING, NULL);
+
+ while (!workers_may_exit) {
+ if (!is_idle) {
+ rv = ap_queue_info_set_idle(worker_queue_info, NULL);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf,
+ "ap_queue_info_set_idle failed. Attempting to "
+ "shutdown process gracefully.");
+ signal_threads(ST_GRACEFUL);
+ break;
+ }
+ is_idle = 1;
+ }
+
+ ap_update_child_status_from_indexes(process_slot, thread_slot,
+ SERVER_READY, NULL);
+ worker_pop:
+ if (workers_may_exit) {
+ break;
+ }
+ rv = ap_queue_pop(worker_queue, &csd, &cs, &ptrans);
+
+ if (rv != APR_SUCCESS) {
+ /* We get APR_EOF during a graceful shutdown once all the
+ * connections accepted by this server process have been handled.
+ */
+ if (APR_STATUS_IS_EOF(rv)) {
+ break;
+ }
+ /* We get APR_EINTR whenever ap_queue_pop() has been interrupted
+ * from an explicit call to ap_queue_interrupt_all(). This allows
+ * us to unblock threads stuck in ap_queue_pop() when a shutdown
+ * is pending.
+ *
+ * If workers_may_exit is set and this is ungraceful termination/
+ * restart, we are bound to get an error on some systems (e.g.,
+ * AIX, which sanity-checks mutex operations) since the queue
+ * may have already been cleaned up. Don't log the "error" if
+ * workers_may_exit is set.
+ */
+ else if (APR_STATUS_IS_EINTR(rv)) {
+ goto worker_pop;
+ }
+ /* We got some other error. */
+ else if (!workers_may_exit) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "ap_queue_pop failed");
+ }
+ continue;
+ }
+ is_idle = 0;
+ worker_sockets[thread_slot] = csd;
+ rv = process_socket(ptrans, csd, cs, process_slot, thread_slot);
+ if (!rv) {
+ requests_this_child--;
+ }
+ worker_sockets[thread_slot] = NULL;
+ }
+
+ ap_update_child_status_from_indexes(process_slot, thread_slot,
+ (dying) ? SERVER_DEAD :
+ SERVER_GRACEFUL,
+ (request_rec *) NULL);
+
+ apr_thread_exit(thd, APR_SUCCESS);
+ return NULL;
+}
+
+static int check_signal(int signum)
+{
+ switch (signum) {
+ case SIGTERM:
+ case SIGINT:
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static void create_listener_thread(thread_starter * ts)
+{
+ int my_child_num = ts->child_num_arg;
+ apr_threadattr_t *thread_attr = ts->threadattr;
+ proc_info *my_info;
+ apr_status_t rv;
+
+ my_info = (proc_info *) malloc(sizeof(proc_info));
+ my_info->pid = my_child_num;
+ my_info->tid = -1; /* listener thread doesn't have a thread slot */
+ my_info->sd = 0;
+ rv = apr_thread_create(&ts->listener, thread_attr, listener_thread,
+ my_info, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "apr_thread_create: unable to create listener thread");
+ /* let the parent decide how bad this really is */
+ clean_child_exit(APEXIT_CHILDSICK);
+ }
+ apr_os_thread_get(&listener_os_thread, ts->listener);
+}
+
+/* XXX under some circumstances not understood, children can get stuck
+ * in start_threads forever trying to take over slots which will
+ * never be cleaned up; for now there is an APLOG_DEBUG message issued
+ * every so often when this condition occurs
+ */
+static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy)
+{
+ thread_starter *ts = dummy;
+ apr_thread_t **threads = ts->threads;
+ apr_threadattr_t *thread_attr = ts->threadattr;
+ int child_num_arg = ts->child_num_arg;
+ int my_child_num = child_num_arg;
+ proc_info *my_info;
+ apr_status_t rv;
+ int i;
+ int threads_created = 0;
+ int listener_started = 0;
+ int loops;
+ int prev_threads_created;
+
+ /* We must create the fd queues before we start up the listener
+ * and worker threads. */
+ worker_queue = apr_pcalloc(pchild, sizeof(*worker_queue));
+ rv = ap_queue_init(worker_queue, ap_threads_per_child, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "ap_queue_init() failed");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ rv = ap_queue_info_create(&worker_queue_info, pchild,
+ ap_threads_per_child);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "ap_queue_info_create() failed");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ worker_sockets = apr_pcalloc(pchild, ap_threads_per_child
+ * sizeof(apr_socket_t *));
+
+ loops = prev_threads_created = 0;
+ while (1) {
+ /* ap_threads_per_child does not include the listener thread */
+ for (i = 0; i < ap_threads_per_child; i++) {
+ int status =
+ ap_scoreboard_image->servers[child_num_arg][i].status;
+
+ if (status != SERVER_GRACEFUL && status != SERVER_DEAD) {
+ continue;
+ }
+
+ my_info = (proc_info *) malloc(sizeof(proc_info));
+ if (my_info == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, ap_server_conf,
+ "malloc: out of memory");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+ my_info->pid = my_child_num;
+ my_info->tid = i;
+ my_info->sd = 0;
+
+ /* We are creating threads right now */
+ ap_update_child_status_from_indexes(my_child_num, i,
+ SERVER_STARTING, NULL);
+ /* We let each thread update its own scoreboard entry. This is
+ * done because it lets us deal with tid better.
+ */
+ rv = apr_thread_create(&threads[i], thread_attr,
+ worker_thread, my_info, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "apr_thread_create: unable to create worker thread");
+ /* let the parent decide how bad this really is */
+ clean_child_exit(APEXIT_CHILDSICK);
+ }
+ threads_created++;
+ }
+
+ /* Start the listener only when there are workers available */
+ if (!listener_started && threads_created) {
+ create_listener_thread(ts);
+ listener_started = 1;
+ }
+
+
+ if (start_thread_may_exit || threads_created == ap_threads_per_child) {
+ break;
+ }
+ /* wait for previous generation to clean up an entry */
+ apr_sleep(apr_time_from_sec(1));
+ ++loops;
+ if (loops % 120 == 0) { /* every couple of minutes */
+ if (prev_threads_created == threads_created) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "child %" APR_PID_T_FMT " isn't taking over "
+ "slots very quickly (%d of %d)",
+ ap_my_pid, threads_created,
+ ap_threads_per_child);
+ }
+ prev_threads_created = threads_created;
+ }
+ }
+
+ /* What state should this child_main process be listed as in the
+ * scoreboard...?
+ * ap_update_child_status_from_indexes(my_child_num, i, SERVER_STARTING,
+ * (request_rec *) NULL);
+ *
+ * This state should be listed separately in the scoreboard, in some kind
+ * of process_status, not mixed in with the worker threads' status.
+ * "life_status" is almost right, but it's in the worker's structure, and
+ * the name could be clearer. gla
+ */
+ apr_thread_exit(thd, APR_SUCCESS);
+ return NULL;
+}
+
+static void join_workers(apr_thread_t * listener, apr_thread_t ** threads)
+{
+ int i;
+ apr_status_t rv, thread_rv;
+
+ if (listener) {
+ int iter;
+
+ /* deal with a rare timing window which affects waking up the
+ * listener thread... if the signal sent to the listener thread
+ * is delivered between the time it verifies that the
+ * listener_may_exit flag is clear and the time it enters a
+ * blocking syscall, the signal didn't do any good... work around
+ * that by sleeping briefly and sending it again
+ */
+
+ iter = 0;
+ while (iter < 10 &&
+#ifdef HAVE_PTHREAD_KILL
+ pthread_kill(*listener_os_thread, 0)
+#else
+ kill(ap_my_pid, 0)
+#endif
+ == 0) {
+ /* listener not dead yet */
+ apr_sleep(apr_time_make(0, 500000));
+ wakeup_listener();
+ ++iter;
+ }
+ if (iter >= 10) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "the listener thread didn't exit");
+ }
+ else {
+ rv = apr_thread_join(&thread_rv, listener);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "apr_thread_join: unable to join listener thread");
+ }
+ }
+ }
+
+ for (i = 0; i < ap_threads_per_child; i++) {
+ if (threads[i]) { /* if we ever created this thread */
+ rv = apr_thread_join(&thread_rv, threads[i]);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "apr_thread_join: unable to join worker "
+ "thread %d", i);
+ }
+ }
+ }
+}
+
+static void join_start_thread(apr_thread_t * start_thread_id)
+{
+ apr_status_t rv, thread_rv;
+
+ start_thread_may_exit = 1; /* tell it to give up in case it is still
+ * trying to take over slots from a
+ * previous generation
+ */
+ rv = apr_thread_join(&thread_rv, start_thread_id);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "apr_thread_join: unable to join the start " "thread");
+ }
+}
+
+static void child_main(int child_num_arg)
+{
+ apr_thread_t **threads;
+ apr_status_t rv;
+ thread_starter *ts;
+ apr_threadattr_t *thread_attr;
+ apr_thread_t *start_thread_id;
+
+ mpm_state = AP_MPMQ_STARTING; /* for benefit of any hooks that run as this
+ * child initializes
+ */
+ ap_my_pid = getpid();
+ ap_fatal_signal_child_setup(ap_server_conf);
+ apr_pool_create(&pchild, pconf);
+
+ /*stuff to do before we switch id's, so we have permissions. */
+ ap_reopen_scoreboard(pchild, NULL, 0);
+
+ if (unixd_setup_child()) {
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ap_run_child_init(pchild, ap_server_conf);
+
+ /* done with init critical section */
+
+ /* Just use the standard apr_setup_signal_thread to block all signals
+ * from being received. The child processes no longer use signals for
+ * any communication with the parent process.
+ */
+ rv = apr_setup_signal_thread();
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf,
+ "Couldn't initialize signal thread");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ if (ap_max_requests_per_child) {
+ requests_this_child = ap_max_requests_per_child;
+ }
+ else {
+ /* coding a value of zero means infinity */
+ requests_this_child = INT_MAX;
+ }
+
+ /* Setup worker threads */
+
+ /* clear the storage; we may not create all our threads immediately,
+ * and we want a 0 entry to indicate a thread which was not created
+ */
+ threads = (apr_thread_t **) calloc(1,
+ sizeof(apr_thread_t *) *
+ ap_threads_per_child);
+ if (threads == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, ap_server_conf,
+ "malloc: out of memory");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ts = (thread_starter *) apr_palloc(pchild, sizeof(*ts));
+
+ apr_threadattr_create(&thread_attr, pchild);
+ /* 0 means PTHREAD_CREATE_JOINABLE */
+ apr_threadattr_detach_set(thread_attr, 0);
+
+ if (ap_thread_stacksize != 0) {
+ apr_threadattr_stacksize_set(thread_attr, ap_thread_stacksize);
+ }
+
+ ts->threads = threads;
+ ts->listener = NULL;
+ ts->child_num_arg = child_num_arg;
+ ts->threadattr = thread_attr;
+
+ rv = apr_thread_create(&start_thread_id, thread_attr, start_threads,
+ ts, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "apr_thread_create: unable to create worker thread");
+ /* let the parent decide how bad this really is */
+ clean_child_exit(APEXIT_CHILDSICK);
+ }
+
+ mpm_state = AP_MPMQ_RUNNING;
+
+ /* If we are only running in one_process mode, we will want to
+ * still handle signals. */
+ if (one_process) {
+ /* Block until we get a terminating signal. */
+ apr_signal_thread(check_signal);
+ /* make sure the start thread has finished; signal_threads()
+ * and join_workers() depend on that
+ */
+ /* XXX join_start_thread() won't be awakened if one of our
+ * threads encounters a critical error and attempts to
+ * shutdown this child
+ */
+ join_start_thread(start_thread_id);
+
+ /* helps us terminate a little more quickly than the dispatch of the
+ * signal thread; beats the Pipe of Death and the browsers
+ */
+ signal_threads(ST_UNGRACEFUL);
+
+ /* A terminating signal was received. Now join each of the
+ * workers to clean them up.
+ * If the worker already exited, then the join frees
+ * their resources and returns.
+ * If the worker hasn't exited, then this blocks until
+ * they have (then cleans up).
+ */
+ join_workers(ts->listener, threads);
+ }
+ else { /* !one_process */
+ /* remove SIGTERM from the set of blocked signals... if one of
+ * the other threads in the process needs to take us down
+ * (e.g., for MaxRequestsPerChild) it will send us SIGTERM
+ */
+ unblock_signal(SIGTERM);
+ apr_signal(SIGTERM, dummy_signal_handler);
+ /* Watch for any messages from the parent over the POD */
+ while (1) {
+ rv = ap_mpm_pod_check(pod);
+ if (rv == AP_NORESTART) {
+ /* see if termination was triggered while we slept */
+ switch (terminate_mode) {
+ case ST_GRACEFUL:
+ rv = AP_GRACEFUL;
+ break;
+ case ST_UNGRACEFUL:
+ rv = AP_RESTART;
+ break;
+ }
+ }
+ if (rv == AP_GRACEFUL || rv == AP_RESTART) {
+ /* make sure the start thread has finished;
+ * signal_threads() and join_workers depend on that
+ */
+ join_start_thread(start_thread_id);
+ signal_threads(rv ==
+ AP_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL);
+ break;
+ }
+ }
+
+ /* A terminating signal was received. Now join each of the
+ * workers to clean them up.
+ * If the worker already exited, then the join frees
+ * their resources and returns.
+ * If the worker hasn't exited, then this blocks until
+ * they have (then cleans up).
+ */
+ join_workers(ts->listener, threads);
+ }
+
+ free(threads);
+
+ clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0);
+}
+
+static int make_child(server_rec * s, int slot)
+{
+ int pid;
+
+ if (slot + 1 > ap_max_daemons_limit) {
+ ap_max_daemons_limit = slot + 1;
+ }
+
+ if (one_process) {
+ set_signals();
+ ap_scoreboard_image->parent[slot].pid = getpid();
+ child_main(slot);
+ }
+
+ if ((pid = fork()) == -1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, s,
+ "fork: Unable to fork new process");
+
+ /* fork didn't succeed. Fix the scoreboard or else
+ * it will say SERVER_STARTING forever and ever
+ */
+ ap_update_child_status_from_indexes(slot, 0, SERVER_DEAD, NULL);
+
+ /* In case system resources are maxxed out, we don't want
+ Apache running away with the CPU trying to fork over and
+ over and over again. */
+ apr_sleep(apr_time_from_sec(10));
+
+ return -1;
+ }
+
+ if (!pid) {
+#ifdef HAVE_BINDPROCESSOR
+ /* By default, AIX binds to a single processor. This bit unbinds
+ * children which will then bind to another CPU.
+ */
+ int status = bindprocessor(BINDPROCESS, (int) getpid(),
+ PROCESSOR_CLASS_ANY);
+ if (status != OK)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno,
+ ap_server_conf,
+ "processor unbind failed %d", status);
+#endif
+ RAISE_SIGSTOP(MAKE_CHILD);
+
+ apr_signal(SIGTERM, just_die);
+ child_main(slot);
+
+ clean_child_exit(0);
+ }
+ /* else */
+ ap_scoreboard_image->parent[slot].quiescing = 0;
+ ap_scoreboard_image->parent[slot].pid = pid;
+ return 0;
+}
+
+/* start up a bunch of children */
+static void startup_children(int number_to_start)
+{
+ int i;
+
+ for (i = 0; number_to_start && i < ap_daemons_limit; ++i) {
+ if (ap_scoreboard_image->parent[i].pid != 0) {
+ continue;
+ }
+ if (make_child(ap_server_conf, i) < 0) {
+ break;
+ }
+ --number_to_start;
+ }
+}
+
+
+/*
+ * idle_spawn_rate is the number of children that will be spawned on the
+ * next maintenance cycle if there aren't enough idle servers. It is
+ * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
+ * without the need to spawn.
+ */
+static int idle_spawn_rate = 1;
+#ifndef MAX_SPAWN_RATE
+#define MAX_SPAWN_RATE (32)
+#endif
+static int hold_off_on_exponential_spawning;
+
+static void perform_idle_server_maintenance(void)
+{
+ int i, j;
+ int idle_thread_count;
+ worker_score *ws;
+ process_score *ps;
+ int free_length;
+ int totally_free_length = 0;
+ int free_slots[MAX_SPAWN_RATE];
+ int last_non_dead;
+ int total_non_dead;
+ int active_thread_count = 0;
+
+ /* initialize the free_list */
+ free_length = 0;
+
+ idle_thread_count = 0;
+ last_non_dead = -1;
+ total_non_dead = 0;
+
+ for (i = 0; i < ap_daemons_limit; ++i) {
+ /* Initialization to satisfy the compiler. It doesn't know
+ * that ap_threads_per_child is always > 0 */
+ int status = SERVER_DEAD;
+ int any_dying_threads = 0;
+ int any_dead_threads = 0;
+ int all_dead_threads = 1;
+
+ if (i >= ap_max_daemons_limit
+ && totally_free_length == idle_spawn_rate)
+ break;
+ ps = &ap_scoreboard_image->parent[i];
+ for (j = 0; j < ap_threads_per_child; j++) {
+ ws = &ap_scoreboard_image->servers[i][j];
+ status = ws->status;
+
+ /* XXX any_dying_threads is probably no longer needed GLA */
+ any_dying_threads = any_dying_threads ||
+ (status == SERVER_GRACEFUL);
+ any_dead_threads = any_dead_threads || (status == SERVER_DEAD);
+ all_dead_threads = all_dead_threads &&
+ (status == SERVER_DEAD || status == SERVER_GRACEFUL);
+
+ /* We consider a starting server as idle because we started it
+ * at least a cycle ago, and if it still hasn't finished starting
+ * then we're just going to swamp things worse by forking more.
+ * So we hopefully won't need to fork more if we count it.
+ * This depends on the ordering of SERVER_READY and SERVER_STARTING.
+ */
+ if (ps->pid != 0) { /* XXX just set all_dead_threads in outer
+ for loop if no pid? not much else matters */
+ if (status <= SERVER_READY &&
+ !ps->quiescing && ps->generation == ap_my_generation) {
+ ++idle_thread_count;
+ }
+ if (status >= SERVER_READY && status < SERVER_GRACEFUL) {
+ ++active_thread_count;
+ }
+ }
+ }
+ if (any_dead_threads
+ && totally_free_length < idle_spawn_rate
+ && free_length < MAX_SPAWN_RATE
+ && (!ps->pid /* no process in the slot */
+ || ps->quiescing)) { /* or at least one is going away */
+ if (all_dead_threads) {
+ /* great! we prefer these, because the new process can
+ * start more threads sooner. So prioritize this slot
+ * by putting it ahead of any slots with active threads.
+ *
+ * first, make room by moving a slot that's potentially still
+ * in use to the end of the array
+ */
+ free_slots[free_length] = free_slots[totally_free_length];
+ free_slots[totally_free_length++] = i;
+ }
+ else {
+ /* slot is still in use - back of the bus
+ */
+ free_slots[free_length] = i;
+ }
+ ++free_length;
+ }
+ /* XXX if (!ps->quiescing) is probably more reliable GLA */
+ if (!any_dying_threads) {
+ last_non_dead = i;
+ ++total_non_dead;
+ }
+ }
+
+ if (sick_child_detected) {
+ if (active_thread_count > 0) {
+ /* some child processes appear to be working. don't kill the
+ * whole server.
+ */
+ sick_child_detected = 0;
+ }
+ else {
+ /* looks like a basket case. give up.
+ */
+ shutdown_pending = 1;
+ child_fatal = 1;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0,
+ ap_server_conf,
+ "No active workers found..."
+ " Apache is exiting!");
+ /* the child already logged the failure details */
+ return;
+ }
+ }
+
+ ap_max_daemons_limit = last_non_dead + 1;
+
+ if (idle_thread_count > max_spare_threads) {
+ /* Kill off one child */
+ ap_mpm_pod_signal(pod, TRUE);
+ idle_spawn_rate = 1;
+ }
+ else if (idle_thread_count < min_spare_threads) {
+ /* terminate the free list */
+ if (free_length == 0) {
+ /* only report this condition once */
+ static int reported = 0;
+
+ if (!reported) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ ap_server_conf,
+ "server reached MaxClients setting, consider"
+ " raising the MaxClients setting");
+ reported = 1;
+ }
+ idle_spawn_rate = 1;
+ }
+ else {
+ if (free_length > idle_spawn_rate) {
+ free_length = idle_spawn_rate;
+ }
+ if (idle_spawn_rate >= 8) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0,
+ ap_server_conf,
+ "server seems busy, (you may need "
+ "to increase StartServers, ThreadsPerChild "
+ "or Min/MaxSpareThreads), "
+ "spawning %d children, there are around %d idle "
+ "threads, and %d total children", free_length,
+ idle_thread_count, total_non_dead);
+ }
+ for (i = 0; i < free_length; ++i) {
+ make_child(ap_server_conf, free_slots[i]);
+ }
+ /* the next time around we want to spawn twice as many if this
+ * wasn't good enough, but not if we've just done a graceful
+ */
+ if (hold_off_on_exponential_spawning) {
+ --hold_off_on_exponential_spawning;
+ }
+ else if (idle_spawn_rate < MAX_SPAWN_RATE) {
+ idle_spawn_rate *= 2;
+ }
+ }
+ }
+ else {
+ idle_spawn_rate = 1;
+ }
+}
+
+static void server_main_loop(int remaining_children_to_start)
+{
+ int child_slot;
+ apr_exit_why_e exitwhy;
+ int status, processed_status;
+ apr_proc_t pid;
+ int i;
+
+ while (!restart_pending && !shutdown_pending) {
+ ap_wait_or_timeout(&exitwhy, &status, &pid, pconf);
+
+ if (pid.pid != -1) {
+ processed_status = ap_process_child_status(&pid, exitwhy, status);
+ if (processed_status == APEXIT_CHILDFATAL) {
+ shutdown_pending = 1;
+ child_fatal = 1;
+ return;
+ }
+ else if (processed_status == APEXIT_CHILDSICK) {
+ /* tell perform_idle_server_maintenance to check into this
+ * on the next timer pop
+ */
+ sick_child_detected = 1;
+ }
+ /* non-fatal death... note that it's gone in the scoreboard. */
+ child_slot = find_child_by_pid(&pid);
+ if (child_slot >= 0) {
+ for (i = 0; i < ap_threads_per_child; i++)
+ ap_update_child_status_from_indexes(child_slot, i,
+ SERVER_DEAD,
+ (request_rec *) NULL);
+
+ ap_scoreboard_image->parent[child_slot].pid = 0;
+ ap_scoreboard_image->parent[child_slot].quiescing = 0;
+ if (processed_status == APEXIT_CHILDSICK) {
+ /* resource shortage, minimize the fork rate */
+ idle_spawn_rate = 1;
+ }
+ else if (remaining_children_to_start
+ && child_slot < ap_daemons_limit) {
+ /* we're still doing a 1-for-1 replacement of dead
+ * children with new children
+ */
+ make_child(ap_server_conf, child_slot);
+ --remaining_children_to_start;
+ }
+#if APR_HAS_OTHER_CHILD
+ }
+ else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH,
+ status) == 0) {
+ /* handled */
+#endif
+ }
+ else if (is_graceful) {
+ /* Great, we've probably just lost a slot in the
+ * scoreboard. Somehow we don't know about this child.
+ */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ ap_server_conf,
+ "long lost child came home! (pid %ld)",
+ (long) pid.pid);
+ }
+ /* Don't perform idle maintenance when a child dies,
+ * only do it when there's a timeout. Remember only a
+ * finite number of children can die, and it's pretty
+ * pathological for a lot to die suddenly.
+ */
+ continue;
+ }
+ else if (remaining_children_to_start) {
+ /* we hit a 1 second timeout in which none of the previous
+ * generation of children needed to be reaped... so assume
+ * they're all done, and pick up the slack if any is left.
+ */
+ startup_children(remaining_children_to_start);
+ remaining_children_to_start = 0;
+ /* In any event we really shouldn't do the code below because
+ * few of the servers we just started are in the IDLE state
+ * yet, so we'd mistakenly create an extra server.
+ */
+ continue;
+ }
+
+ perform_idle_server_maintenance();
+ }
+}
+
+int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s)
+{
+ int remaining_children_to_start;
+
+ ap_log_pid(pconf, ap_pid_fname);
+
+ first_server_limit = server_limit;
+ first_thread_limit = thread_limit;
+
+ if (changed_limit_at_restart) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "WARNING: Attempt to change ServerLimit or ThreadLimit "
+ "ignored during restart");
+ changed_limit_at_restart = 0;
+ }
+
+ if (!is_graceful) {
+ if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+ /* fix the generation number in the global score; we just got a new,
+ * cleared scoreboard
+ */
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+ }
+
+ set_signals();
+ /* Don't thrash... */
+ if (max_spare_threads < min_spare_threads + ap_threads_per_child)
+ max_spare_threads = min_spare_threads + ap_threads_per_child;
+
+ /* If we're doing a graceful_restart then we're going to see a lot
+ * of children exiting immediately when we get into the main loop
+ * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty
+ * rapidly... and for each one that exits we'll start a new one until
+ * we reach at least daemons_min_free. But we may be permitted to
+ * start more than that, so we'll just keep track of how many we're
+ * supposed to start up without the 1 second penalty between each fork.
+ */
+ remaining_children_to_start = ap_daemons_to_start;
+ if (remaining_children_to_start > ap_daemons_limit) {
+ remaining_children_to_start = ap_daemons_limit;
+ }
+ if (!is_graceful) {
+ startup_children(remaining_children_to_start);
+ remaining_children_to_start = 0;
+ }
+ else {
+ /* give the system some time to recover before kicking into
+ * exponential mode */
+ hold_off_on_exponential_spawning = 10;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+
+ restart_pending = shutdown_pending = 0;
+ mpm_state = AP_MPMQ_RUNNING;
+
+ server_main_loop(remaining_children_to_start);
+ mpm_state = AP_MPMQ_STOPPING;
+
+ if (shutdown_pending && !is_graceful) {
+ /* Time to shut down:
+ * Kill child processes, tell them to call child_exit, etc...
+ */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, FALSE);
+ ap_reclaim_child_processes(1); /* Start with SIGTERM */
+
+ if (!child_fatal) {
+ /* cleanup pid file on normal shutdown */
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative(pconf, ap_pid_fname);
+ if (pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0,
+ ap_server_conf,
+ "removed PID file %s (pid=%ld)",
+ pidfile, (long) getpid());
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0,
+ ap_server_conf, "caught SIGTERM, shutting down");
+ }
+ return 1;
+ } else if (shutdown_pending) {
+ /* Time to gracefully shut down:
+ * Kill child processes, tell them to call child_exit, etc...
+ */
+ int active_children;
+ int index;
+ apr_time_t cutoff = 0;
+
+ /* Close our listeners, and then ask our children to do same */
+ ap_close_listeners();
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, TRUE);
+ ap_relieve_child_processes();
+
+ if (!child_fatal) {
+ /* cleanup pid file on normal shutdown */
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+ if ( pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0,
+ ap_server_conf,
+ "removed PID file %s (pid=%ld)",
+ pidfile, (long)getpid());
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught " AP_SIG_GRACEFUL_STOP_STRING
+ ", shutting down gracefully");
+ }
+
+ if (ap_graceful_shutdown_timeout) {
+ cutoff = apr_time_now() +
+ apr_time_from_sec(ap_graceful_shutdown_timeout);
+ }
+
+ /* Don't really exit until each child has finished */
+ shutdown_pending = 0;
+ do {
+ /* Pause for a second */
+ apr_sleep(apr_time_from_sec(1));
+
+ /* Relieve any children which have now exited */
+ ap_relieve_child_processes();
+
+ active_children = 0;
+ for (index = 0; index < ap_daemons_limit; ++index) {
+ if (MPM_CHILD_PID(index) != 0) {
+ if (kill(MPM_CHILD_PID(index), 0) == 0) {
+ active_children = 1;
+ /* Having just one child is enough to stay around */
+ break;
+ }
+ }
+ }
+ } while (!shutdown_pending && active_children &&
+ (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff));
+
+ /* We might be here because we received SIGTERM, either
+ * way, try and make sure that all of our processes are
+ * really dead.
+ */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, FALSE);
+ ap_reclaim_child_processes(1);
+
+ return 1;
+ }
+
+ /* we've been told to restart */
+ apr_signal(SIGHUP, SIG_IGN);
+
+ if (one_process) {
+ /* not worth thinking about */
+ return 1;
+ }
+
+ /* advance to the next generation */
+ /* XXX: we really need to make sure this new generation number isn't in
+ * use by any of the children.
+ */
+ ++ap_my_generation;
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+
+ if (is_graceful) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ AP_SIG_GRACEFUL_STRING
+ " received. Doing graceful restart");
+ /* wake up the children...time to die. But we'll have more soon */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, TRUE);
+
+
+ /* This is mostly for debugging... so that we know what is still
+ * gracefully dealing with existing request.
+ */
+
+ }
+ else {
+ /* Kill 'em all. Since the child acts the same on the parents SIGTERM
+ * and a SIGHUP, we may as well use the same signal, because some user
+ * pthreads are stealing signals from us left and right.
+ */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, FALSE);
+
+ ap_reclaim_child_processes(1); /* Start with SIGTERM */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "SIGHUP received. Attempting to restart");
+ }
+
+ return 0;
+}
+
+/* This really should be a post_config hook, but the error log is already
+ * redirected by that point, so we need to do this in the open_logs phase.
+ */
+static int worker_open_logs(apr_pool_t * p, apr_pool_t * plog,
+ apr_pool_t * ptemp, server_rec * s)
+{
+ apr_status_t rv;
+
+ pconf = p;
+ ap_server_conf = s;
+
+ if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT | APLOG_STARTUP, 0,
+ NULL, "no listening sockets available, shutting down");
+ return DONE;
+ }
+
+ if (!one_process) {
+ if ((rv = ap_mpm_pod_open(pconf, &pod))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT | APLOG_STARTUP, rv, NULL,
+ "Could not open pipe-of-death.");
+ return DONE;
+ }
+ }
+ return OK;
+}
+
+static int worker_pre_config(apr_pool_t * pconf, apr_pool_t * plog,
+ apr_pool_t * ptemp)
+{
+ static int restart_num = 0;
+ int no_detach, debug, foreground;
+ ap_directive_t *pdir;
+ ap_directive_t *max_clients = NULL;
+ apr_status_t rv;
+
+ mpm_state = AP_MPMQ_STARTING;
+
+ /* make sure that "ThreadsPerChild" gets set before "MaxClients" */
+ for (pdir = ap_conftree; pdir != NULL; pdir = pdir->next) {
+ if (strncasecmp(pdir->directive, "ThreadsPerChild", 15) == 0) {
+ if (!max_clients) {
+ /* we're in the clear, got ThreadsPerChild first */
+ break;
+ }
+ else {
+ /* now to swap the data */
+ ap_directive_t temp;
+
+ temp.directive = pdir->directive;
+ temp.args = pdir->args;
+ /* Make sure you don't change 'next', or you may get loops! */
+ /* XXX: first_child, parent, and data can never be set
+ * for these directives, right? -aaron */
+ temp.filename = pdir->filename;
+ temp.line_num = pdir->line_num;
+
+ pdir->directive = max_clients->directive;
+ pdir->args = max_clients->args;
+ pdir->filename = max_clients->filename;
+ pdir->line_num = max_clients->line_num;
+
+ max_clients->directive = temp.directive;
+ max_clients->args = temp.args;
+ max_clients->filename = temp.filename;
+ max_clients->line_num = temp.line_num;
+ break;
+ }
+ }
+ else if (!max_clients
+ && strncasecmp(pdir->directive, "MaxClients", 10) == 0) {
+ max_clients = pdir;
+ }
+ }
+
+ debug = ap_exists_config_define("DEBUG");
+
+ if (debug) {
+ foreground = one_process = 1;
+ no_detach = 0;
+ }
+ else {
+ one_process = ap_exists_config_define("ONE_PROCESS");
+ no_detach = ap_exists_config_define("NO_DETACH");
+ foreground = ap_exists_config_define("FOREGROUND");
+ }
+
+ /* sigh, want this only the second time around */
+ if (restart_num++ == 1) {
+ is_graceful = 0;
+ rv = apr_pollset_create(&event_pollset, 1, plog,
+ APR_POLLSET_THREADSAFE);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "Couldn't create a Thread Safe Pollset. "
+ "Is it supported on your platform?");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_pollset_destroy(event_pollset);
+
+ if (!one_process && !foreground) {
+ rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND
+ : APR_PROC_DETACH_DAEMONIZE);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "apr_proc_detach failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ parent_pid = ap_my_pid = getpid();
+ }
+
+ unixd_pre_config(ptemp);
+ ap_listen_pre_config();
+ ap_daemons_to_start = DEFAULT_START_DAEMON;
+ min_spare_threads = DEFAULT_MIN_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD;
+ max_spare_threads = DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD;
+ ap_daemons_limit = server_limit;
+ ap_threads_per_child = DEFAULT_THREADS_PER_CHILD;
+ ap_pid_fname = DEFAULT_PIDLOG;
+ ap_lock_fname = DEFAULT_LOCKFILE;
+ ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
+ ap_extended_status = 0;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+
+ apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir));
+
+ return OK;
+}
+
+static void event_hooks(apr_pool_t * p)
+{
+ /* The worker open_logs phase must run before the core's, or stderr
+ * will be redirected to a file, and the messages won't print to the
+ * console.
+ */
+ static const char *const aszSucc[] = { "core.c", NULL };
+ one_process = 0;
+
+ ap_hook_open_logs(worker_open_logs, NULL, aszSucc, APR_HOOK_MIDDLE);
+ /* we need to set the MPM state before other pre-config hooks use MPM query
+ * to retrieve it, so register as REALLY_FIRST
+ */
+ ap_hook_pre_config(worker_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_to_start = atoi(arg);
+ return NULL;
+}
+
+static const char *set_min_spare_threads(cmd_parms * cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ min_spare_threads = atoi(arg);
+ if (min_spare_threads <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: detected MinSpareThreads set to non-positive.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Resetting to 1 to avoid almost certain Apache failure.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Please read the documentation.");
+ min_spare_threads = 1;
+ }
+
+ return NULL;
+}
+
+static const char *set_max_spare_threads(cmd_parms * cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ max_spare_threads = atoi(arg);
+ return NULL;
+}
+
+static const char *set_max_clients(cmd_parms * cmd, void *dummy,
+ const char *arg)
+{
+ int max_clients;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* It is ok to use ap_threads_per_child here because we are
+ * sure that it gets set before MaxClients in the pre_config stage. */
+ max_clients = atoi(arg);
+ if (max_clients < ap_threads_per_child) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients (%d) must be at least as large",
+ max_clients);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " as ThreadsPerChild (%d). Automatically",
+ ap_threads_per_child);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " increasing MaxClients to %d.", ap_threads_per_child);
+ max_clients = ap_threads_per_child;
+ }
+ ap_daemons_limit = max_clients / ap_threads_per_child;
+ if ((max_clients > 0) && (max_clients % ap_threads_per_child)) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients (%d) is not an integer multiple",
+ max_clients);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " of ThreadsPerChild (%d), lowering MaxClients to %d",
+ ap_threads_per_child,
+ ap_daemons_limit * ap_threads_per_child);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " for a maximum of %d child processes,",
+ ap_daemons_limit);
+ max_clients = ap_daemons_limit * ap_threads_per_child;
+ }
+ if (ap_daemons_limit > server_limit) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients of %d would require %d servers,",
+ max_clients, ap_daemons_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " and would exceed the ServerLimit value of %d.",
+ server_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " Automatically lowering MaxClients to %d. To increase,",
+ server_limit * ap_threads_per_child);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " please see the ServerLimit directive.");
+ ap_daemons_limit = server_limit;
+ }
+ else if (ap_daemons_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require MaxClients > 0, setting to 1");
+ ap_daemons_limit = 1;
+ }
+ return NULL;
+}
+
+static const char *set_threads_per_child(cmd_parms * cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_per_child = atoi(arg);
+ if (ap_threads_per_child > thread_limit) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ThreadsPerChild of %d exceeds ThreadLimit "
+ "value of %d", ap_threads_per_child, thread_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "threads, lowering ThreadsPerChild to %d. To increase, "
+ "please see the", thread_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " ThreadLimit directive.");
+ ap_threads_per_child = thread_limit;
+ }
+ else if (ap_threads_per_child < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ThreadsPerChild > 0, setting to 1");
+ ap_threads_per_child = 1;
+ }
+ return NULL;
+}
+static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ int tmp_server_limit;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ tmp_server_limit = atoi(arg);
+ /* you cannot change ServerLimit across a restart; ignore
+ * any such attempts
+ */
+ if (first_server_limit &&
+ tmp_server_limit != server_limit) {
+ /* how do we log a message? the error log is a bit bucket at this
+ * point; we'll just have to set a flag so that ap_mpm_run()
+ * logs a warning later
+ */
+ changed_limit_at_restart = 1;
+ return NULL;
+ }
+ server_limit = tmp_server_limit;
+
+ if (server_limit > MAX_SERVER_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ServerLimit of %d exceeds compile time limit "
+ "of %d servers,", server_limit, MAX_SERVER_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ServerLimit to %d.", MAX_SERVER_LIMIT);
+ server_limit = MAX_SERVER_LIMIT;
+ }
+ else if (server_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ServerLimit > 0, setting to 1");
+ server_limit = 1;
+ }
+ return NULL;
+}
+
+static const char *set_thread_limit(cmd_parms * cmd, void *dummy,
+ const char *arg)
+{
+ int tmp_thread_limit;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ tmp_thread_limit = atoi(arg);
+ /* you cannot change ThreadLimit across a restart; ignore
+ * any such attempts
+ */
+ if (first_thread_limit && tmp_thread_limit != thread_limit) {
+ /* how do we log a message? the error log is a bit bucket at this
+ * point; we'll just have to set a flag so that ap_mpm_run()
+ * logs a warning later
+ */
+ changed_limit_at_restart = 1;
+ return NULL;
+ }
+ thread_limit = tmp_thread_limit;
+
+ if (thread_limit > MAX_THREAD_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ThreadLimit of %d exceeds compile time limit "
+ "of %d servers,", thread_limit, MAX_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ThreadLimit to %d.", MAX_THREAD_LIMIT);
+ thread_limit = MAX_THREAD_LIMIT;
+ }
+ else if (thread_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ThreadLimit > 0, setting to 1");
+ thread_limit = 1;
+ }
+ return NULL;
+}
+
+static const command_rec event_cmds[] = {
+ UNIX_DAEMON_COMMANDS,
+ LISTEN_COMMANDS,
+ AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF,
+ "Number of child processes launched at server startup"),
+ AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF,
+ "Maximum number of child processes for this run of Apache"),
+ AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
+ "Minimum number of idle threads, to handle request spikes"),
+ AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
+ "Maximum number of idle threads"),
+ AP_INIT_TAKE1("MaxClients", set_max_clients, NULL, RSRC_CONF,
+ "Maximum number of threads alive at the same time"),
+ AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF,
+ "Number of threads each child creates"),
+ AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF,
+ "Maximum number of worker threads per child process for this "
+ "run of Apache - Upper limit for ThreadsPerChild"),
+ AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND,
+ {NULL}
+};
+
+module AP_MODULE_DECLARE_DATA mpm_event_module = {
+ MPM20_MODULE_STUFF,
+ ap_mpm_rewrite_args, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ event_cmds, /* command apr_table_t */
+ event_hooks /* register_hooks */
+};
diff --git a/server/mpm/experimental/event/fdqueue.c b/server/mpm/experimental/event/fdqueue.c
new file mode 100644
index 00000000..51952ced
--- /dev/null
+++ b/server/mpm/experimental/event/fdqueue.c
@@ -0,0 +1,420 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fdqueue.h"
+#include "apr_atomic.h"
+
+typedef struct recycled_pool
+{
+ apr_pool_t *pool;
+ struct recycled_pool *next;
+} recycled_pool;
+
+struct fd_queue_info_t
+{
+ apr_int32_t idlers; /**
+ * 0 or positive: number of idle worker threads
+ * negative: number of threads blocked waiting
+ * for an idle worker
+ */
+ apr_thread_mutex_t *idlers_mutex;
+ apr_thread_cond_t *wait_for_idler;
+ int terminated;
+ int max_idlers;
+ recycled_pool *recycled_pools;
+};
+
+static apr_status_t queue_info_cleanup(void *data_)
+{
+ fd_queue_info_t *qi = data_;
+ apr_thread_cond_destroy(qi->wait_for_idler);
+ apr_thread_mutex_destroy(qi->idlers_mutex);
+
+ /* Clean up any pools in the recycled list */
+ for (;;) {
+ struct recycled_pool *first_pool = qi->recycled_pools;
+ if (first_pool == NULL) {
+ break;
+ }
+ if (apr_atomic_casptr
+ ((volatile void **) &(qi->recycled_pools), first_pool->next,
+ first_pool) == first_pool) {
+ apr_pool_destroy(first_pool->pool);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_queue_info_create(fd_queue_info_t ** queue_info,
+ apr_pool_t * pool, int max_idlers)
+{
+ apr_status_t rv;
+ fd_queue_info_t *qi;
+
+ qi = apr_palloc(pool, sizeof(*qi));
+ memset(qi, 0, sizeof(*qi));
+
+ rv = apr_thread_mutex_create(&qi->idlers_mutex, APR_THREAD_MUTEX_DEFAULT,
+ pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ rv = apr_thread_cond_create(&qi->wait_for_idler, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ qi->recycled_pools = NULL;
+ qi->max_idlers = max_idlers;
+ apr_pool_cleanup_register(pool, qi, queue_info_cleanup,
+ apr_pool_cleanup_null);
+
+ *queue_info = qi;
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_queue_info_set_idle(fd_queue_info_t * queue_info,
+ apr_pool_t * pool_to_recycle)
+{
+ apr_status_t rv;
+ int prev_idlers;
+
+ ap_push_pool(queue_info, pool_to_recycle);
+
+ /* Atomically increment the count of idle workers */
+ prev_idlers = apr_atomic_inc32(&(queue_info->idlers));
+
+ /* If other threads are waiting on a worker, wake one up */
+ if (prev_idlers < 0) {
+ rv = apr_thread_mutex_lock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ return rv;
+ }
+ rv = apr_thread_cond_signal(queue_info->wait_for_idler);
+ if (rv != APR_SUCCESS) {
+ apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ return rv;
+ }
+ rv = apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info)
+{
+ apr_status_t rv;
+ int prev_idlers;
+
+ /* Atomically decrement the idle worker count, saving the old value */
+ prev_idlers = apr_atomic_add32(&(queue_info->idlers), -1);
+
+ /* Block if there weren't any idle workers */
+ if (prev_idlers <= 0) {
+ rv = apr_thread_mutex_lock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ AP_DEBUG_ASSERT(0);
+ apr_atomic_inc32(&(queue_info->idlers)); /* back out dec */
+ return rv;
+ }
+ /* Re-check the idle worker count to guard against a
+ * race condition. Now that we're in the mutex-protected
+ * region, one of two things may have happened:
+ * - If the idle worker count is still negative, the
+ * workers are all still busy, so it's safe to
+ * block on a condition variable.
+ * - If the idle worker count is non-negative, then a
+ * worker has become idle since the first check
+ * of queue_info->idlers above. It's possible
+ * that the worker has also signaled the condition
+ * variable--and if so, the listener missed it
+ * because it wasn't yet blocked on the condition
+ * variable. But if the idle worker count is
+ * now non-negative, it's safe for this function to
+ * return immediately.
+ *
+ * A negative value in queue_info->idlers tells how many
+ * threads are waiting on an idle worker.
+ */
+ if (queue_info->idlers < 0) {
+ rv = apr_thread_cond_wait(queue_info->wait_for_idler,
+ queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ apr_status_t rv2;
+ AP_DEBUG_ASSERT(0);
+ rv2 = apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ if (rv2 != APR_SUCCESS) {
+ return rv2;
+ }
+ return rv;
+ }
+ }
+ rv = apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ if (queue_info->terminated) {
+ return APR_EOF;
+ }
+ else {
+ return APR_SUCCESS;
+ }
+}
+
+
+void ap_push_pool(fd_queue_info_t * queue_info,
+ apr_pool_t * pool_to_recycle)
+{
+ /* If we have been given a pool to recycle, atomically link
+ * it into the queue_info's list of recycled pools
+ */
+ if (pool_to_recycle) {
+ struct recycled_pool *new_recycle;
+ new_recycle = (struct recycled_pool *) apr_palloc(pool_to_recycle,
+ sizeof
+ (*new_recycle));
+ new_recycle->pool = pool_to_recycle;
+ for (;;) {
+ new_recycle->next = queue_info->recycled_pools;
+ if (apr_atomic_casptr
+ ((volatile void **) &(queue_info->recycled_pools),
+ new_recycle, new_recycle->next) == new_recycle->next) {
+ break;
+ }
+ }
+ }
+}
+
+void ap_pop_pool(apr_pool_t ** recycled_pool, fd_queue_info_t * queue_info)
+{
+ /* Atomically pop a pool from the recycled list */
+
+ /* This function is safe only as long as it is single threaded because
+ * it reaches into the queue and accesses "next" which can change.
+ * We are OK today because it is only called from the listener thread.
+ * cas-based pushes do not have the same limitation - any number can
+ * happen concurrently with a single cas-based pop.
+ */
+
+ *recycled_pool = NULL;
+
+
+ /* Atomically pop a pool from the recycled list */
+ for (;;) {
+ struct recycled_pool *first_pool = queue_info->recycled_pools;
+ if (first_pool == NULL) {
+ break;
+ }
+ if (apr_atomic_casptr
+ ((volatile void **) &(queue_info->recycled_pools),
+ first_pool->next, first_pool) == first_pool) {
+ *recycled_pool = first_pool->pool;
+ break;
+ }
+ }
+}
+
+apr_status_t ap_queue_info_term(fd_queue_info_t * queue_info)
+{
+ apr_status_t rv;
+ rv = apr_thread_mutex_lock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ queue_info->terminated = 1;
+ apr_thread_cond_broadcast(queue_info->wait_for_idler);
+ return apr_thread_mutex_unlock(queue_info->idlers_mutex);
+}
+
+/**
+ * Detects when the fd_queue_t is full. This utility function is expected
+ * to be called from within critical sections, and is not threadsafe.
+ */
+#define ap_queue_full(queue) ((queue)->nelts == (queue)->bounds)
+
+/**
+ * Detects when the fd_queue_t is empty. This utility function is expected
+ * to be called from within critical sections, and is not threadsafe.
+ */
+#define ap_queue_empty(queue) ((queue)->nelts == 0)
+
+/**
+ * Callback routine that is called to destroy this
+ * fd_queue_t when its pool is destroyed.
+ */
+static apr_status_t ap_queue_destroy(void *data)
+{
+ fd_queue_t *queue = data;
+
+ /* Ignore errors here, we can't do anything about them anyway.
+ * XXX: We should at least try to signal an error here, it is
+ * indicative of a programmer error. -aaron */
+ apr_thread_cond_destroy(queue->not_empty);
+ apr_thread_mutex_destroy(queue->one_big_mutex);
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Initialize the fd_queue_t.
+ */
+apr_status_t ap_queue_init(fd_queue_t * queue, int queue_capacity,
+ apr_pool_t * a)
+{
+ int i;
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_create(&queue->one_big_mutex,
+ APR_THREAD_MUTEX_DEFAULT,
+ a)) != APR_SUCCESS) {
+ return rv;
+ }
+ if ((rv = apr_thread_cond_create(&queue->not_empty, a)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ queue->data = apr_palloc(a, queue_capacity * sizeof(fd_queue_elem_t));
+ queue->bounds = queue_capacity;
+ queue->nelts = 0;
+
+ /* Set all the sockets in the queue to NULL */
+ for (i = 0; i < queue_capacity; ++i)
+ queue->data[i].sd = NULL;
+
+ apr_pool_cleanup_register(a, queue, ap_queue_destroy,
+ apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Push a new socket onto the queue.
+ *
+ * precondition: ap_queue_info_wait_for_idler has already been called
+ * to reserve an idle worker thread
+ */
+apr_status_t ap_queue_push(fd_queue_t * queue, apr_socket_t * sd,
+ conn_state_t * cs, apr_pool_t * p)
+{
+ fd_queue_elem_t *elem;
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ AP_DEBUG_ASSERT(!queue->terminated);
+ AP_DEBUG_ASSERT(!ap_queue_full(queue));
+
+ elem = &queue->data[queue->nelts];
+ elem->sd = sd;
+ elem->cs = cs;
+ elem->p = p;
+ queue->nelts++;
+
+ apr_thread_cond_signal(queue->not_empty);
+
+ if ((rv = apr_thread_mutex_unlock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Retrieves the next available socket from the queue. If there are no
+ * sockets available, it will block until one becomes available.
+ * Once retrieved, the socket is placed into the address specified by
+ * 'sd'.
+ */
+apr_status_t ap_queue_pop(fd_queue_t * queue, apr_socket_t ** sd,
+ conn_state_t ** cs, apr_pool_t ** p)
+{
+ fd_queue_elem_t *elem;
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Keep waiting until we wake up and find that the queue is not empty. */
+ if (ap_queue_empty(queue)) {
+ if (!queue->terminated) {
+ apr_thread_cond_wait(queue->not_empty, queue->one_big_mutex);
+ }
+ /* If we wake up and it's still empty, then we were interrupted */
+ if (ap_queue_empty(queue)) {
+ rv = apr_thread_mutex_unlock(queue->one_big_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (queue->terminated) {
+ return APR_EOF; /* no more elements ever again */
+ }
+ else {
+ return APR_EINTR;
+ }
+ }
+ }
+
+ elem = &queue->data[--queue->nelts];
+ *sd = elem->sd;
+ *cs = elem->cs;
+ *p = elem->p;
+#ifdef AP_DEBUG
+ elem->sd = NULL;
+ elem->p = NULL;
+#endif /* AP_DEBUG */
+
+ rv = apr_thread_mutex_unlock(queue->one_big_mutex);
+ return rv;
+}
+
+apr_status_t ap_queue_interrupt_all(fd_queue_t * queue)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+ apr_thread_cond_broadcast(queue->not_empty);
+ return apr_thread_mutex_unlock(queue->one_big_mutex);
+}
+
+apr_status_t ap_queue_term(fd_queue_t * queue)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+ /* we must hold one_big_mutex when setting this... otherwise,
+ * we could end up setting it and waking everybody up just after a
+ * would-be popper checks it but right before they block
+ */
+ queue->terminated = 1;
+ if ((rv = apr_thread_mutex_unlock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+ return ap_queue_interrupt_all(queue);
+}
diff --git a/server/mpm/experimental/event/fdqueue.h b/server/mpm/experimental/event/fdqueue.h
new file mode 100644
index 00000000..bd18adfd
--- /dev/null
+++ b/server/mpm/experimental/event/fdqueue.h
@@ -0,0 +1,82 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file event/fdqueue.h
+ * @brief fd queue declarations
+ *
+ * @addtogroup APACHE_MPM_EVENT
+ * @{
+ */
+
+#ifndef FDQUEUE_H
+#define FDQUEUE_H
+#include "httpd.h"
+#include <stdlib.h>
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+#include <sys/types.h>
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#include <apr_errno.h>
+
+typedef struct fd_queue_info_t fd_queue_info_t;
+
+apr_status_t ap_queue_info_create(fd_queue_info_t ** queue_info,
+ apr_pool_t * pool, int max_idlers);
+apr_status_t ap_queue_info_set_idle(fd_queue_info_t * queue_info,
+ apr_pool_t * pool_to_recycle);
+apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info);
+apr_status_t ap_queue_info_term(fd_queue_info_t * queue_info);
+
+struct fd_queue_elem_t
+{
+ apr_socket_t *sd;
+ apr_pool_t *p;
+ conn_state_t *cs;
+};
+typedef struct fd_queue_elem_t fd_queue_elem_t;
+
+struct fd_queue_t
+{
+ fd_queue_elem_t *data;
+ int nelts;
+ int bounds;
+ apr_thread_mutex_t *one_big_mutex;
+ apr_thread_cond_t *not_empty;
+ int terminated;
+};
+typedef struct fd_queue_t fd_queue_t;
+
+void ap_pop_pool(apr_pool_t ** recycled_pool, fd_queue_info_t * queue_info);
+void ap_push_pool(fd_queue_info_t * queue_info,
+ apr_pool_t * pool_to_recycle);
+
+apr_status_t ap_queue_init(fd_queue_t * queue, int queue_capacity,
+ apr_pool_t * a);
+apr_status_t ap_queue_push(fd_queue_t * queue, apr_socket_t * sd,
+ conn_state_t * cs, apr_pool_t * p);
+apr_status_t ap_queue_pop(fd_queue_t * queue, apr_socket_t ** sd,
+ conn_state_t ** cs, apr_pool_t ** p);
+apr_status_t ap_queue_interrupt_all(fd_queue_t * queue);
+apr_status_t ap_queue_term(fd_queue_t * queue);
+
+#endif /* FDQUEUE_H */
+/** @} */
diff --git a/server/mpm/experimental/event/mpm.h b/server/mpm/experimental/event/mpm.h
new file mode 100644
index 00000000..cf4e61d9
--- /dev/null
+++ b/server/mpm/experimental/event/mpm.h
@@ -0,0 +1,62 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file event/mpm.h
+ * @brief Unix Exent driven MPM
+ *
+ * @defgroup APACHE_MPM_EVENT Unix Event MPM
+ * @ingroup APACHE_OS_UNIX APACHE_MPM
+ * @{
+ */
+
+#include "scoreboard.h"
+#include "unixd.h"
+
+#ifndef APACHE_MPM_EVENT_H
+#define APACHE_MPM_EVENT_H
+
+#define EVENT_MPM
+
+#define MPM_NAME "Event"
+
+#define AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+#define AP_MPM_WANT_WAIT_OR_TIMEOUT
+#define AP_MPM_WANT_PROCESS_CHILD_STATUS
+#define AP_MPM_WANT_SET_PIDFILE
+#define AP_MPM_WANT_SET_SCOREBOARD
+#define AP_MPM_WANT_SET_LOCKFILE
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_WANT_SET_COREDUMPDIR
+#define AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+#define AP_MPM_WANT_SIGNAL_SERVER
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+#define AP_MPM_WANT_SET_STACKSIZE
+#define AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
+#define AP_MPM_WANT_FATAL_SIGNAL_HANDLER
+#define AP_MPM_DISABLE_NAGLE_ACCEPTED_SOCK
+
+#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid)
+#define MPM_NOTE_CHILD_KILLED(i) (MPM_CHILD_PID(i) = 0)
+#define MPM_ACCEPT_FUNC unixd_accept
+
+extern int ap_threads_per_child;
+extern int ap_max_daemons_limit;
+extern server_rec *ap_server_conf;
+extern char ap_coredump_dir[MAX_STRING_LEN];
+
+#endif /* APACHE_MPM_EVENT_H */
+/** @} */
diff --git a/server/mpm/experimental/event/mpm_default.h b/server/mpm/experimental/event/mpm_default.h
new file mode 100644
index 00000000..c26ee714
--- /dev/null
+++ b/server/mpm/experimental/event/mpm_default.h
@@ -0,0 +1,79 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * @file event/mpm_default.h
+ * @brief Event MPM defaults
+ *
+ * @addtogroup APACHE_MPM_EVENT
+ * @{
+ */
+
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* Number of servers to spawn off by default --- also, if fewer than
+ * this free when the caretaker checks, it will spawn more.
+ */
+#ifndef DEFAULT_START_DAEMON
+#define DEFAULT_START_DAEMON 3
+#endif
+
+/* Maximum number of *free* server processes --- more than this, and
+ * they will die off.
+ */
+
+#ifndef DEFAULT_MAX_FREE_DAEMON
+#define DEFAULT_MAX_FREE_DAEMON 10
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+
+#ifndef DEFAULT_MIN_FREE_DAEMON
+#define DEFAULT_MIN_FREE_DAEMON 3
+#endif
+
+#ifndef DEFAULT_THREADS_PER_CHILD
+#define DEFAULT_THREADS_PER_CHILD 25
+#endif
+
+/* File used for accept locking, when we use a file */
+#ifndef DEFAULT_LOCKFILE
+#define DEFAULT_LOCKFILE DEFAULT_REL_RUNTIMEDIR "/accept.lock"
+#endif
+
+/* Where the main/parent process's pid is logged */
+#ifndef DEFAULT_PIDLOG
+#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+#endif
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If <= 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD
+#define DEFAULT_MAX_REQUESTS_PER_CHILD 10000
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/experimental/event/pod.c b/server/mpm/experimental/event/pod.c
new file mode 100644
index 00000000..5a9999f7
--- /dev/null
+++ b/server/mpm/experimental/event/pod.c
@@ -0,0 +1,109 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pod.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t * p, ap_pod_t ** pod)
+{
+ apr_status_t rv;
+
+ *pod = apr_palloc(p, sizeof(**pod));
+ rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+/*
+ apr_file_pipe_timeout_set((*pod)->pod_in, 0);
+*/
+ (*pod)->p = p;
+
+ /* close these before exec. */
+ apr_file_inherit_unset((*pod)->pod_in);
+ apr_file_inherit_unset((*pod)->pod_out);
+
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t * pod)
+{
+ char c;
+ apr_os_file_t fd;
+ int rc;
+
+ /* we need to surface EINTR so we'll have to grab the
+ * native file descriptor and do the OS read() ourselves
+ */
+ apr_os_file_get(&fd, pod->pod_in);
+ rc = read(fd, &c, 1);
+ if (rc == 1) {
+ switch (c) {
+ case RESTART_CHAR:
+ return AP_RESTART;
+ case GRACEFUL_CHAR:
+ return AP_GRACEFUL;
+ }
+ }
+ return AP_NORESTART;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t * pod)
+{
+ apr_status_t rv;
+
+ rv = apr_file_close(pod->pod_out);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_file_close(pod->pod_in);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ return rv;
+}
+
+static apr_status_t pod_signal_internal(ap_pod_t * pod, int graceful)
+{
+ apr_status_t rv;
+ char char_of_death = graceful ? GRACEFUL_CHAR : RESTART_CHAR;
+ apr_size_t one = 1;
+
+ rv = apr_file_write(pod->pod_out, &char_of_death, &one);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
+ "write pipe_of_death");
+ }
+ return rv;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t * pod, int graceful)
+{
+ return pod_signal_internal(pod, graceful);
+}
+
+AP_DECLARE(void) ap_mpm_pod_killpg(ap_pod_t * pod, int num, int graceful)
+{
+ int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ for (i = 0; i < num && rv == APR_SUCCESS; i++) {
+ rv = pod_signal_internal(pod, graceful);
+ }
+}
diff --git a/server/mpm/experimental/event/pod.h b/server/mpm/experimental/event/pod.h
new file mode 100644
index 00000000..78e2b9d7
--- /dev/null
+++ b/server/mpm/experimental/event/pod.h
@@ -0,0 +1,60 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file event/pod.h
+ * @brief pod definitions
+ *
+ * @addtogroup APACHE_MPM_EVENT
+ * @{
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "mpm.h"
+#include "mpm_common.h"
+#include "ap_mpm.h"
+#include "ap_listen.h"
+#include "mpm_default.h"
+
+#define RESTART_CHAR '$'
+#define GRACEFUL_CHAR '!'
+
+#define AP_RESTART 0
+#define AP_GRACEFUL 1
+
+typedef struct ap_pod_t ap_pod_t;
+
+struct ap_pod_t
+{
+ apr_file_t *pod_in;
+ apr_file_t *pod_out;
+ apr_pool_t *p;
+};
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t * p, ap_pod_t ** pod);
+AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t * pod);
+AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t * pod);
+AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t * pod, int graceful);
+AP_DECLARE(void) ap_mpm_pod_killpg(ap_pod_t * pod, int num, int graceful);
+/** @} */
diff --git a/server/mpm/mpmt_os2/Makefile.in b/server/mpm/mpmt_os2/Makefile.in
new file mode 100644
index 00000000..38e598ed
--- /dev/null
+++ b/server/mpm/mpmt_os2/Makefile.in
@@ -0,0 +1,5 @@
+
+LTLIBRARY_NAME = libmpmt_os2.la
+LTLIBRARY_SOURCES = mpmt_os2.c mpmt_os2_child.c
+
+include $(top_srcdir)/build/ltlib.mk
diff --git a/server/mpm/mpmt_os2/config5.m4 b/server/mpm/mpmt_os2/config5.m4
new file mode 100644
index 00000000..b27c296d
--- /dev/null
+++ b/server/mpm/mpmt_os2/config5.m4
@@ -0,0 +1,5 @@
+if test "$MPM_NAME" = "mpmt_os2" ; then
+ AC_CACHE_SAVE
+ APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile)
+ APR_ADDTO(CFLAGS,-Zmt)
+fi
diff --git a/server/mpm/mpmt_os2/mpm.h b/server/mpm/mpmt_os2/mpm.h
new file mode 100644
index 00000000..f8bbc30e
--- /dev/null
+++ b/server/mpm/mpmt_os2/mpm.h
@@ -0,0 +1,43 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mpmt_os2/mpm.h
+ * @brief MPM for os2
+ *
+ * @defgroup APACHE_MPM_OS2 os2 MPM
+ * @ingroup APACHE_OS_OS2 APACHE_MPM
+ */
+
+#ifndef APACHE_MPM_MPMT_OS2_H
+#define APACHE_MPM_MPMT_OS2_H
+
+#define MPMT_OS2_MPM
+
+#include "httpd.h"
+#include "mpm_default.h"
+#include "scoreboard.h"
+
+#define MPM_NAME "MPMT_OS2"
+
+extern server_rec *ap_server_conf;
+#define AP_MPM_WANT_SET_PIDFILE
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_DISABLE_NAGLE_ACCEPTED_SOCK
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+
+#endif /* APACHE_MPM_MPMT_OS2_H */
+/** @} */
diff --git a/server/mpm/mpmt_os2/mpm_default.h b/server/mpm/mpmt_os2/mpm_default.h
new file mode 100644
index 00000000..2a21cd50
--- /dev/null
+++ b/server/mpm/mpmt_os2/mpm_default.h
@@ -0,0 +1,68 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mpmt_os2/mpm_default.h
+ * @brief os2 MPM defaults
+ *
+ * @addtogroup APACHE_MPM_OS2
+ * @{
+ */
+
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* Number of servers processes to spawn off by default
+ */
+#ifndef DEFAULT_START_DAEMON
+#define DEFAULT_START_DAEMON 2
+#endif
+
+/* Maximum number of *free* server threads --- more than this, and
+ * they will die off.
+ */
+
+#ifndef DEFAULT_MAX_SPARE_THREAD
+#define DEFAULT_MAX_SPARE_THREAD 10
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+
+#ifndef DEFAULT_MIN_SPARE_THREAD
+#define DEFAULT_MIN_SPARE_THREAD 5
+#endif
+
+/* Where the main/parent process's pid is logged */
+#ifndef DEFAULT_PIDLOG
+#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+#endif
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If <= 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD
+#define DEFAULT_MAX_REQUESTS_PER_CHILD 10000
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/mpmt_os2/mpmt_os2.c b/server/mpm/mpmt_os2/mpmt_os2.c
new file mode 100644
index 00000000..8fb10ce9
--- /dev/null
+++ b/server/mpm/mpmt_os2/mpmt_os2.c
@@ -0,0 +1,577 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Multi-process, multi-threaded MPM for OS/2
+ *
+ * Server consists of
+ * - a main, parent process
+ * - a small, static number of child processes
+ *
+ * The parent process's job is to manage the child processes. This involves
+ * spawning children as required to ensure there are always ap_daemons_to_start
+ * processes accepting connections.
+ *
+ * Each child process consists of a a pool of worker threads and a
+ * main thread that accepts connections & passes them to the workers via
+ * a work queue. The worker thread pool is dynamic, managed by a maintanence
+ * thread so that the number of idle threads is kept between
+ * min_spare_threads & max_spare_threads.
+ *
+ */
+
+/*
+ Todo list
+ - Enforce MaxClients somehow
+*/
+#define CORE_PRIVATE
+#define INCL_NOPMAPI
+#define INCL_DOS
+#define INCL_DOSERRORS
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "mpm_default.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "mpm.h"
+#include "ap_mpm.h"
+#include "ap_listen.h"
+#include "apr_portable.h"
+#include "mpm_common.h"
+#include "apr_strings.h"
+#include <os2.h>
+#include <process.h>
+
+/* We don't need many processes,
+ * they're only for redundancy in the event of a crash
+ */
+#define HARD_SERVER_LIMIT 10
+
+/* Limit on the total number of threads per process
+ */
+#ifndef HARD_THREAD_LIMIT
+#define HARD_THREAD_LIMIT 256
+#endif
+
+server_rec *ap_server_conf;
+static apr_pool_t *pconf = NULL; /* Pool for config stuff */
+static const char *ap_pid_fname=NULL;
+
+/* Config globals */
+static int one_process = 0;
+static int ap_daemons_to_start = 0;
+static int ap_thread_limit = 0;
+static int ap_max_requests_per_child = 0;
+int ap_min_spare_threads = 0;
+int ap_max_spare_threads = 0;
+
+/* Keep track of a few interesting statistics */
+int ap_max_daemons_limit = -1;
+
+/* volatile just in case */
+static int volatile shutdown_pending;
+static int volatile restart_pending;
+static int volatile is_graceful = 0;
+ap_generation_t volatile ap_my_generation=0; /* Used by the scoreboard */
+static int is_parent_process=TRUE;
+HMTX ap_mpm_accept_mutex = 0;
+
+/* An array of these is stored in a shared memory area for passing
+ * sockets from the parent to child processes
+ */
+typedef struct {
+ struct sockaddr_in name;
+ apr_os_sock_t listen_fd;
+} listen_socket_t;
+
+typedef struct {
+ HMTX accept_mutex;
+ listen_socket_t listeners[1];
+} parent_info_t;
+
+static char master_main();
+static void spawn_child(int slot);
+void ap_mpm_child_main(apr_pool_t *pconf);
+static void set_signals();
+
+
+int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s )
+{
+ char *listener_shm_name;
+ parent_info_t *parent_info;
+ ULONG rc;
+ pconf = _pconf;
+ ap_server_conf = s;
+ restart_pending = 0;
+
+ DosSetMaxFH(ap_thread_limit * 2);
+ listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getppid());
+ rc = DosGetNamedSharedMem((PPVOID)&parent_info, listener_shm_name, PAG_READ);
+ is_parent_process = rc != 0;
+ ap_scoreboard_fname = apr_psprintf(pconf, "/sharemem/httpd/scoreboard.%d", is_parent_process ? getpid() : getppid());
+
+ if (rc == 0) {
+ /* Child process */
+ ap_listen_rec *lr;
+ int num_listeners = 0;
+
+ ap_mpm_accept_mutex = parent_info->accept_mutex;
+
+ /* Set up a default listener if necessary */
+ if (ap_listeners == NULL) {
+ ap_listen_rec *lr = apr_pcalloc(s->process->pool, sizeof(ap_listen_rec));
+ ap_listeners = lr;
+ apr_sockaddr_info_get(&lr->bind_addr, "0.0.0.0", APR_UNSPEC,
+ DEFAULT_HTTP_PORT, 0, s->process->pool);
+ apr_socket_create(&lr->sd, lr->bind_addr->family,
+ SOCK_STREAM, 0, s->process->pool);
+ }
+
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ apr_sockaddr_t *sa;
+ apr_os_sock_put(&lr->sd, &parent_info->listeners[num_listeners].listen_fd, pconf);
+ apr_socket_addr_get(&sa, APR_LOCAL, lr->sd);
+ num_listeners++;
+ }
+
+ DosFreeMem(parent_info);
+
+ /* Do the work */
+ ap_mpm_child_main(pconf);
+
+ /* Outta here */
+ return 1;
+ }
+ else {
+ /* Parent process */
+ char restart;
+ is_parent_process = TRUE;
+
+ if (ap_setup_listeners(ap_server_conf) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
+ "no listening sockets available, shutting down");
+ return 1;
+ }
+
+ ap_log_pid(pconf, ap_pid_fname);
+
+ restart = master_main();
+ ++ap_my_generation;
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+
+ if (!restart) {
+ const char *pidfile = ap_server_root_relative(pconf, ap_pid_fname);
+
+ if (pidfile != NULL && remove(pidfile) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS,
+ ap_server_conf, "removed PID file %s (pid=%d)",
+ pidfile, getpid());
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught SIGTERM, shutting down");
+ return 1;
+ }
+ } /* Parent process */
+
+ return 0; /* Restart */
+}
+
+
+
+/* Main processing of the parent process
+ * returns TRUE if restarting
+ */
+static char master_main()
+{
+ server_rec *s = ap_server_conf;
+ ap_listen_rec *lr;
+ parent_info_t *parent_info;
+ char *listener_shm_name;
+ int listener_num, num_listeners, slot;
+ ULONG rc;
+
+ printf("%s \n", ap_get_server_version());
+ set_signals();
+
+ if (ap_setup_listeners(ap_server_conf) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
+ "no listening sockets available, shutting down");
+ return FALSE;
+ }
+
+ /* Allocate a shared memory block for the array of listeners */
+ for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
+ num_listeners++;
+ }
+
+ listener_shm_name = apr_psprintf(pconf, "/sharemem/httpd/parent_info.%d", getpid());
+ rc = DosAllocSharedMem((PPVOID)&parent_info, listener_shm_name,
+ sizeof(parent_info_t) + num_listeners * sizeof(listen_socket_t),
+ PAG_READ|PAG_WRITE|PAG_COMMIT);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
+ "failure allocating shared memory, shutting down");
+ return FALSE;
+ }
+
+ /* Store the listener sockets in the shared memory area for our children to see */
+ for (listener_num = 0, lr = ap_listeners; lr; lr = lr->next, listener_num++) {
+ apr_os_sock_get(&parent_info->listeners[listener_num].listen_fd, lr->sd);
+ }
+
+ /* Create mutex to prevent multiple child processes from detecting
+ * a connection with apr_poll()
+ */
+
+ rc = DosCreateMutexSem(NULL, &ap_mpm_accept_mutex, DC_SEM_SHARED, FALSE);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s,
+ "failure creating accept mutex, shutting down");
+ return FALSE;
+ }
+
+ parent_info->accept_mutex = ap_mpm_accept_mutex;
+
+ /* Allocate shared memory for scoreboard */
+ if (ap_scoreboard_image == NULL) {
+ void *sb_mem;
+ rc = DosAllocSharedMem(&sb_mem, ap_scoreboard_fname,
+ ap_calc_scoreboard_size(),
+ PAG_COMMIT|PAG_READ|PAG_WRITE);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "unable to allocate shared memory for scoreboard , exiting");
+ return FALSE;
+ }
+
+ ap_init_scoreboard(sb_mem);
+ }
+
+ ap_scoreboard_image->global->restart_time = apr_time_now();
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+#ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "AcceptMutex: %s (default: %s)",
+ apr_proc_mutex_name(accept_mutex),
+ apr_proc_mutex_defname());
+#endif
+ if (one_process) {
+ ap_scoreboard_image->parent[0].pid = getpid();
+ ap_mpm_child_main(pconf);
+ return FALSE;
+ }
+
+ while (!restart_pending && !shutdown_pending) {
+ RESULTCODES proc_rc;
+ PID child_pid;
+ int active_children = 0;
+
+ /* Count number of active children */
+ for (slot=0; slot < HARD_SERVER_LIMIT; slot++) {
+ active_children += ap_scoreboard_image->parent[slot].pid != 0 &&
+ !ap_scoreboard_image->parent[slot].quiescing;
+ }
+
+ /* Spawn children if needed */
+ for (slot=0; slot < HARD_SERVER_LIMIT && active_children < ap_daemons_to_start; slot++) {
+ if (ap_scoreboard_image->parent[slot].pid == 0) {
+ spawn_child(slot);
+ active_children++;
+ }
+ }
+
+ rc = DosWaitChild(DCWA_PROCESSTREE, DCWW_NOWAIT, &proc_rc, &child_pid, 0);
+
+ if (rc == 0) {
+ /* A child has terminated, remove its scoreboard entry & terminate if necessary */
+ for (slot=0; ap_scoreboard_image->parent[slot].pid != child_pid && slot < HARD_SERVER_LIMIT; slot++);
+
+ if (slot < HARD_SERVER_LIMIT) {
+ ap_scoreboard_image->parent[slot].pid = 0;
+ ap_scoreboard_image->parent[slot].quiescing = 0;
+
+ if (proc_rc.codeTerminate == TC_EXIT) {
+ /* Child terminated normally, check its exit code and
+ * terminate server if child indicates a fatal error
+ */
+ if (proc_rc.codeResult == APEXIT_CHILDFATAL)
+ break;
+ }
+ }
+ } else if (rc == ERROR_CHILD_NOT_COMPLETE) {
+ /* No child exited, lets sleep for a while.... */
+ apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
+ }
+ }
+
+ /* Signal children to shut down, either gracefully or immediately */
+ for (slot=0; slot<HARD_SERVER_LIMIT; slot++) {
+ kill(ap_scoreboard_image->parent[slot].pid, is_graceful ? SIGHUP : SIGTERM);
+ }
+
+ DosFreeMem(parent_info);
+ return restart_pending;
+}
+
+
+
+static void spawn_child(int slot)
+{
+ PPIB ppib;
+ PTIB ptib;
+ char fail_module[100];
+ char progname[CCHMAXPATH];
+ RESULTCODES proc_rc;
+ ULONG rc;
+
+ ap_scoreboard_image->parent[slot].generation = ap_my_generation;
+ DosGetInfoBlocks(&ptib, &ppib);
+ DosQueryModuleName(ppib->pib_hmte, sizeof(progname), progname);
+ rc = DosExecPgm(fail_module, sizeof(fail_module), EXEC_ASYNCRESULT,
+ ppib->pib_pchcmd, NULL, &proc_rc, progname);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "error spawning child, slot %d", slot);
+ }
+
+ if (ap_max_daemons_limit < slot) {
+ ap_max_daemons_limit = slot;
+ }
+
+ ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate;
+}
+
+
+
+/* Signal handling routines */
+
+static void sig_term(int sig)
+{
+ shutdown_pending = 1;
+ signal(SIGTERM, SIG_DFL);
+}
+
+
+
+static void sig_restart(int sig)
+{
+ if (sig == SIGUSR1) {
+ is_graceful = 1;
+ }
+
+ restart_pending = 1;
+}
+
+
+
+static void set_signals()
+{
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sig_term;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
+
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)");
+
+ sa.sa_handler = sig_restart;
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
+ if (sigaction(SIGUSR1, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGUSR1)");
+}
+
+
+
+/* Enquiry functions used get MPM status info */
+
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch (query_code) {
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = ap_max_daemons_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_DYNAMIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_NOT_SUPPORTED;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = HARD_SERVER_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = HARD_THREAD_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_child;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+
+
+int ap_graceful_stop_signalled(void)
+{
+ return is_graceful;
+}
+
+
+
+/* Configuration handling stuff */
+
+static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ one_process = ap_exists_config_define("ONE_PROCESS") ||
+ ap_exists_config_define("DEBUG");
+ is_graceful = 0;
+ ap_listen_pre_config();
+ ap_daemons_to_start = DEFAULT_START_DAEMON;
+ ap_thread_limit = HARD_THREAD_LIMIT;
+ ap_pid_fname = DEFAULT_PIDLOG;
+ ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
+ ap_extended_status = 0;
+ ap_min_spare_threads = DEFAULT_MIN_SPARE_THREAD;
+ ap_max_spare_threads = DEFAULT_MAX_SPARE_THREAD;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+
+ return OK;
+}
+
+
+
+static void mpmt_os2_hooks(apr_pool_t *p)
+{
+ ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+
+
+static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_to_start = atoi(arg);
+ return NULL;
+}
+
+
+
+static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_min_spare_threads = atoi(arg);
+
+ if (ap_min_spare_threads <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: detected MinSpareThreads set to non-positive.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Resetting to 1 to avoid almost certain Apache failure.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Please read the documentation.");
+ ap_min_spare_threads = 1;
+ }
+
+ return NULL;
+}
+
+
+
+static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_max_spare_threads = atoi(arg);
+ return NULL;
+}
+
+
+
+static const char *ignore_cmd(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ return NULL;
+}
+
+
+
+static const command_rec mpmt_os2_cmds[] = {
+LISTEN_COMMANDS,
+AP_INIT_TAKE1( "StartServers", set_daemons_to_start, NULL, RSRC_CONF,
+ "Number of child processes launched at server startup" ),
+AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
+ "Minimum number of idle children, to handle request spikes"),
+AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
+ "Maximum number of idle children"),
+AP_INIT_TAKE1("User", ignore_cmd, NULL, RSRC_CONF,
+ "Not applicable on this platform"),
+AP_INIT_TAKE1("Group", ignore_cmd, NULL, RSRC_CONF,
+ "Not applicable on this platform"),
+AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \
+ "Not applicable on this platform"),
+{ NULL }
+};
+
+module AP_MODULE_DECLARE_DATA mpm_mpmt_os2_module = {
+ MPM20_MODULE_STUFF,
+ NULL, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ mpmt_os2_cmds, /* command apr_table_t */
+ mpmt_os2_hooks, /* register_hooks */
+};
diff --git a/server/mpm/mpmt_os2/mpmt_os2_child.c b/server/mpm/mpmt_os2/mpmt_os2_child.c
new file mode 100644
index 00000000..bc313315
--- /dev/null
+++ b/server/mpm/mpmt_os2/mpmt_os2_child.c
@@ -0,0 +1,480 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CORE_PRIVATE
+#define INCL_NOPMAPI
+#define INCL_DOS
+#define INCL_DOSERRORS
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "mpm_default.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "mpm.h"
+#include "ap_mpm.h"
+#include "ap_listen.h"
+#include "apr_portable.h"
+#include "apr_poll.h"
+#include "mpm_common.h"
+#include "apr_strings.h"
+#include <os2.h>
+#include <process.h>
+
+/* XXXXXX move these to header file private to this MPM */
+
+/* We don't need many processes,
+ * they're only for redundancy in the event of a crash
+ */
+#define HARD_SERVER_LIMIT 10
+
+/* Limit on the total number of threads per process
+ */
+#ifndef HARD_THREAD_LIMIT
+#define HARD_THREAD_LIMIT 256
+#endif
+
+#define ID_FROM_CHILD_THREAD(c, t) ((c * HARD_THREAD_LIMIT) + t)
+
+typedef struct {
+ apr_pool_t *pconn;
+ apr_socket_t *conn_sd;
+} worker_args_t;
+
+#define WORKTYPE_CONN 0
+#define WORKTYPE_EXIT 1
+
+static apr_pool_t *pchild = NULL;
+static int child_slot;
+static int shutdown_pending = 0;
+extern int ap_my_generation;
+static int volatile is_graceful = 1;
+HEV shutdown_event; /* signaled when this child is shutting down */
+
+/* grab some MPM globals */
+extern int ap_min_spare_threads;
+extern int ap_max_spare_threads;
+extern HMTX ap_mpm_accept_mutex;
+
+static void worker_main(void *vpArg);
+static void clean_child_exit(int code);
+static void set_signals();
+static void server_maintenance(void *vpArg);
+
+
+static void clean_child_exit(int code)
+{
+ if (pchild) {
+ apr_pool_destroy(pchild);
+ }
+
+ exit(code);
+}
+
+
+
+void ap_mpm_child_main(apr_pool_t *pconf)
+{
+ ap_listen_rec *lr = NULL;
+ int requests_this_child = 0;
+ int rv = 0;
+ unsigned long ulTimes;
+ int my_pid = getpid();
+ ULONG rc, c;
+ HQUEUE workq;
+ apr_pollset_t *pollset;
+ int num_listeners;
+ TID server_maint_tid;
+ void *sb_mem;
+
+ /* Stop Ctrl-C/Ctrl-Break signals going to child processes */
+ DosSetSignalExceptionFocus(0, &ulTimes);
+ set_signals();
+
+ /* Create pool for child */
+ apr_pool_create(&pchild, pconf);
+
+ ap_run_child_init(pchild, ap_server_conf);
+
+ /* Create an event semaphore used to trigger other threads to shutdown */
+ rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "unable to create shutdown semaphore, exiting");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ /* Gain access to the scoreboard. */
+ rc = DosGetNamedSharedMem(&sb_mem, ap_scoreboard_fname,
+ PAG_READ|PAG_WRITE);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "scoreboard not readable in child, exiting");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ap_calc_scoreboard_size();
+ ap_init_scoreboard(sb_mem);
+
+ /* Gain access to the accpet mutex */
+ rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex);
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "accept mutex couldn't be accessed in child, exiting");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ /* Find our pid in the scoreboard so we know what slot our parent allocated us */
+ for (child_slot = 0; ap_scoreboard_image->parent[child_slot].pid != my_pid && child_slot < HARD_SERVER_LIMIT; child_slot++);
+
+ if (child_slot == HARD_SERVER_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
+ "child pid not found in scoreboard, exiting");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ap_my_generation = ap_scoreboard_image->parent[child_slot].generation;
+ memset(ap_scoreboard_image->servers[child_slot], 0, sizeof(worker_score) * HARD_THREAD_LIMIT);
+
+ /* Set up an OS/2 queue for passing connections & termination requests
+ * to worker threads
+ */
+ rc = DosCreateQueue(&workq, QUE_FIFO, apr_psprintf(pchild, "/queues/httpd/work.%d", my_pid));
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "unable to create work queue, exiting");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ /* Create initial pool of worker threads */
+ for (c = 0; c < ap_min_spare_threads; c++) {
+// ap_scoreboard_image->servers[child_slot][c].tid = _beginthread(worker_main, NULL, 128*1024, (void *)c);
+ }
+
+ /* Start maintenance thread */
+ server_maint_tid = _beginthread(server_maintenance, NULL, 32768, NULL);
+
+ /* Set up poll */
+ for (num_listeners = 0, lr = ap_listeners; lr; lr = lr->next) {
+ num_listeners++;
+ }
+
+ apr_pollset_create(&pollset, num_listeners, pchild, 0);
+
+ for (lr = ap_listeners; lr != NULL; lr = lr->next) {
+ apr_pollfd_t pfd = { 0 };
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = lr->sd;
+ pfd.reqevents = APR_POLLIN;
+ pfd.client_data = lr;
+ apr_pollset_add(pollset, &pfd);
+ }
+
+ /* Main connection accept loop */
+ do {
+ apr_pool_t *pconn;
+ worker_args_t *worker_args;
+ int last_poll_idx = 0;
+
+ apr_pool_create(&pconn, pchild);
+ worker_args = apr_palloc(pconn, sizeof(worker_args_t));
+ worker_args->pconn = pconn;
+
+ if (num_listeners == 1) {
+ rv = apr_socket_accept(&worker_args->conn_sd, ap_listeners->sd, pconn);
+ } else {
+ const apr_pollfd_t *poll_results;
+ apr_int32_t num_poll_results;
+
+ rc = DosRequestMutexSem(ap_mpm_accept_mutex, SEM_INDEFINITE_WAIT);
+
+ if (shutdown_pending) {
+ DosReleaseMutexSem(ap_mpm_accept_mutex);
+ break;
+ }
+
+ rv = APR_FROM_OS_ERROR(rc);
+
+ if (rv == APR_SUCCESS) {
+ rv = apr_pollset_poll(pollset, -1, &num_poll_results, &poll_results);
+ DosReleaseMutexSem(ap_mpm_accept_mutex);
+ }
+
+ if (rv == APR_SUCCESS) {
+ if (last_poll_idx >= num_listeners) {
+ last_poll_idx = 0;
+ }
+
+ lr = poll_results[last_poll_idx++].client_data;
+ rv = apr_socket_accept(&worker_args->conn_sd, lr->sd, pconn);
+ last_poll_idx++;
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ if (!APR_STATUS_IS_EINTR(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,
+ "apr_socket_accept");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+ } else {
+ DosWriteQueue(workq, WORKTYPE_CONN, sizeof(worker_args_t), worker_args, 0);
+ requests_this_child++;
+ }
+
+ if (ap_max_requests_per_child != 0 && requests_this_child >= ap_max_requests_per_child)
+ break;
+ } while (!shutdown_pending && ap_my_generation == ap_scoreboard_image->global->running_generation);
+
+ ap_scoreboard_image->parent[child_slot].quiescing = 1;
+ DosPostEventSem(shutdown_event);
+ DosWaitThread(&server_maint_tid, DCWW_WAIT);
+
+ if (is_graceful) {
+ char someleft;
+
+ /* tell our worker threads to exit */
+ for (c=0; c<HARD_THREAD_LIMIT; c++) {
+ if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
+ DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0);
+ }
+ }
+
+ do {
+ someleft = 0;
+
+ for (c=0; c<HARD_THREAD_LIMIT; c++) {
+ if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
+ someleft = 1;
+ DosSleep(1000);
+ break;
+ }
+ }
+ } while (someleft);
+ } else {
+ DosPurgeQueue(workq);
+
+ for (c=0; c<HARD_THREAD_LIMIT; c++) {
+ if (ap_scoreboard_image->servers[child_slot][c].status != SERVER_DEAD) {
+ DosKillThread(ap_scoreboard_image->servers[child_slot][c].tid);
+ }
+ }
+ }
+
+ apr_pool_destroy(pchild);
+}
+
+
+
+void add_worker()
+{
+ int thread_slot;
+
+ /* Find a free thread slot */
+ for (thread_slot=0; thread_slot < HARD_THREAD_LIMIT; thread_slot++) {
+ if (ap_scoreboard_image->servers[child_slot][thread_slot].status == SERVER_DEAD) {
+ ap_scoreboard_image->servers[child_slot][thread_slot].status = SERVER_STARTING;
+ ap_scoreboard_image->servers[child_slot][thread_slot].tid =
+ _beginthread(worker_main, NULL, 128*1024, (void *)thread_slot);
+ break;
+ }
+ }
+}
+
+
+
+ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec,
+ EXCEPTIONREGISTRATIONRECORD *pRegRec,
+ CONTEXTRECORD *pContext,
+ PVOID p)
+{
+ int c;
+
+ if (pReportRec->fHandlerFlags & EH_NESTED_CALL) {
+ return XCPT_CONTINUE_SEARCH;
+ }
+
+ if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION ||
+ pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
+ "caught exception in worker thread, initiating child shutdown pid=%d", getpid());
+ for (c=0; c<HARD_THREAD_LIMIT; c++) {
+ if (ap_scoreboard_image->servers[child_slot][c].tid == _gettid()) {
+ ap_scoreboard_image->servers[child_slot][c].status = SERVER_DEAD;
+ break;
+ }
+ }
+
+ /* Shut down process ASAP, it could be quite unhealthy & leaking resources */
+ shutdown_pending = 1;
+ ap_scoreboard_image->parent[child_slot].quiescing = 1;
+ kill(getpid(), SIGHUP);
+ DosUnwindException(UNWIND_ALL, 0, 0);
+ }
+
+ return XCPT_CONTINUE_SEARCH;
+}
+
+
+
+static void worker_main(void *vpArg)
+{
+ long conn_id;
+ conn_rec *current_conn;
+ apr_pool_t *pconn;
+ apr_allocator_t *allocator;
+ apr_bucket_alloc_t *bucket_alloc;
+ worker_args_t *worker_args;
+ HQUEUE workq;
+ PID owner;
+ int rc;
+ REQUESTDATA rd;
+ ULONG len;
+ BYTE priority;
+ int thread_slot = (int)vpArg;
+ EXCEPTIONREGISTRATIONRECORD reg_rec = { NULL, thread_exception_handler };
+ ap_sb_handle_t *sbh;
+
+ /* Trap exceptions in this thread so we don't take down the whole process */
+ DosSetExceptionHandler( &reg_rec );
+
+ rc = DosOpenQueue(&owner, &workq,
+ apr_psprintf(pchild, "/queues/httpd/work.%d", getpid()));
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "unable to open work queue, exiting");
+ ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0;
+ }
+
+ conn_id = ID_FROM_CHILD_THREAD(child_slot, thread_slot);
+ ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_READY,
+ NULL);
+
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ bucket_alloc = apr_bucket_alloc_create_ex(allocator);
+
+ while (rc = DosReadQueue(workq, &rd, &len, (PPVOID)&worker_args, 0, DCWW_WAIT, &priority, NULLHANDLE),
+ rc == 0 && rd.ulData != WORKTYPE_EXIT) {
+ pconn = worker_args->pconn;
+ ap_create_sb_handle(&sbh, pconn, child_slot, thread_slot);
+ current_conn = ap_run_create_connection(pconn, ap_server_conf,
+ worker_args->conn_sd, conn_id,
+ sbh, bucket_alloc);
+
+ if (current_conn) {
+ ap_process_connection(current_conn, worker_args->conn_sd);
+ ap_lingering_close(current_conn);
+ }
+
+ apr_pool_destroy(pconn);
+ ap_update_child_status_from_indexes(child_slot, thread_slot,
+ SERVER_READY, NULL);
+ }
+
+ ap_update_child_status_from_indexes(child_slot, thread_slot, SERVER_DEAD,
+ NULL);
+
+ apr_bucket_alloc_destroy(bucket_alloc);
+ apr_allocator_destroy(allocator);
+}
+
+
+
+static void server_maintenance(void *vpArg)
+{
+ int num_idle, num_needed;
+ ULONG num_pending = 0;
+ int threadnum;
+ HQUEUE workq;
+ ULONG rc;
+ PID owner;
+
+ rc = DosOpenQueue(&owner, &workq,
+ apr_psprintf(pchild, "/queues/httpd/work.%d", getpid()));
+
+ if (rc) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf,
+ "unable to open work queue in maintenance thread");
+ return;
+ }
+
+ do {
+ for (num_idle=0, threadnum=0; threadnum < HARD_THREAD_LIMIT; threadnum++) {
+ num_idle += ap_scoreboard_image->servers[child_slot][threadnum].status == SERVER_READY;
+ }
+
+ DosQueryQueue(workq, &num_pending);
+ num_needed = ap_min_spare_threads - num_idle + num_pending;
+
+ if (num_needed > 0) {
+ for (threadnum=0; threadnum < num_needed; threadnum++) {
+ add_worker();
+ }
+ }
+
+ if (num_idle - num_pending > ap_max_spare_threads) {
+ DosWriteQueue(workq, WORKTYPE_EXIT, 0, NULL, 0);
+ }
+ } while (DosWaitEventSem(shutdown_event, 500) == ERROR_TIMEOUT);
+}
+
+
+
+/* Signal handling routines */
+
+static void sig_term(int sig)
+{
+ shutdown_pending = 1;
+ is_graceful = 0;
+ signal(SIGTERM, SIG_DFL);
+}
+
+
+
+static void sig_hup(int sig)
+{
+ shutdown_pending = 1;
+ is_graceful = 1;
+}
+
+
+
+static void set_signals()
+{
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sig_term;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
+
+ sa.sa_handler = sig_hup;
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
+}
diff --git a/server/mpm/netware/mpm.h b/server/mpm/netware/mpm.h
new file mode 100644
index 00000000..106d62a5
--- /dev/null
+++ b/server/mpm/netware/mpm.h
@@ -0,0 +1,58 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file netware/mpm.h
+ * @brief Netware MPM
+ *
+ * @defgroup APACHE_MPM_NETWARE Netware MPM
+ * @ingroup APACHE_OS_NETWARE APACHE_MPM
+ * @{
+ */
+
+#include "scoreboard.h"
+
+#ifndef APACHE_MPM_THREADED_H
+#define APACHE_MPM_THREADED_H
+
+#define THREADED_MPM
+
+#define MPM_NAME "NetWare_Threaded"
+
+/*#define AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+ #define AP_MPM_WANT_WAIT_OR_TIMEOUT
+ #define AP_MPM_WANT_PROCESS_CHILD_STATUS
+ #define AP_MPM_WANT_SET_PIDFILE
+ #define AP_MPM_WANT_SET_SCOREBOARD
+ #define AP_MPM_WANT_SET_LOCKFILE
+*/
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+#define AP_MPM_WANT_SET_STACKSIZE
+#define AP_MPM_DISABLE_NAGLE_ACCEPTED_SOCK
+/*#define AP_MPM_WANT_SET_COREDUMPDIR
+ #define AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+*/
+
+#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid)
+#define MPM_NOTE_CHILD_KILLED(i) (MPM_CHILD_PID(i) = 0)
+
+extern int ap_threads_per_child;
+extern int ap_max_workers_limit;
+extern server_rec *ap_server_conf;
+
+#endif /* APACHE_MPM_THREADED_H */
+/** @} */
diff --git a/server/mpm/netware/mpm_default.h b/server/mpm/netware/mpm_default.h
new file mode 100644
index 00000000..96c5aa09
--- /dev/null
+++ b/server/mpm/netware/mpm_default.h
@@ -0,0 +1,122 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file netware/mpm_default.h
+ * @brief Defaults for Netware MPM
+ *
+ * @addtogroup APACHE_MPM_NETWARE
+ * @{
+ */
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* Number of servers to spawn off by default --- also, if fewer than
+ * this free when the caretaker checks, it will spawn more.
+ */
+#ifndef DEFAULT_START_DAEMON
+#define DEFAULT_START_DAEMON 1
+#endif
+
+/* Maximum number of *free* server processes --- more than this, and
+ * they will die off.
+ */
+
+#ifndef DEFAULT_MAX_FREE_DAEMON
+#define DEFAULT_MAX_FREE_DAEMON 1
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+
+#ifndef DEFAULT_MIN_FREE_DAEMON
+#define DEFAULT_MIN_FREE_DAEMON 1
+#endif
+
+/* Limit on the threads per process. Clients will be locked out if more than
+ * this * HARD_SERVER_LIMIT are needed.
+ *
+ * We keep this for one reason it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef HARD_THREAD_LIMIT
+#define HARD_THREAD_LIMIT 2048
+#endif
+
+#ifndef DEFAULT_THREADS_PER_CHILD
+#define DEFAULT_THREADS_PER_CHILD 50
+#endif
+
+/* Number of threads to spawn off by default --- also, if fewer than
+ * this free when the caretaker checks, it will spawn more.
+ */
+#ifndef DEFAULT_START_THREADS
+#define DEFAULT_START_THREADS DEFAULT_THREADS_PER_CHILD
+#endif
+
+/* Maximum number of *free* threads --- more than this, and
+ * they will die off.
+ */
+
+#ifndef DEFAULT_MAX_FREE_THREADS
+#define DEFAULT_MAX_FREE_THREADS 100
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+
+#ifndef DEFAULT_MIN_FREE_THREADS
+#define DEFAULT_MIN_FREE_THREADS 10
+#endif
+
+/* Check for definition of DEFAULT_REL_RUNTIMEDIR */
+#ifndef DEFAULT_REL_RUNTIMEDIR
+#define DEFAULT_REL_RUNTIMEDIR "logs"
+#endif
+
+/* File used for accept locking, when we use a file */
+/*#ifndef DEFAULT_LOCKFILE
+ #define DEFAULT_LOCKFILE DEFAULT_REL_RUNTIMEDIR "/accept.lock"
+ #endif
+*/
+
+/* Where the main/parent process's pid is logged */
+/*#ifndef DEFAULT_PIDLOG
+ #define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+ #endif
+*/
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If <= 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD
+#define DEFAULT_MAX_REQUESTS_PER_CHILD 0
+#endif
+
+/* Default stack size allocated for each worker thread.
+ */
+#ifndef DEFAULT_THREAD_STACKSIZE
+#define DEFAULT_THREAD_STACKSIZE 65536
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/netware/mpm_netware.c b/server/mpm/netware/mpm_netware.c
new file mode 100644
index 00000000..dd4cb359
--- /dev/null
+++ b/server/mpm/netware/mpm_netware.c
@@ -0,0 +1,1291 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * httpd.c: simple http daemon for answering WWW file requests
+ *
+ *
+ * 03-21-93 Rob McCool wrote original code (up to NCSA HTTPd 1.3)
+ *
+ * 03-06-95 blong
+ * changed server number for child-alone processes to 0 and changed name
+ * of processes
+ *
+ * 03-10-95 blong
+ * Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu)
+ * including set group before fork, and call gettime before to fork
+ * to set up libraries.
+ *
+ * 04-14-95 rst / rh
+ * Brandon's code snarfed from NCSA 1.4, but tinkered to work with the
+ * Apache server, and also to have child processes do accept() directly.
+ *
+ * April-July '95 rst
+ * Extensive rework for Apache.
+ */
+
+#include "apr.h"
+#include "apr_portable.h"
+#include "apr_strings.h"
+#include "apr_thread_proc.h"
+#include "apr_signal.h"
+#include "apr_tables.h"
+#include "apr_getopt.h"
+#include "apr_thread_mutex.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifndef USE_WINSOCK
+#include <sys/select.h>
+#endif
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "mpm_default.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "mpm_common.h"
+#include "ap_listen.h"
+#include "ap_mmn.h"
+
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+
+#include <signal.h>
+
+#include <netware.h>
+#include <nks/netware.h>
+#include <library.h>
+#include <screen.h>
+
+/* Limit on the total --- clients will be locked out if more servers than
+ * this are needed. It is intended solely to keep the server from crashing
+ * when things get out of hand.
+ *
+ * We keep a hard maximum number of servers, for two reasons --- first off,
+ * in case something goes seriously wrong, we want to stop the fork bomb
+ * short of actually crashing the machine we're running on by filling some
+ * kernel table. Secondly, it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef HARD_SERVER_LIMIT
+#define HARD_SERVER_LIMIT 1
+#endif
+
+#define WORKER_DEAD SERVER_DEAD
+#define WORKER_STARTING SERVER_STARTING
+#define WORKER_READY SERVER_READY
+#define WORKER_IDLE_KILL SERVER_IDLE_KILL
+
+/* config globals */
+
+int ap_threads_per_child=0; /* Worker threads per child */
+static int ap_threads_to_start=0;
+static int ap_threads_min_free=0;
+static int ap_threads_max_free=0;
+static int ap_threads_limit=0;
+static int mpm_state = AP_MPMQ_STARTING;
+
+/*
+ * The max child slot ever assigned, preserved across restarts. Necessary
+ * to deal with MaxClients changes across SIGWINCH restarts. We use this
+ * value to optimize routines that have to scan the entire scoreboard.
+ */
+int ap_max_workers_limit = -1;
+server_rec *ap_server_conf;
+
+/* *Non*-shared http_main globals... */
+
+int hold_screen_on_exit = 0; /* Indicates whether the screen should be held open */
+
+static fd_set listenfds;
+static int listenmaxfd;
+
+static apr_pool_t *pconf; /* Pool for config stuff */
+static apr_pool_t *pmain; /* Pool for httpd child stuff */
+
+static pid_t ap_my_pid; /* it seems silly to call getpid all the time */
+static char *ap_my_addrspace = NULL;
+
+static int die_now = 0;
+
+/* Keep track of the number of worker threads currently active */
+static unsigned long worker_thread_count;
+static int request_count;
+
+/* Structure used to register/deregister a console handler with the OS */
+static int InstallConsoleHandler(void);
+static void RemoveConsoleHandler(void);
+static int CommandLineInterpreter(scr_t screenID, const char *commandLine);
+static CommandParser_t ConsoleHandler = {0, NULL, 0};
+#define HANDLEDCOMMAND 0
+#define NOTMYCOMMAND 1
+
+static int show_settings = 0;
+
+//#define DBINFO_ON
+//#define DBPRINT_ON
+#ifdef DBPRINT_ON
+#define DBPRINT0(s) printf(s)
+#define DBPRINT1(s,v1) printf(s,v1)
+#define DBPRINT2(s,v1,v2) printf(s,v1,v2)
+#else
+#define DBPRINT0(s)
+#define DBPRINT1(s,v1)
+#define DBPRINT2(s,v1,v2)
+#endif
+
+/* volatile just in case */
+static int volatile shutdown_pending;
+static int volatile restart_pending;
+static int volatile is_graceful;
+static int volatile wait_to_finish=1;
+ap_generation_t volatile ap_my_generation=0;
+
+/* a clean exit from a child with proper cleanup */
+static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans,
+ apr_bucket_alloc_t *bucket_alloc) __attribute__ ((noreturn));
+static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans,
+ apr_bucket_alloc_t *bucket_alloc)
+{
+ apr_bucket_alloc_destroy(bucket_alloc);
+ if (!shutdown_pending) {
+ apr_pool_destroy(ptrans);
+ }
+
+ atomic_dec (&worker_thread_count);
+ if (worker_num >=0)
+ ap_update_child_status_from_indexes(0, worker_num, WORKER_DEAD,
+ (request_rec *) NULL);
+ NXThreadExit((void*)&code);
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch(query_code){
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = 1;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_DYNAMIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_NOT_SUPPORTED;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = HARD_SERVER_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = HARD_THREAD_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_THREADS:
+ *result = ap_threads_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_THREADS:
+ *result = ap_threads_min_free;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_THREADS:
+ *result = ap_threads_max_free;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_DAEMONS:
+ *result = 1;
+ return APR_SUCCESS;
+ case AP_MPMQ_MPM_STATE:
+ *result = mpm_state;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+
+/*****************************************************************
+ * Connection structures and accounting...
+ */
+
+static void mpm_term(void)
+{
+ RemoveConsoleHandler();
+ wait_to_finish = 0;
+ NXThreadYield();
+}
+
+static void sig_term(int sig)
+{
+ if (shutdown_pending == 1) {
+ /* Um, is this _probably_ not an error, if the user has
+ * tried to do a shutdown twice quickly, so we won't
+ * worry about reporting it.
+ */
+ return;
+ }
+ shutdown_pending = 1;
+
+ DBPRINT0 ("waiting for threads\n");
+ while (wait_to_finish) {
+ apr_thread_yield();
+ }
+ DBPRINT0 ("goodbye\n");
+}
+
+/* restart() is the signal handler for SIGHUP and SIGWINCH
+ * in the parent process, unless running in ONE_PROCESS mode
+ */
+static void restart(void)
+{
+ if (restart_pending == 1) {
+ /* Probably not an error - don't bother reporting it */
+ return;
+ }
+ restart_pending = 1;
+ is_graceful = 1;
+}
+
+static void set_signals(void)
+{
+ apr_signal(SIGTERM, sig_term);
+ apr_signal(SIGABRT, sig_term);
+}
+
+int nlmUnloadSignaled(int wait)
+{
+ shutdown_pending = 1;
+
+ if (wait) {
+ while (wait_to_finish) {
+ NXThreadYield();
+ }
+ }
+
+ return 0;
+}
+
+/*****************************************************************
+ * Child process main loop.
+ * The following vars are static to avoid getting clobbered by longjmp();
+ * they are really private to child_main.
+ */
+
+
+int ap_graceful_stop_signalled(void)
+{
+ /* not ever called anymore... */
+ return 0;
+}
+
+#define MAX_WB_RETRIES 3
+#ifdef DBINFO_ON
+static int would_block = 0;
+static int retry_success = 0;
+static int retry_fail = 0;
+static int avg_retries = 0;
+#endif
+
+/*static */
+void worker_main(void *arg)
+{
+ ap_listen_rec *lr, *first_lr, *last_lr = NULL;
+ apr_pool_t *ptrans;
+ apr_pool_t *pbucket;
+ apr_allocator_t *allocator;
+ apr_bucket_alloc_t *bucket_alloc;
+ conn_rec *current_conn;
+ apr_status_t stat = APR_EINIT;
+ ap_sb_handle_t *sbh;
+
+ int my_worker_num = (int)arg;
+ apr_socket_t *csd = NULL;
+ int requests_this_child = 0;
+ apr_socket_t *sd = NULL;
+ fd_set main_fds;
+
+ int sockdes;
+ int srv;
+ struct timeval tv;
+ int wouldblock_retry;
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+
+ apr_pool_create_ex(&ptrans, pmain, NULL, allocator);
+ apr_allocator_owner_set(allocator, ptrans);
+ apr_pool_tag(ptrans, "transaction");
+
+ bucket_alloc = apr_bucket_alloc_create_ex(allocator);
+
+ atomic_inc (&worker_thread_count);
+
+ while (!die_now) {
+ /*
+ * (Re)initialize this child to a pre-connection state.
+ */
+ current_conn = NULL;
+ apr_pool_clear(ptrans);
+
+ if ((ap_max_requests_per_child > 0
+ && requests_this_child++ >= ap_max_requests_per_child)) {
+ DBPRINT1 ("\n**Thread slot %d is shutting down", my_worker_num);
+ clean_child_exit(0, my_worker_num, ptrans, bucket_alloc);
+ }
+
+ ap_update_child_status_from_indexes(0, my_worker_num, WORKER_READY,
+ (request_rec *) NULL);
+
+ /*
+ * Wait for an acceptable connection to arrive.
+ */
+
+ for (;;) {
+ if (shutdown_pending || restart_pending || (ap_scoreboard_image->servers[0][my_worker_num].status == WORKER_IDLE_KILL)) {
+ DBPRINT1 ("\nThread slot %d is shutting down\n", my_worker_num);
+ clean_child_exit(0, my_worker_num, ptrans, bucket_alloc);
+ }
+
+ /* Check the listen queue on all sockets for requests */
+ memcpy(&main_fds, &listenfds, sizeof(fd_set));
+ srv = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv);
+
+ if (srv <= 0) {
+ if (srv < 0) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "select() failed on listen socket");
+ apr_thread_yield();
+ }
+ continue;
+ }
+
+ /* remember the last_lr we searched last time around so that
+ we don't end up starving any particular listening socket */
+ if (last_lr == NULL) {
+ lr = ap_listeners;
+ }
+ else {
+ lr = last_lr->next;
+ if (!lr)
+ lr = ap_listeners;
+ }
+ first_lr = lr;
+ do {
+ apr_os_sock_get(&sockdes, lr->sd);
+ if (FD_ISSET(sockdes, &main_fds))
+ goto got_listener;
+ lr = lr->next;
+ if (!lr)
+ lr = ap_listeners;
+ } while (lr != first_lr);
+ /* if we get here, something unexpected happened. Go back
+ into the select state and try again.
+ */
+ continue;
+ got_listener:
+ last_lr = lr;
+ sd = lr->sd;
+
+ wouldblock_retry = MAX_WB_RETRIES;
+
+ while (wouldblock_retry) {
+ if ((stat = apr_socket_accept(&csd, sd, ptrans)) == APR_SUCCESS) {
+ break;
+ }
+ else {
+ /* if the error is a wouldblock then maybe we were too
+ quick try to pull the next request from the listen
+ queue. Try a few more times then return to our idle
+ listen state. */
+ if (!APR_STATUS_IS_EAGAIN(stat)) {
+ break;
+ }
+
+ if (wouldblock_retry--) {
+ apr_thread_yield();
+ }
+ }
+ }
+
+ /* If we got a new socket, set it to non-blocking mode and process
+ it. Otherwise handle the error. */
+ if (stat == APR_SUCCESS) {
+ apr_socket_opt_set(csd, APR_SO_NONBLOCK, 0);
+#ifdef DBINFO_ON
+ if (wouldblock_retry < MAX_WB_RETRIES) {
+ retry_success++;
+ avg_retries += (MAX_WB_RETRIES-wouldblock_retry);
+ }
+#endif
+ break; /* We have a socket ready for reading */
+ }
+ else {
+#ifdef DBINFO_ON
+ if (APR_STATUS_IS_EAGAIN(stat)) {
+ would_block++;
+ retry_fail++;
+ }
+ else if (
+#else
+ if (APR_STATUS_IS_EAGAIN(stat) ||
+#endif
+ APR_STATUS_IS_ECONNRESET(stat) ||
+ APR_STATUS_IS_ETIMEDOUT(stat) ||
+ APR_STATUS_IS_EHOSTUNREACH(stat) ||
+ APR_STATUS_IS_ENETUNREACH(stat)) {
+ ;
+ }
+#ifdef USE_WINSOCK
+ else if (APR_STATUS_IS_ENETDOWN(stat)) {
+ /*
+ * When the network layer has been shut down, there
+ * is not much use in simply exiting: the parent
+ * would simply re-create us (and we'd fail again).
+ * Use the CHILDFATAL code to tear the server down.
+ * @@@ Martin's idea for possible improvement:
+ * A different approach would be to define
+ * a new APEXIT_NETDOWN exit code, the reception
+ * of which would make the parent shutdown all
+ * children, then idle-loop until it detected that
+ * the network is up again, and restart the children.
+ * Ben Hyde noted that temporary ENETDOWN situations
+ * occur in mobile IP.
+ */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, stat, ap_server_conf,
+ "apr_socket_accept: giving up.");
+ clean_child_exit(APEXIT_CHILDFATAL, my_worker_num, ptrans,
+ bucket_alloc);
+ }
+#endif
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, stat, ap_server_conf,
+ "apr_socket_accept: (client socket)");
+ clean_child_exit(1, my_worker_num, ptrans, bucket_alloc);
+ }
+ }
+ }
+
+ ap_create_sb_handle(&sbh, ptrans, 0, my_worker_num);
+ /*
+ * We now have a connection, so set it up with the appropriate
+ * socket options, file descriptors, and read/write buffers.
+ */
+ current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd,
+ my_worker_num, sbh,
+ bucket_alloc);
+ if (current_conn) {
+ ap_process_connection(current_conn, csd);
+ ap_lingering_close(current_conn);
+ }
+ request_count++;
+ }
+ clean_child_exit(0, my_worker_num, ptrans, bucket_alloc);
+}
+
+
+static int make_child(server_rec *s, int slot)
+{
+ int tid;
+ int err=0;
+ NXContext_t ctx;
+
+ if (slot + 1 > ap_max_workers_limit) {
+ ap_max_workers_limit = slot + 1;
+ }
+
+ ap_update_child_status_from_indexes(0, slot, WORKER_STARTING,
+ (request_rec *) NULL);
+
+ if (ctx = NXContextAlloc((void (*)(void *)) worker_main, (void*)slot, NX_PRIO_MED, ap_thread_stacksize, NX_CTX_NORMAL, &err)) {
+ char threadName[32];
+
+ sprintf (threadName, "Apache_Worker %d", slot);
+ NXContextSetName(ctx, threadName);
+ err = NXThreadCreate(ctx, NX_THR_BIND_CONTEXT, &tid);
+ if (err) {
+ NXContextFree (ctx);
+ }
+ }
+
+ if (err) {
+ /* create thread didn't succeed. Fix the scoreboard or else
+ * it will say SERVER_STARTING forever and ever
+ */
+ ap_update_child_status_from_indexes(0, slot, WORKER_DEAD,
+ (request_rec *) NULL);
+
+ /* In case system resources are maxxed out, we don't want
+ Apache running away with the CPU trying to fork over and
+ over and over again. */
+ apr_thread_yield();
+
+ return -1;
+ }
+
+ ap_scoreboard_image->servers[0][slot].tid = tid;
+
+ return 0;
+}
+
+
+/* start up a bunch of worker threads */
+static void startup_workers(int number_to_start)
+{
+ int i;
+
+ for (i = 0; number_to_start && i < ap_threads_limit; ++i) {
+ if (ap_scoreboard_image->servers[0][i].status != WORKER_DEAD) {
+ continue;
+ }
+ if (make_child(ap_server_conf, i) < 0) {
+ break;
+ }
+ --number_to_start;
+ }
+}
+
+
+/*
+ * idle_spawn_rate is the number of children that will be spawned on the
+ * next maintenance cycle if there aren't enough idle servers. It is
+ * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
+ * without the need to spawn.
+ */
+static int idle_spawn_rate = 1;
+#ifndef MAX_SPAWN_RATE
+#define MAX_SPAWN_RATE (64)
+#endif
+static int hold_off_on_exponential_spawning;
+
+static void perform_idle_server_maintenance(apr_pool_t *p)
+{
+ int i;
+ int to_kill;
+ int idle_count;
+ worker_score *ws;
+ int free_length;
+ int free_slots[MAX_SPAWN_RATE];
+ int last_non_dead;
+ int total_non_dead;
+
+ /* initialize the free_list */
+ free_length = 0;
+
+ to_kill = -1;
+ idle_count = 0;
+ last_non_dead = -1;
+ total_non_dead = 0;
+
+ for (i = 0; i < ap_threads_limit; ++i) {
+ int status;
+
+ if (i >= ap_max_workers_limit && free_length == idle_spawn_rate)
+ break;
+ ws = &ap_scoreboard_image->servers[0][i];
+ status = ws->status;
+ if (status == WORKER_DEAD) {
+ /* try to keep children numbers as low as possible */
+ if (free_length < idle_spawn_rate) {
+ free_slots[free_length] = i;
+ ++free_length;
+ }
+ }
+ else if (status == WORKER_IDLE_KILL) {
+ /* If it is already marked to die, skip it */
+ continue;
+ }
+ else {
+ /* We consider a starting server as idle because we started it
+ * at least a cycle ago, and if it still hasn't finished starting
+ * then we're just going to swamp things worse by forking more.
+ * So we hopefully won't need to fork more if we count it.
+ * This depends on the ordering of SERVER_READY and SERVER_STARTING.
+ */
+ if (status <= WORKER_READY) {
+ ++ idle_count;
+ /* always kill the highest numbered child if we have to...
+ * no really well thought out reason ... other than observing
+ * the server behaviour under linux where lower numbered children
+ * tend to service more hits (and hence are more likely to have
+ * their data in cpu caches).
+ */
+ to_kill = i;
+ }
+
+ ++total_non_dead;
+ last_non_dead = i;
+ }
+ }
+ DBPRINT2("Total: %d Idle Count: %d \r", total_non_dead, idle_count);
+ ap_max_workers_limit = last_non_dead + 1;
+ if (idle_count > ap_threads_max_free) {
+ /* kill off one child... we use the pod because that'll cause it to
+ * shut down gracefully, in case it happened to pick up a request
+ * while we were counting
+ */
+ idle_spawn_rate = 1;
+ ap_update_child_status_from_indexes(0, last_non_dead, WORKER_IDLE_KILL,
+ (request_rec *) NULL);
+ DBPRINT1("\nKilling idle thread: %d\n", last_non_dead);
+ }
+ else if (idle_count < ap_threads_min_free) {
+ /* terminate the free list */
+ if (free_length == 0) {
+ /* only report this condition once */
+ static int reported = 0;
+
+ if (!reported) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
+ "server reached MaxClients setting, consider"
+ " raising the MaxClients setting");
+ reported = 1;
+ }
+ idle_spawn_rate = 1;
+ }
+ else {
+ if (idle_spawn_rate >= 8) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "server seems busy, (you may need "
+ "to increase StartServers, or Min/MaxSpareServers), "
+ "spawning %d children, there are %d idle, and "
+ "%d total children", idle_spawn_rate,
+ idle_count, total_non_dead);
+ }
+ DBPRINT0("\n");
+ for (i = 0; i < free_length; ++i) {
+ DBPRINT1("Spawning additional thread slot: %d\n", free_slots[i]);
+ make_child(ap_server_conf, free_slots[i]);
+ }
+ /* the next time around we want to spawn twice as many if this
+ * wasn't good enough, but not if we've just done a graceful
+ */
+ if (hold_off_on_exponential_spawning) {
+ --hold_off_on_exponential_spawning;
+ }
+ else if (idle_spawn_rate < MAX_SPAWN_RATE) {
+ idle_spawn_rate *= 2;
+ }
+ }
+ }
+ else {
+ idle_spawn_rate = 1;
+ }
+}
+
+static void display_settings ()
+{
+ int status_array[SERVER_NUM_STATUS];
+ int i, status, total=0;
+ int reqs = request_count;
+#ifdef DBINFO_ON
+ int wblock = would_block;
+
+ would_block = 0;
+#endif
+
+ request_count = 0;
+
+ ClearScreen (getscreenhandle());
+ printf("%s \n", ap_get_server_version());
+
+ for (i=0;i<SERVER_NUM_STATUS;i++) {
+ status_array[i] = 0;
+ }
+
+ for (i = 0; i < ap_threads_limit; ++i) {
+ status = (ap_scoreboard_image->servers[0][i]).status;
+ status_array[status]++;
+ }
+
+ for (i=0;i<SERVER_NUM_STATUS;i++) {
+ switch(i)
+ {
+ case SERVER_DEAD:
+ printf ("Available:\t%d\n", status_array[i]);
+ break;
+ case SERVER_STARTING:
+ printf ("Starting:\t%d\n", status_array[i]);
+ break;
+ case SERVER_READY:
+ printf ("Ready:\t\t%d\n", status_array[i]);
+ break;
+ case SERVER_BUSY_READ:
+ printf ("Busy:\t\t%d\n", status_array[i]);
+ break;
+ case SERVER_BUSY_WRITE:
+ printf ("Busy Write:\t%d\n", status_array[i]);
+ break;
+ case SERVER_BUSY_KEEPALIVE:
+ printf ("Busy Keepalive:\t%d\n", status_array[i]);
+ break;
+ case SERVER_BUSY_LOG:
+ printf ("Busy Log:\t%d\n", status_array[i]);
+ break;
+ case SERVER_BUSY_DNS:
+ printf ("Busy DNS:\t%d\n", status_array[i]);
+ break;
+ case SERVER_CLOSING:
+ printf ("Closing:\t%d\n", status_array[i]);
+ break;
+ case SERVER_GRACEFUL:
+ printf ("Restart:\t%d\n", status_array[i]);
+ break;
+ case SERVER_IDLE_KILL:
+ printf ("Idle Kill:\t%d\n", status_array[i]);
+ break;
+ default:
+ printf ("Unknown Status:\t%d\n", status_array[i]);
+ break;
+ }
+ if (i != SERVER_DEAD)
+ total+=status_array[i];
+ }
+ printf ("Total Running:\t%d\tout of: \t%d\n", total, ap_threads_limit);
+ printf ("Requests per interval:\t%d\n", reqs);
+
+#ifdef DBINFO_ON
+ printf ("Would blocks:\t%d\n", wblock);
+ printf ("Successful retries:\t%d\n", retry_success);
+ printf ("Failed retries:\t%d\n", retry_fail);
+ printf ("Avg retries:\t%d\n", retry_success == 0 ? 0 : avg_retries / retry_success);
+#endif
+}
+
+static void show_server_data()
+{
+ ap_listen_rec *lr;
+ module **m;
+
+ printf("%s\n", ap_get_server_version());
+ if (ap_my_addrspace && (ap_my_addrspace[0] != 'O') && (ap_my_addrspace[1] != 'S'))
+ printf(" Running in address space %s\n", ap_my_addrspace);
+
+
+ /* Display listening ports */
+ printf(" Listening on port(s):");
+ lr = ap_listeners;
+ do {
+ printf(" %d", lr->bind_addr->port);
+ lr = lr->next;
+ } while(lr && lr != ap_listeners);
+
+ /* Display dynamic modules loaded */
+ printf("\n");
+ for (m = ap_loaded_modules; *m != NULL; m++) {
+ if (((module*)*m)->dynamic_load_handle) {
+ printf(" Loaded dynamic module %s\n", ((module*)*m)->name);
+ }
+ }
+}
+
+
+static int setup_listeners(server_rec *s)
+{
+ ap_listen_rec *lr;
+ int sockdes;
+
+ if (ap_setup_listeners(s) < 1 ) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s,
+ "no listening sockets available, shutting down");
+ return -1;
+ }
+
+ listenmaxfd = -1;
+ FD_ZERO(&listenfds);
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ apr_os_sock_get(&sockdes, lr->sd);
+ FD_SET(sockdes, &listenfds);
+ if (sockdes > listenmaxfd) {
+ listenmaxfd = sockdes;
+ }
+ }
+ return 0;
+}
+
+static int shutdown_listeners()
+{
+ ap_listen_rec *lr;
+
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ apr_socket_close(lr->sd);
+ }
+ ap_listeners = NULL;
+ return 0;
+}
+
+/*****************************************************************
+ * Executive routines.
+ */
+
+int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
+{
+ apr_status_t status=0;
+
+ pconf = _pconf;
+ ap_server_conf = s;
+
+ if (setup_listeners(s)) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, status, s,
+ "no listening sockets available, shutting down");
+ return -1;
+ }
+
+ restart_pending = shutdown_pending = 0;
+ worker_thread_count = 0;
+
+ if (!is_graceful) {
+ if (ap_run_pre_mpm(s->process->pool, SB_NOT_SHARED) != OK) {
+ return 1;
+ }
+ }
+
+ /* Only set slot 0 since that is all NetWare will ever have. */
+ ap_scoreboard_image->parent[0].pid = getpid();
+
+ set_signals();
+
+ apr_pool_create(&pmain, pconf);
+ ap_run_child_init(pmain, ap_server_conf);
+
+ if (ap_threads_max_free < ap_threads_min_free + 1) /* Don't thrash... */
+ ap_threads_max_free = ap_threads_min_free + 1;
+ request_count = 0;
+
+ startup_workers(ap_threads_to_start);
+
+ /* Allow the Apache screen to be closed normally on exit() only if it
+ has not been explicitly forced to close on exit(). (ie. the -E flag
+ was specified at startup) */
+ if (hold_screen_on_exit > 0) {
+ hold_screen_on_exit = 0;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+#ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "AcceptMutex: %s (default: %s)",
+ apr_proc_mutex_name(accept_mutex),
+ apr_proc_mutex_defname());
+#endif
+ show_server_data();
+
+ mpm_state = AP_MPMQ_RUNNING;
+ while (!restart_pending && !shutdown_pending) {
+ perform_idle_server_maintenance(pconf);
+ if (show_settings)
+ display_settings();
+ apr_thread_yield();
+ apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
+ }
+ mpm_state = AP_MPMQ_STOPPING;
+
+
+ /* Shutdown the listen sockets so that we don't get stuck in a blocking call.
+ shutdown_listeners();*/
+
+ if (shutdown_pending) { /* Got an unload from the console */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught SIGTERM, shutting down");
+
+ while (worker_thread_count > 0) {
+ printf ("\rShutdown pending. Waiting for %d thread(s) to terminate...",
+ worker_thread_count);
+ apr_thread_yield();
+ }
+
+ return 1;
+ }
+ else { /* the only other way out is a restart */
+ /* advance to the next generation */
+ /* XXX: we really need to make sure this new generation number isn't in
+ * use by any of the children.
+ */
+ ++ap_my_generation;
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "Graceful restart requested, doing restart");
+
+ /* Wait for all of the threads to terminate before initiating the restart */
+ while (worker_thread_count > 0) {
+ printf ("\rRestart pending. Waiting for %d thread(s) to terminate...",
+ worker_thread_count);
+ apr_thread_yield();
+ }
+ printf ("\nRestarting...\n");
+ }
+
+ return 0;
+}
+
+static int netware_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ int debug;
+ char *addrname = NULL;
+
+ mpm_state = AP_MPMQ_STARTING;
+
+ debug = ap_exists_config_define("DEBUG");
+
+ is_graceful = 0;
+ ap_my_pid = getpid();
+ addrname = getaddressspacename (NULL, NULL);
+ if (addrname) {
+ ap_my_addrspace = apr_pstrdup (p, addrname);
+ free (addrname);
+ }
+
+#ifndef USE_WINSOCK
+ /* The following call has been moved to the mod_nw_ssl pre-config handler */
+ ap_listen_pre_config();
+#endif
+
+ ap_threads_to_start = DEFAULT_START_THREADS;
+ ap_threads_min_free = DEFAULT_MIN_FREE_THREADS;
+ ap_threads_max_free = DEFAULT_MAX_FREE_THREADS;
+ ap_threads_limit = HARD_THREAD_LIMIT;
+ ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
+ ap_extended_status = 0;
+ ap_thread_stacksize = DEFAULT_THREAD_STACKSIZE;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+
+ return OK;
+}
+
+static void netware_mpm_hooks(apr_pool_t *p)
+{
+ ap_hook_pre_config(netware_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+void netware_rewrite_args(process_rec *process)
+{
+ char *def_server_root;
+ char optbuf[3];
+ const char *opt_arg;
+ apr_getopt_t *opt;
+ apr_array_header_t *mpm_new_argv;
+
+
+ atexit (mpm_term);
+ InstallConsoleHandler();
+
+ /* Make sure to hold the Apache screen open if exit() is called */
+ hold_screen_on_exit = 1;
+
+ /* Rewrite process->argv[];
+ *
+ * add default -d serverroot from the path of this executable
+ *
+ * The end result will look like:
+ * The -d serverroot default from the running executable
+ */
+ if (process->argc > 0) {
+ char *s = apr_pstrdup (process->pconf, process->argv[0]);
+ if (s) {
+ int i, len = strlen(s);
+
+ for (i=len; i; i--) {
+ if (s[i] == '\\' || s[i] == '/') {
+ s[i] = '\0';
+ apr_filepath_merge(&def_server_root, NULL, s,
+ APR_FILEPATH_TRUENAME, process->pool);
+ break;
+ }
+ }
+ /* Use process->pool so that the rewritten argv
+ * lasts for the lifetime of the server process,
+ * because pconf will be destroyed after the
+ * initial pre-flight of the config parser.
+ */
+ mpm_new_argv = apr_array_make(process->pool, process->argc + 2,
+ sizeof(const char *));
+ *(const char **)apr_array_push(mpm_new_argv) = process->argv[0];
+ *(const char **)apr_array_push(mpm_new_argv) = "-d";
+ *(const char **)apr_array_push(mpm_new_argv) = def_server_root;
+
+ optbuf[0] = '-';
+ optbuf[2] = '\0';
+ apr_getopt_init(&opt, process->pool, process->argc, (char**) process->argv);
+ while (apr_getopt(opt, AP_SERVER_BASEARGS"n:", optbuf + 1, &opt_arg) == APR_SUCCESS) {
+ switch (optbuf[1]) {
+ case 'n':
+ if (opt_arg) {
+ renamescreen(opt_arg);
+ }
+ break;
+ case 'E':
+ /* Don't need to hold the screen open if the output is going to a file */
+ hold_screen_on_exit = -1;
+ default:
+ *(const char **)apr_array_push(mpm_new_argv) =
+ apr_pstrdup(process->pool, optbuf);
+
+ if (opt_arg) {
+ *(const char **)apr_array_push(mpm_new_argv) = opt_arg;
+ }
+ break;
+ }
+ }
+ process->argc = mpm_new_argv->nelts;
+ process->argv = (const char * const *) mpm_new_argv->elts;
+ }
+ }
+}
+
+static int CommandLineInterpreter(scr_t screenID, const char *commandLine)
+{
+ char *szCommand = "APACHE2 ";
+ int iCommandLen = 8;
+ char szcommandLine[256];
+ char *pID;
+ screenID = screenID;
+
+
+ if (commandLine == NULL)
+ return NOTMYCOMMAND;
+ if (strlen(commandLine) <= strlen(szCommand))
+ return NOTMYCOMMAND;
+
+ strncpy (szcommandLine, commandLine, sizeof(szcommandLine)-1);
+
+ /* All added commands begin with "APACHE2 " */
+
+ if (!strnicmp(szCommand, szcommandLine, iCommandLen)) {
+ ActivateScreen (getscreenhandle());
+
+ /* If an instance id was not given but the nlm is loaded in
+ protected space, then the the command belongs to the
+ OS address space instance to pass it on. */
+ pID = strstr (szcommandLine, "-p");
+ if ((pID == NULL) && nlmisloadedprotected())
+ return NOTMYCOMMAND;
+
+ /* If we got an instance id but it doesn't match this
+ instance of the nlm, pass it on. */
+ if (pID) {
+ pID = &pID[2];
+ while (*pID && (*pID == ' '))
+ pID++;
+ }
+ if (pID && ap_my_addrspace && strnicmp(pID, ap_my_addrspace, strlen(ap_my_addrspace)))
+ return NOTMYCOMMAND;
+
+ /* If we have determined that this command belongs to this
+ instance of the nlm, then handle it. */
+ if (!strnicmp("RESTART",&szcommandLine[iCommandLen],3)) {
+ printf("Restart Requested...\n");
+ restart();
+ }
+ else if (!strnicmp("VERSION",&szcommandLine[iCommandLen],3)) {
+ printf("Server version: %s\n", ap_get_server_version());
+ printf("Server built: %s\n", ap_get_server_built());
+ }
+ else if (!strnicmp("MODULES",&szcommandLine[iCommandLen],3)) {
+ ap_show_modules();
+ }
+ else if (!strnicmp("DIRECTIVES",&szcommandLine[iCommandLen],3)) {
+ ap_show_directives();
+ }
+ else if (!strnicmp("SHUTDOWN",&szcommandLine[iCommandLen],3)) {
+ printf("Shutdown Requested...\n");
+ shutdown_pending = 1;
+ }
+ else if (!strnicmp("SETTINGS",&szcommandLine[iCommandLen],3)) {
+ if (show_settings) {
+ show_settings = 0;
+ ClearScreen (getscreenhandle());
+ show_server_data();
+ }
+ else {
+ show_settings = 1;
+ display_settings();
+ }
+ }
+ else {
+ show_settings = 0;
+ if (strnicmp("HELP",&szcommandLine[iCommandLen],3))
+ printf("Unknown APACHE2 command %s\n", &szcommandLine[iCommandLen]);
+ printf("Usage: APACHE2 [command] [-p <instance ID>]\n");
+ printf("Commands:\n");
+ printf("\tDIRECTIVES - Show directives\n");
+ printf("\tHELP - Display this help information\n");
+ printf("\tMODULES - Show a list of the loaded modules\n");
+ printf("\tRESTART - Reread the configuration file and restart Apache\n");
+ printf("\tSETTINGS - Show current thread status\n");
+ printf("\tSHUTDOWN - Shutdown Apache\n");
+ printf("\tVERSION - Display the server version information\n");
+ }
+
+ /* Tell NetWare we handled the command */
+ return HANDLEDCOMMAND;
+ }
+
+ /* Tell NetWare that the command isn't mine */
+ return NOTMYCOMMAND;
+}
+
+static int InstallConsoleHandler(void)
+{
+ /* Our command line handler interfaces the system operator
+ with this NLM */
+
+ NX_WRAP_INTERFACE(CommandLineInterpreter, 2, (void*)&(ConsoleHandler.parser));
+
+ ConsoleHandler.rTag = AllocateResourceTag(getnlmhandle(), "Command Line Processor",
+ ConsoleCommandSignature);
+ if (!ConsoleHandler.rTag)
+ {
+ printf("Error on allocate resource tag\n");
+ return 1;
+ }
+
+ RegisterConsoleCommand(&ConsoleHandler);
+
+ /* The Remove procedure unregisters the console handler */
+
+ return 0;
+}
+
+static void RemoveConsoleHandler(void)
+{
+ UnRegisterConsoleCommand(&ConsoleHandler);
+ NX_UNWRAP_INTERFACE(ConsoleHandler.parser);
+}
+
+static const char *set_threads_to_start(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_to_start = atoi(arg);
+ return NULL;
+}
+
+static const char *set_min_free_threads(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_min_free = atoi(arg);
+ if (ap_threads_min_free <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: detected MinSpareServers set to non-positive.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Resetting to 1 to avoid almost certain Apache failure.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Please read the documentation.");
+ ap_threads_min_free = 1;
+ }
+
+ return NULL;
+}
+
+static const char *set_max_free_threads(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_max_free = atoi(arg);
+ return NULL;
+}
+
+static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_limit = atoi(arg);
+ if (ap_threads_limit > HARD_THREAD_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxThreads of %d exceeds compile time limit "
+ "of %d threads,", ap_threads_limit, HARD_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering MaxThreads to %d. To increase, please "
+ "see the", HARD_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " HARD_THREAD_LIMIT define in %s.",
+ AP_MPM_HARD_LIMITS_FILE);
+ ap_threads_limit = HARD_THREAD_LIMIT;
+ }
+ else if (ap_threads_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require MaxThreads > 0, setting to 1");
+ ap_threads_limit = 1;
+ }
+ return NULL;
+}
+
+static const command_rec netware_mpm_cmds[] = {
+LISTEN_COMMANDS,
+AP_INIT_TAKE1("StartThreads", set_threads_to_start, NULL, RSRC_CONF,
+ "Number of worker threads launched at server startup"),
+AP_INIT_TAKE1("MinSpareThreads", set_min_free_threads, NULL, RSRC_CONF,
+ "Minimum number of idle threads, to handle request spikes"),
+AP_INIT_TAKE1("MaxSpareThreads", set_max_free_threads, NULL, RSRC_CONF,
+ "Maximum number of idle threads"),
+AP_INIT_TAKE1("MaxThreads", set_thread_limit, NULL, RSRC_CONF,
+ "Maximum number of worker threads alive at the same time"),
+{ NULL }
+};
+
+module AP_MODULE_DECLARE_DATA mpm_netware_module = {
+ MPM20_MODULE_STUFF,
+ netware_rewrite_args, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ netware_mpm_cmds, /* command apr_table_t */
+ netware_mpm_hooks, /* register hooks */
+};
diff --git a/server/mpm/prefork/Makefile.in b/server/mpm/prefork/Makefile.in
new file mode 100644
index 00000000..034bf5ce
--- /dev/null
+++ b/server/mpm/prefork/Makefile.in
@@ -0,0 +1,5 @@
+
+LTLIBRARY_NAME = libprefork.la
+LTLIBRARY_SOURCES = prefork.c
+
+include $(top_srcdir)/build/ltlib.mk
diff --git a/server/mpm/prefork/config.m4 b/server/mpm/prefork/config.m4
new file mode 100644
index 00000000..9c189a86
--- /dev/null
+++ b/server/mpm/prefork/config.m4
@@ -0,0 +1,3 @@
+if test "$MPM_NAME" = "prefork" ; then
+ APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile)
+fi
diff --git a/server/mpm/prefork/mpm.h b/server/mpm/prefork/mpm.h
new file mode 100644
index 00000000..bf1fb949
--- /dev/null
+++ b/server/mpm/prefork/mpm.h
@@ -0,0 +1,62 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file prefork/mpm.h
+ * @brief Unix Prefork MPM (default for Uinx systems)
+ *
+ * @defgroup APACHE_MPM_PREFORK Unix Prefork
+ * @ingroup APACHE_MPM APACHE_OS_UNIX
+ * @{
+ */
+
+#include "httpd.h"
+#include "mpm_default.h"
+#include "scoreboard.h"
+#include "unixd.h"
+
+#ifndef APACHE_MPM_PREFORK_H
+#define APACHE_MPM_PREFORK_H
+
+#define PREFORK_MPM
+
+#define MPM_NAME "Prefork"
+
+#define AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+#define AP_MPM_WANT_WAIT_OR_TIMEOUT
+#define AP_MPM_WANT_PROCESS_CHILD_STATUS
+#define AP_MPM_WANT_SET_PIDFILE
+#define AP_MPM_WANT_SET_SCOREBOARD
+#define AP_MPM_WANT_SET_LOCKFILE
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_WANT_SET_COREDUMPDIR
+#define AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+#define AP_MPM_WANT_SIGNAL_SERVER
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+#define AP_MPM_WANT_FATAL_SIGNAL_HANDLER
+#define AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
+#define AP_MPM_DISABLE_NAGLE_ACCEPTED_SOCK
+
+#define AP_MPM_USES_POD 1
+#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid)
+#define MPM_NOTE_CHILD_KILLED(i) (MPM_CHILD_PID(i) = 0)
+#define MPM_ACCEPT_FUNC unixd_accept
+
+extern int ap_threads_per_child;
+extern int ap_max_daemons_limit;
+extern server_rec *ap_server_conf;
+#endif /* APACHE_MPM_PREFORK_H */
+/** @} */
diff --git a/server/mpm/prefork/mpm_default.h b/server/mpm/prefork/mpm_default.h
new file mode 100644
index 00000000..d548a7ec
--- /dev/null
+++ b/server/mpm/prefork/mpm_default.h
@@ -0,0 +1,74 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file prefork/mpm_default.h
+ * @brief Prefork MPM defaults
+ *
+ * @addtogroup APACHE_MPM_PREFORK
+ * @{
+ */
+
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* Number of servers to spawn off by default --- also, if fewer than
+ * this free when the caretaker checks, it will spawn more.
+ */
+#ifndef DEFAULT_START_DAEMON
+#define DEFAULT_START_DAEMON 5
+#endif
+
+/* Maximum number of *free* server processes --- more than this, and
+ * they will die off.
+ */
+
+#ifndef DEFAULT_MAX_FREE_DAEMON
+#define DEFAULT_MAX_FREE_DAEMON 10
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+
+#ifndef DEFAULT_MIN_FREE_DAEMON
+#define DEFAULT_MIN_FREE_DAEMON 5
+#endif
+
+/* File used for accept locking, when we use a file */
+#ifndef DEFAULT_LOCKFILE
+#define DEFAULT_LOCKFILE DEFAULT_REL_RUNTIMEDIR "/accept.lock"
+#endif
+
+/* Where the main/parent process's pid is logged */
+#ifndef DEFAULT_PIDLOG
+#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+#endif
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If <= 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD
+#define DEFAULT_MAX_REQUESTS_PER_CHILD 10000
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c
new file mode 100644
index 00000000..3858c290
--- /dev/null
+++ b/server/mpm/prefork/prefork.c
@@ -0,0 +1,1478 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_portable.h"
+#include "apr_strings.h"
+#include "apr_thread_proc.h"
+#include "apr_signal.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "mpm_default.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "scoreboard.h"
+#include "ap_mpm.h"
+#include "unixd.h"
+#include "mpm_common.h"
+#include "ap_listen.h"
+#include "ap_mmn.h"
+#include "apr_poll.h"
+
+#ifdef HAVE_BSTRING_H
+#include <bstring.h> /* for IRIX, FD_SET calls bzero() */
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#ifdef HAVE_SYS_PROCESSOR_H
+#include <sys/processor.h> /* for bindprocessor() */
+#endif
+
+#include <signal.h>
+#include <sys/times.h>
+
+/* Limit on the total --- clients will be locked out if more servers than
+ * this are needed. It is intended solely to keep the server from crashing
+ * when things get out of hand.
+ *
+ * We keep a hard maximum number of servers, for two reasons --- first off,
+ * in case something goes seriously wrong, we want to stop the fork bomb
+ * short of actually crashing the machine we're running on by filling some
+ * kernel table. Secondly, it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef DEFAULT_SERVER_LIMIT
+#define DEFAULT_SERVER_LIMIT 256
+#endif
+
+/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want
+ * some sort of compile-time limit to help catch typos.
+ */
+#ifndef MAX_SERVER_LIMIT
+#define MAX_SERVER_LIMIT 200000
+#endif
+
+#ifndef HARD_THREAD_LIMIT
+#define HARD_THREAD_LIMIT 1
+#endif
+
+/* config globals */
+
+int ap_threads_per_child=0; /* Worker threads per child */
+static apr_proc_mutex_t *accept_mutex;
+static int ap_daemons_to_start=0;
+static int ap_daemons_min_free=0;
+static int ap_daemons_max_free=0;
+static int ap_daemons_limit=0; /* MaxClients */
+static int server_limit = DEFAULT_SERVER_LIMIT;
+static int first_server_limit = 0;
+static int changed_limit_at_restart;
+static int mpm_state = AP_MPMQ_STARTING;
+static ap_pod_t *pod;
+
+/*
+ * The max child slot ever assigned, preserved across restarts. Necessary
+ * to deal with MaxClients changes across AP_SIG_GRACEFUL restarts. We
+ * use this value to optimize routines that have to scan the entire scoreboard.
+ */
+int ap_max_daemons_limit = -1;
+server_rec *ap_server_conf;
+
+/* one_process --- debugging mode variable; can be set from the command line
+ * with the -X flag. If set, this gets you the child_main loop running
+ * in the process which originally started up (no detach, no make_child),
+ * which is a pretty nice debugging environment. (You'll get a SIGHUP
+ * early in standalone_main; just continue through. This is the server
+ * trying to kill off any child processes which it might have lying
+ * around --- Apache doesn't keep track of their pids, it just sends
+ * SIGHUP to the process group, ignoring it in the root process.
+ * Continue through and you'll be fine.).
+ */
+
+static int one_process = 0;
+
+static apr_pool_t *pconf; /* Pool for config stuff */
+static apr_pool_t *pchild; /* Pool for httpd child stuff */
+
+static pid_t ap_my_pid; /* it seems silly to call getpid all the time */
+static pid_t parent_pid;
+#ifndef MULTITHREAD
+static int my_child_num;
+#endif
+ap_generation_t volatile ap_my_generation=0;
+
+#ifdef TPF
+int tpf_child = 0;
+char tpf_server_name[INETD_SERVNAME_LENGTH+1];
+#endif /* TPF */
+
+static volatile int die_now = 0;
+
+#ifdef GPROF
+/*
+ * change directory for gprof to plop the gmon.out file
+ * configure in httpd.conf:
+ * GprofDir $RuntimeDir/ -> $ServerRoot/$RuntimeDir/gmon.out
+ * GprofDir $RuntimeDir/% -> $ServerRoot/$RuntimeDir/gprof.$pid/gmon.out
+ */
+static void chdir_for_gprof(void)
+{
+ core_server_config *sconf =
+ ap_get_module_config(ap_server_conf->module_config, &core_module);
+ char *dir = sconf->gprof_dir;
+ const char *use_dir;
+
+ if(dir) {
+ apr_status_t res;
+ char *buf = NULL ;
+ int len = strlen(sconf->gprof_dir) - 1;
+ if(*(dir + len) == '%') {
+ dir[len] = '\0';
+ buf = ap_append_pid(pconf, dir, "gprof.");
+ }
+ use_dir = ap_server_root_relative(pconf, buf ? buf : dir);
+ res = apr_dir_make(use_dir,
+ APR_UREAD | APR_UWRITE | APR_UEXECUTE |
+ APR_GREAD | APR_GEXECUTE |
+ APR_WREAD | APR_WEXECUTE, pconf);
+ if(res != APR_SUCCESS && !APR_STATUS_IS_EEXIST(res)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, res, ap_server_conf,
+ "gprof: error creating directory %s", dir);
+ }
+ }
+ else {
+ use_dir = ap_server_root_relative(pconf, DEFAULT_REL_RUNTIMEDIR);
+ }
+
+ chdir(use_dir);
+}
+#else
+#define chdir_for_gprof()
+#endif
+
+/* XXX - I don't know if TPF will ever use this module or not, so leave
+ * the ap_check_signals calls in but disable them - manoj */
+#define ap_check_signals()
+
+/* a clean exit from a child with proper cleanup */
+static void clean_child_exit(int code) __attribute__ ((noreturn));
+static void clean_child_exit(int code)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+
+ if (pchild) {
+ apr_pool_destroy(pchild);
+ }
+ ap_mpm_pod_close(pod);
+ chdir_for_gprof();
+ exit(code);
+}
+
+static void accept_mutex_on(void)
+{
+ apr_status_t rv = apr_proc_mutex_lock(accept_mutex);
+ if (rv != APR_SUCCESS) {
+ const char *msg = "couldn't grab the accept mutex";
+
+ if (ap_my_generation !=
+ ap_scoreboard_image->global->running_generation) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, NULL, "%s", msg);
+ clean_child_exit(0);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, NULL, "%s", msg);
+ exit(APEXIT_CHILDFATAL);
+ }
+ }
+}
+
+static void accept_mutex_off(void)
+{
+ apr_status_t rv = apr_proc_mutex_unlock(accept_mutex);
+ if (rv != APR_SUCCESS) {
+ const char *msg = "couldn't release the accept mutex";
+
+ if (ap_my_generation !=
+ ap_scoreboard_image->global->running_generation) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, NULL, "%s", msg);
+ /* don't exit here... we have a connection to
+ * process, after which point we'll see that the
+ * generation changed and we'll exit cleanly
+ */
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, NULL, "%s", msg);
+ exit(APEXIT_CHILDFATAL);
+ }
+ }
+}
+
+/* On some architectures it's safe to do unserialized accept()s in the single
+ * Listen case. But it's never safe to do it in the case where there's
+ * multiple Listen statements. Define SINGLE_LISTEN_UNSERIALIZED_ACCEPT
+ * when it's safe in the single Listen case.
+ */
+#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
+#define SAFE_ACCEPT(stmt) do {if (ap_listeners->next) {stmt;}} while(0)
+#else
+#define SAFE_ACCEPT(stmt) do {stmt;} while(0)
+#endif
+
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch(query_code){
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = ap_daemons_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_NOT_SUPPORTED;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_DYNAMIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = server_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = HARD_THREAD_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_THREADS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = ap_daemons_min_free;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_THREADS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = ap_daemons_max_free;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_THREADS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_DAEMONS:
+ *result = server_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MPM_STATE:
+ *result = mpm_state;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+#if defined(NEED_WAITPID)
+/*
+ Systems without a real waitpid sometimes lose a child's exit while waiting
+ for another. Search through the scoreboard for missing children.
+ */
+int reap_children(int *exitcode, apr_exit_why_e *status)
+{
+ int n, pid;
+
+ for (n = 0; n < ap_max_daemons_limit; ++n) {
+ if (ap_scoreboard_image->servers[n][0].status != SERVER_DEAD &&
+ kill((pid = ap_scoreboard_image->parent[n].pid), 0) == -1) {
+ ap_update_child_status_from_indexes(n, 0, SERVER_DEAD, NULL);
+ /* just mark it as having a successful exit status */
+ *status = APR_PROC_EXIT;
+ *exitcode = 0;
+ return(pid);
+ }
+ }
+ return 0;
+}
+#endif
+
+/*****************************************************************
+ * Connection structures and accounting...
+ */
+
+static void just_die(int sig)
+{
+ clean_child_exit(0);
+}
+
+static void stop_listening(int sig)
+{
+ ap_close_listeners();
+
+ /* For a graceful stop, we want the child to exit when done */
+ die_now = 1;
+}
+
+/* volatile just in case */
+static int volatile shutdown_pending;
+static int volatile restart_pending;
+static int volatile is_graceful;
+
+static void sig_term(int sig)
+{
+ if (shutdown_pending == 1) {
+ /* Um, is this _probably_ not an error, if the user has
+ * tried to do a shutdown twice quickly, so we won't
+ * worry about reporting it.
+ */
+ return;
+ }
+ shutdown_pending = 1;
+ is_graceful = (sig == AP_SIG_GRACEFUL_STOP);
+}
+
+/* restart() is the signal handler for SIGHUP and AP_SIG_GRACEFUL
+ * in the parent process, unless running in ONE_PROCESS mode
+ */
+static void restart(int sig)
+{
+ if (restart_pending == 1) {
+ /* Probably not an error - don't bother reporting it */
+ return;
+ }
+ restart_pending = 1;
+ is_graceful = (sig == AP_SIG_GRACEFUL);
+}
+
+static void set_signals(void)
+{
+#ifndef NO_USE_SIGACTION
+ struct sigaction sa;
+#endif
+
+ if (!one_process) {
+ ap_fatal_signal_setup(ap_server_conf, pconf);
+ }
+
+#ifndef NO_USE_SIGACTION
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ sa.sa_handler = sig_term;
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)");
+#ifdef AP_SIG_GRACEFUL_STOP
+ if (sigaction(AP_SIG_GRACEFUL_STOP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(" AP_SIG_GRACEFUL_STOP_STRING ")");
+#endif
+#ifdef SIGINT
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)");
+#endif
+#ifdef SIGXCPU
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGXCPU, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGXCPU)");
+#endif
+#ifdef SIGXFSZ
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGXFSZ, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGXFSZ)");
+#endif
+#ifdef SIGPIPE
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGPIPE)");
+#endif
+
+ /* we want to ignore HUPs and AP_SIG_GRACEFUL while we're busy
+ * processing one
+ */
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, AP_SIG_GRACEFUL);
+ sa.sa_handler = restart;
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGHUP)");
+ if (sigaction(AP_SIG_GRACEFUL, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(" AP_SIG_GRACEFUL_STRING ")");
+#else
+ if (!one_process) {
+#ifdef SIGXCPU
+ apr_signal(SIGXCPU, SIG_DFL);
+#endif /* SIGXCPU */
+#ifdef SIGXFSZ
+ apr_signal(SIGXFSZ, SIG_DFL);
+#endif /* SIGXFSZ */
+ }
+
+ apr_signal(SIGTERM, sig_term);
+#ifdef SIGHUP
+ apr_signal(SIGHUP, restart);
+#endif /* SIGHUP */
+#ifdef AP_SIG_GRACEFUL
+ apr_signal(AP_SIG_GRACEFUL, restart);
+#endif /* AP_SIG_GRACEFUL */
+#ifdef AP_SIG_GRACEFUL_STOP
+ apr_signal(AP_SIG_GRACEFUL_STOP, sig_term);
+#endif /* AP_SIG_GRACEFUL */
+#ifdef SIGPIPE
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif /* SIGPIPE */
+
+#endif
+}
+
+/*****************************************************************
+ * Child process main loop.
+ * The following vars are static to avoid getting clobbered by longjmp();
+ * they are really private to child_main.
+ */
+
+static int requests_this_child;
+static int num_listensocks = 0;
+
+
+int ap_graceful_stop_signalled(void)
+{
+ /* not ever called anymore... */
+ return 0;
+}
+
+
+static void child_main(int child_num_arg)
+{
+ apr_pool_t *ptrans;
+ apr_allocator_t *allocator;
+ apr_status_t status;
+ int i;
+ ap_listen_rec *lr;
+ apr_pollset_t *pollset;
+ ap_sb_handle_t *sbh;
+ apr_bucket_alloc_t *bucket_alloc;
+ int last_poll_idx = 0;
+
+ mpm_state = AP_MPMQ_STARTING; /* for benefit of any hooks that run as this
+ * child initializes
+ */
+
+ my_child_num = child_num_arg;
+ ap_my_pid = getpid();
+ requests_this_child = 0;
+
+ ap_fatal_signal_child_setup(ap_server_conf);
+
+ /* Get a sub context for global allocations in this child, so that
+ * we can have cleanups occur when the child exits.
+ */
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ apr_pool_create_ex(&pchild, pconf, NULL, allocator);
+ apr_allocator_owner_set(allocator, pchild);
+
+ apr_pool_create(&ptrans, pchild);
+ apr_pool_tag(ptrans, "transaction");
+
+ /* needs to be done before we switch UIDs so we have permissions */
+ ap_reopen_scoreboard(pchild, NULL, 0);
+ status = apr_proc_mutex_child_init(&accept_mutex, ap_lock_fname, pchild);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf,
+ "Couldn't initialize cross-process lock in child "
+ "(%s) (%d)", ap_lock_fname, ap_accept_lock_mech);
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ if (unixd_setup_child()) {
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ap_run_child_init(pchild, ap_server_conf);
+
+ ap_create_sb_handle(&sbh, pchild, my_child_num, 0);
+
+ (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL);
+
+ /* Set up the pollfd array */
+ /* ### check the status */
+ (void) apr_pollset_create(&pollset, num_listensocks, pchild, 0);
+
+ for (lr = ap_listeners, i = num_listensocks; i--; lr = lr->next) {
+ apr_pollfd_t pfd = { 0 };
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = lr->sd;
+ pfd.reqevents = APR_POLLIN;
+ pfd.client_data = lr;
+
+ /* ### check the status */
+ (void) apr_pollset_add(pollset, &pfd);
+ }
+
+ mpm_state = AP_MPMQ_RUNNING;
+
+ bucket_alloc = apr_bucket_alloc_create(pchild);
+
+ while (!die_now) {
+ conn_rec *current_conn;
+ void *csd;
+
+ /*
+ * (Re)initialize this child to a pre-connection state.
+ */
+
+ apr_pool_clear(ptrans);
+
+ if ((ap_max_requests_per_child > 0
+ && requests_this_child++ >= ap_max_requests_per_child)) {
+ clean_child_exit(0);
+ }
+
+ (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL);
+
+ /*
+ * Wait for an acceptable connection to arrive.
+ */
+
+ /* Lock around "accept", if necessary */
+ SAFE_ACCEPT(accept_mutex_on());
+
+ if (num_listensocks == 1) {
+ /* There is only one listener record, so refer to that one. */
+ lr = ap_listeners;
+ }
+ else {
+ /* multiple listening sockets - need to poll */
+ for (;;) {
+ apr_int32_t numdesc;
+ const apr_pollfd_t *pdesc;
+
+ /* timeout == -1 == wait forever */
+ status = apr_pollset_poll(pollset, -1, &numdesc, &pdesc);
+ if (status != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(status)) {
+ if (one_process && shutdown_pending) {
+ return;
+ }
+ continue;
+ }
+ /* Single Unix documents select as returning errnos
+ * EBADF, EINTR, and EINVAL... and in none of those
+ * cases does it make sense to continue. In fact
+ * on Linux 2.0.x we seem to end up with EFAULT
+ * occasionally, and we'd loop forever due to it.
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, status,
+ ap_server_conf, "apr_pollset_poll: (listen)");
+ clean_child_exit(1);
+ }
+
+ /* We can always use pdesc[0], but sockets at position N
+ * could end up completely starved of attention in a very
+ * busy server. Therefore, we round-robin across the
+ * returned set of descriptors. While it is possible that
+ * the returned set of descriptors might flip around and
+ * continue to starve some sockets, we happen to know the
+ * internal pollset implementation retains ordering
+ * stability of the sockets. Thus, the round-robin should
+ * ensure that a socket will eventually be serviced.
+ */
+ if (last_poll_idx >= numdesc)
+ last_poll_idx = 0;
+
+ /* Grab a listener record from the client_data of the poll
+ * descriptor, and advance our saved index to round-robin
+ * the next fetch.
+ *
+ * ### hmm... this descriptor might have POLLERR rather
+ * ### than POLLIN
+ */
+ lr = pdesc[last_poll_idx++].client_data;
+ goto got_fd;
+ }
+ }
+ got_fd:
+ /* if we accept() something we don't want to die, so we have to
+ * defer the exit
+ */
+ status = lr->accept_func(&csd, lr, ptrans);
+
+ SAFE_ACCEPT(accept_mutex_off()); /* unlock after "accept" */
+
+ if (status == APR_EGENERAL) {
+ /* resource shortage or should-not-occur occured */
+ clean_child_exit(1);
+ }
+ else if (status != APR_SUCCESS) {
+ continue;
+ }
+
+ /*
+ * We now have a connection, so set it up with the appropriate
+ * socket options, file descriptors, and read/write buffers.
+ */
+
+ current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, my_child_num, sbh, bucket_alloc);
+ if (current_conn) {
+ ap_process_connection(current_conn, csd);
+ ap_lingering_close(current_conn);
+ }
+
+ /* Check the pod and the generation number after processing a
+ * connection so that we'll go away if a graceful restart occurred
+ * while we were processing the connection or we are the lucky
+ * idle server process that gets to die.
+ */
+ if (ap_mpm_pod_check(pod) == APR_SUCCESS) { /* selected as idle? */
+ die_now = 1;
+ }
+ else if (ap_my_generation !=
+ ap_scoreboard_image->global->running_generation) { /* restart? */
+ /* yeah, this could be non-graceful restart, in which case the
+ * parent will kill us soon enough, but why bother checking?
+ */
+ die_now = 1;
+ }
+ }
+ clean_child_exit(0);
+}
+
+
+static int make_child(server_rec *s, int slot)
+{
+ int pid;
+
+ if (slot + 1 > ap_max_daemons_limit) {
+ ap_max_daemons_limit = slot + 1;
+ }
+
+ if (one_process) {
+ apr_signal(SIGHUP, sig_term);
+ /* Don't catch AP_SIG_GRACEFUL in ONE_PROCESS mode :) */
+ apr_signal(SIGINT, sig_term);
+#ifdef SIGQUIT
+ apr_signal(SIGQUIT, SIG_DFL);
+#endif
+ apr_signal(SIGTERM, sig_term);
+ child_main(slot);
+ return 0;
+ }
+
+ (void) ap_update_child_status_from_indexes(slot, 0, SERVER_STARTING,
+ (request_rec *) NULL);
+
+
+#ifdef _OSD_POSIX
+ /* BS2000 requires a "special" version of fork() before a setuid() call */
+ if ((pid = os_fork(unixd_config.user_name)) == -1) {
+#elif defined(TPF)
+ if ((pid = os_fork(s, slot)) == -1) {
+#else
+ if ((pid = fork()) == -1) {
+#endif
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, "fork: Unable to fork new process");
+
+ /* fork didn't succeed. Fix the scoreboard or else
+ * it will say SERVER_STARTING forever and ever
+ */
+ (void) ap_update_child_status_from_indexes(slot, 0, SERVER_DEAD,
+ (request_rec *) NULL);
+
+ /* In case system resources are maxxed out, we don't want
+ * Apache running away with the CPU trying to fork over and
+ * over and over again.
+ */
+ sleep(10);
+
+ return -1;
+ }
+
+ if (!pid) {
+#ifdef HAVE_BINDPROCESSOR
+ /* by default AIX binds to a single processor
+ * this bit unbinds children which will then bind to another cpu
+ */
+ int status = bindprocessor(BINDPROCESS, (int)getpid(),
+ PROCESSOR_CLASS_ANY);
+ if (status != OK) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno,
+ ap_server_conf, "processor unbind failed %d", status);
+ }
+#endif
+ RAISE_SIGSTOP(MAKE_CHILD);
+ AP_MONCONTROL(1);
+ /* Disable the parent's signal handlers and set up proper handling in
+ * the child.
+ */
+ apr_signal(SIGHUP, just_die);
+ apr_signal(SIGTERM, just_die);
+ /* The child process just closes listeners on AP_SIG_GRACEFUL.
+ * The pod is used for signalling the graceful restart.
+ */
+ apr_signal(AP_SIG_GRACEFUL, stop_listening);
+ child_main(slot);
+ }
+
+ ap_scoreboard_image->parent[slot].pid = pid;
+
+ return 0;
+}
+
+
+/* start up a bunch of children */
+static void startup_children(int number_to_start)
+{
+ int i;
+
+ for (i = 0; number_to_start && i < ap_daemons_limit; ++i) {
+ if (ap_scoreboard_image->servers[i][0].status != SERVER_DEAD) {
+ continue;
+ }
+ if (make_child(ap_server_conf, i) < 0) {
+ break;
+ }
+ --number_to_start;
+ }
+}
+
+
+/*
+ * idle_spawn_rate is the number of children that will be spawned on the
+ * next maintenance cycle if there aren't enough idle servers. It is
+ * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
+ * without the need to spawn.
+ */
+static int idle_spawn_rate = 1;
+#ifndef MAX_SPAWN_RATE
+#define MAX_SPAWN_RATE (32)
+#endif
+static int hold_off_on_exponential_spawning;
+
+static void perform_idle_server_maintenance(apr_pool_t *p)
+{
+ int i;
+ int to_kill;
+ int idle_count;
+ worker_score *ws;
+ int free_length;
+ int free_slots[MAX_SPAWN_RATE];
+ int last_non_dead;
+ int total_non_dead;
+
+ /* initialize the free_list */
+ free_length = 0;
+
+ to_kill = -1;
+ idle_count = 0;
+ last_non_dead = -1;
+ total_non_dead = 0;
+
+ for (i = 0; i < ap_daemons_limit; ++i) {
+ int status;
+
+ if (i >= ap_max_daemons_limit && free_length == idle_spawn_rate)
+ break;
+ ws = &ap_scoreboard_image->servers[i][0];
+ status = ws->status;
+ if (status == SERVER_DEAD) {
+ /* try to keep children numbers as low as possible */
+ if (free_length < idle_spawn_rate) {
+ free_slots[free_length] = i;
+ ++free_length;
+ }
+ }
+ else {
+ /* We consider a starting server as idle because we started it
+ * at least a cycle ago, and if it still hasn't finished starting
+ * then we're just going to swamp things worse by forking more.
+ * So we hopefully won't need to fork more if we count it.
+ * This depends on the ordering of SERVER_READY and SERVER_STARTING.
+ */
+ if (status <= SERVER_READY) {
+ ++ idle_count;
+ /* always kill the highest numbered child if we have to...
+ * no really well thought out reason ... other than observing
+ * the server behaviour under linux where lower numbered children
+ * tend to service more hits (and hence are more likely to have
+ * their data in cpu caches).
+ */
+ to_kill = i;
+ }
+
+ ++total_non_dead;
+ last_non_dead = i;
+ }
+ }
+ ap_max_daemons_limit = last_non_dead + 1;
+ if (idle_count > ap_daemons_max_free) {
+ /* kill off one child... we use the pod because that'll cause it to
+ * shut down gracefully, in case it happened to pick up a request
+ * while we were counting
+ */
+ ap_mpm_pod_signal(pod);
+ idle_spawn_rate = 1;
+ }
+ else if (idle_count < ap_daemons_min_free) {
+ /* terminate the free list */
+ if (free_length == 0) {
+ /* only report this condition once */
+ static int reported = 0;
+
+ if (!reported) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf,
+ "server reached MaxClients setting, consider"
+ " raising the MaxClients setting");
+ reported = 1;
+ }
+ idle_spawn_rate = 1;
+ }
+ else {
+ if (idle_spawn_rate >= 8) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "server seems busy, (you may need "
+ "to increase StartServers, or Min/MaxSpareServers), "
+ "spawning %d children, there are %d idle, and "
+ "%d total children", idle_spawn_rate,
+ idle_count, total_non_dead);
+ }
+ for (i = 0; i < free_length; ++i) {
+#ifdef TPF
+ if (make_child(ap_server_conf, free_slots[i]) == -1) {
+ if(free_length == 1) {
+ shutdown_pending = 1;
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, ap_server_conf,
+ "No active child processes: shutting down");
+ }
+ }
+#else
+ make_child(ap_server_conf, free_slots[i]);
+#endif /* TPF */
+ }
+ /* the next time around we want to spawn twice as many if this
+ * wasn't good enough, but not if we've just done a graceful
+ */
+ if (hold_off_on_exponential_spawning) {
+ --hold_off_on_exponential_spawning;
+ }
+ else if (idle_spawn_rate < MAX_SPAWN_RATE) {
+ idle_spawn_rate *= 2;
+ }
+ }
+ }
+ else {
+ idle_spawn_rate = 1;
+ }
+}
+
+/*****************************************************************
+ * Executive routines.
+ */
+
+int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
+{
+ int index;
+ int remaining_children_to_start;
+ apr_status_t rv;
+
+ ap_log_pid(pconf, ap_pid_fname);
+
+ first_server_limit = server_limit;
+ if (changed_limit_at_restart) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "WARNING: Attempt to change ServerLimit "
+ "ignored during restart");
+ changed_limit_at_restart = 0;
+ }
+
+ /* Initialize cross-process accept lock */
+ ap_lock_fname = apr_psprintf(_pconf, "%s.%" APR_PID_T_FMT,
+ ap_server_root_relative(_pconf, ap_lock_fname),
+ ap_my_pid);
+
+ rv = apr_proc_mutex_create(&accept_mutex, ap_lock_fname,
+ ap_accept_lock_mech, _pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
+ "Couldn't create accept lock (%s) (%d)",
+ ap_lock_fname, ap_accept_lock_mech);
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+
+#if APR_USE_SYSVSEM_SERIALIZE
+ if (ap_accept_lock_mech == APR_LOCK_DEFAULT ||
+ ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
+#else
+ if (ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
+#endif
+ rv = unixd_set_proc_mutex_perms(accept_mutex);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
+ "Couldn't set permissions on cross-process lock; "
+ "check User and Group directives");
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+ }
+
+ if (!is_graceful) {
+ if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+ /* fix the generation number in the global score; we just got a new,
+ * cleared scoreboard
+ */
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+ }
+
+ set_signals();
+
+ if (one_process) {
+ AP_MONCONTROL(1);
+ make_child(ap_server_conf, 0);
+ }
+ else {
+ if (ap_daemons_max_free < ap_daemons_min_free + 1) /* Don't thrash... */
+ ap_daemons_max_free = ap_daemons_min_free + 1;
+
+ /* If we're doing a graceful_restart then we're going to see a lot
+ * of children exiting immediately when we get into the main loop
+ * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty
+ * rapidly... and for each one that exits we'll start a new one until
+ * we reach at least daemons_min_free. But we may be permitted to
+ * start more than that, so we'll just keep track of how many we're
+ * supposed to start up without the 1 second penalty between each fork.
+ */
+ remaining_children_to_start = ap_daemons_to_start;
+ if (remaining_children_to_start > ap_daemons_limit) {
+ remaining_children_to_start = ap_daemons_limit;
+ }
+ if (!is_graceful) {
+ startup_children(remaining_children_to_start);
+ remaining_children_to_start = 0;
+ }
+ else {
+ /* give the system some time to recover before kicking into
+ * exponential mode
+ */
+ hold_off_on_exponential_spawning = 10;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+#ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "AcceptMutex: %s (default: %s)",
+ apr_proc_mutex_name(accept_mutex),
+ apr_proc_mutex_defname());
+#endif
+ restart_pending = shutdown_pending = 0;
+
+ mpm_state = AP_MPMQ_RUNNING;
+
+ while (!restart_pending && !shutdown_pending) {
+ int child_slot;
+ apr_exit_why_e exitwhy;
+ int status, processed_status;
+ /* this is a memory leak, but I'll fix it later. */
+ apr_proc_t pid;
+
+ ap_wait_or_timeout(&exitwhy, &status, &pid, pconf);
+
+ /* XXX: if it takes longer than 1 second for all our children
+ * to start up and get into IDLE state then we may spawn an
+ * extra child
+ */
+ if (pid.pid != -1) {
+ processed_status = ap_process_child_status(&pid, exitwhy, status);
+ if (processed_status == APEXIT_CHILDFATAL) {
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+
+ /* non-fatal death... note that it's gone in the scoreboard. */
+ child_slot = find_child_by_pid(&pid);
+ if (child_slot >= 0) {
+ (void) ap_update_child_status_from_indexes(child_slot, 0, SERVER_DEAD,
+ (request_rec *) NULL);
+ if (processed_status == APEXIT_CHILDSICK) {
+ /* child detected a resource shortage (E[NM]FILE, ENOBUFS, etc)
+ * cut the fork rate to the minimum
+ */
+ idle_spawn_rate = 1;
+ }
+ else if (remaining_children_to_start
+ && child_slot < ap_daemons_limit) {
+ /* we're still doing a 1-for-1 replacement of dead
+ * children with new children
+ */
+ make_child(ap_server_conf, child_slot);
+ --remaining_children_to_start;
+ }
+#if APR_HAS_OTHER_CHILD
+ }
+ else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, status) == APR_SUCCESS) {
+ /* handled */
+#endif
+ }
+ else if (is_graceful) {
+ /* Great, we've probably just lost a slot in the
+ * scoreboard. Somehow we don't know about this
+ * child.
+ */
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, ap_server_conf,
+ "long lost child came home! (pid %ld)", (long)pid.pid);
+ }
+ /* Don't perform idle maintenance when a child dies,
+ * only do it when there's a timeout. Remember only a
+ * finite number of children can die, and it's pretty
+ * pathological for a lot to die suddenly.
+ */
+ continue;
+ }
+ else if (remaining_children_to_start) {
+ /* we hit a 1 second timeout in which none of the previous
+ * generation of children needed to be reaped... so assume
+ * they're all done, and pick up the slack if any is left.
+ */
+ startup_children(remaining_children_to_start);
+ remaining_children_to_start = 0;
+ /* In any event we really shouldn't do the code below because
+ * few of the servers we just started are in the IDLE state
+ * yet, so we'd mistakenly create an extra server.
+ */
+ continue;
+ }
+
+ perform_idle_server_maintenance(pconf);
+#ifdef TPF
+ shutdown_pending = os_check_server(tpf_server_name);
+ ap_check_signals();
+ sleep(1);
+#endif /*TPF */
+ }
+ } /* one_process */
+
+ mpm_state = AP_MPMQ_STOPPING;
+
+ if (shutdown_pending && !is_graceful) {
+ /* Time to shut down:
+ * Kill child processes, tell them to call child_exit, etc...
+ */
+ if (unixd_killpg(getpgrp(), SIGTERM) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "killpg SIGTERM");
+ }
+ ap_reclaim_child_processes(1); /* Start with SIGTERM */
+
+ /* cleanup pid file on normal shutdown */
+ {
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+ if ( pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO,
+ 0, ap_server_conf,
+ "removed PID file %s (pid=%ld)",
+ pidfile, (long)getpid());
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught SIGTERM, shutting down");
+
+ return 1;
+ } else if (shutdown_pending) {
+ /* Time to perform a graceful shut down:
+ * Reap the inactive children, and ask the active ones
+ * to close their listeners, then wait until they are
+ * all done to exit.
+ */
+ int active_children;
+ apr_time_t cutoff = 0;
+
+ /* Stop listening */
+ ap_close_listeners();
+
+ /* kill off the idle ones */
+ ap_mpm_pod_killpg(pod, ap_max_daemons_limit);
+
+ /* Send SIGUSR1 to the active children */
+ active_children = 0;
+ for (index = 0; index < ap_daemons_limit; ++index) {
+ if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) {
+ /* Ask each child to close its listeners. */
+ kill(MPM_CHILD_PID(index), AP_SIG_GRACEFUL);
+ active_children++;
+ }
+ }
+
+ /* Allow each child which actually finished to exit */
+ ap_relieve_child_processes();
+
+ /* cleanup pid file */
+ {
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+ if ( pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO,
+ 0, ap_server_conf,
+ "removed PID file %s (pid=%ld)",
+ pidfile, (long)getpid());
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught " AP_SIG_GRACEFUL_STOP_STRING ", shutting down gracefully");
+
+ if (ap_graceful_shutdown_timeout) {
+ cutoff = apr_time_now() +
+ apr_time_from_sec(ap_graceful_shutdown_timeout);
+ }
+
+ /* Don't really exit until each child has finished */
+ shutdown_pending = 0;
+ do {
+ /* Pause for a second */
+ sleep(1);
+
+ /* Relieve any children which have now exited */
+ ap_relieve_child_processes();
+
+ active_children = 0;
+ for (index = 0; index < ap_daemons_limit; ++index) {
+ if (MPM_CHILD_PID(index) != 0) {
+ if (kill(MPM_CHILD_PID(index), 0) == 0) {
+ active_children = 1;
+ /* Having just one child is enough to stay around */
+ break;
+ }
+ }
+ }
+ } while (!shutdown_pending && active_children &&
+ (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff));
+
+ /* We might be here because we received SIGTERM, either
+ * way, try and make sure that all of our processes are
+ * really dead.
+ */
+ unixd_killpg(getpgrp(), SIGTERM);
+
+ return 1;
+ }
+
+ /* we've been told to restart */
+ apr_signal(SIGHUP, SIG_IGN);
+ apr_signal(AP_SIG_GRACEFUL, SIG_IGN);
+ if (one_process) {
+ /* not worth thinking about */
+ return 1;
+ }
+
+ /* advance to the next generation */
+ /* XXX: we really need to make sure this new generation number isn't in
+ * use by any of the children.
+ */
+ ++ap_my_generation;
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+
+ if (is_graceful) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "Graceful restart requested, doing restart");
+
+ /* kill off the idle ones */
+ ap_mpm_pod_killpg(pod, ap_max_daemons_limit);
+
+ /* This is mostly for debugging... so that we know what is still
+ * gracefully dealing with existing request. This will break
+ * in a very nasty way if we ever have the scoreboard totally
+ * file-based (no shared memory)
+ */
+ for (index = 0; index < ap_daemons_limit; ++index) {
+ if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) {
+ ap_scoreboard_image->servers[index][0].status = SERVER_GRACEFUL;
+ /* Ask each child to close its listeners.
+ *
+ * NOTE: we use the scoreboard, because if we send SIGUSR1
+ * to every process in the group, this may include CGI's,
+ * piped loggers, etc. They almost certainly won't handle
+ * it gracefully.
+ */
+ kill(ap_scoreboard_image->parent[index].pid, AP_SIG_GRACEFUL);
+ }
+ }
+ }
+ else {
+ /* Kill 'em off */
+ if (unixd_killpg(getpgrp(), SIGHUP) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "killpg SIGHUP");
+ }
+ ap_reclaim_child_processes(0); /* Not when just starting up */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "SIGHUP received. Attempting to restart");
+ }
+
+ return 0;
+}
+
+/* This really should be a post_config hook, but the error log is already
+ * redirected by that point, so we need to do this in the open_logs phase.
+ */
+static int prefork_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t rv;
+
+ pconf = p;
+ ap_server_conf = s;
+
+ if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0,
+ NULL, "no listening sockets available, shutting down");
+ return DONE;
+ }
+
+ if ((rv = ap_mpm_pod_open(pconf, &pod))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_STARTUP, rv, NULL,
+ "Could not open pipe-of-death.");
+ return DONE;
+ }
+ return OK;
+}
+
+static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ static int restart_num = 0;
+ int no_detach, debug, foreground;
+ apr_status_t rv;
+
+ mpm_state = AP_MPMQ_STARTING;
+
+ debug = ap_exists_config_define("DEBUG");
+
+ if (debug) {
+ foreground = one_process = 1;
+ no_detach = 0;
+ }
+ else
+ {
+ no_detach = ap_exists_config_define("NO_DETACH");
+ one_process = ap_exists_config_define("ONE_PROCESS");
+ foreground = ap_exists_config_define("FOREGROUND");
+ }
+
+ /* sigh, want this only the second time around */
+ if (restart_num++ == 1) {
+ is_graceful = 0;
+
+ if (!one_process && !foreground) {
+ rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND
+ : APR_PROC_DETACH_DAEMONIZE);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "apr_proc_detach failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ parent_pid = ap_my_pid = getpid();
+ }
+
+ unixd_pre_config(ptemp);
+ ap_listen_pre_config();
+ ap_daemons_to_start = DEFAULT_START_DAEMON;
+ ap_daemons_min_free = DEFAULT_MIN_FREE_DAEMON;
+ ap_daemons_max_free = DEFAULT_MAX_FREE_DAEMON;
+ ap_daemons_limit = server_limit;
+ ap_pid_fname = DEFAULT_PIDLOG;
+ ap_lock_fname = DEFAULT_LOCKFILE;
+ ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
+ ap_extended_status = 0;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+
+ apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir));
+
+ return OK;
+}
+
+static void prefork_hooks(apr_pool_t *p)
+{
+ /* The prefork open_logs phase must run before the core's, or stderr
+ * will be redirected to a file, and the messages won't print to the
+ * console.
+ */
+ static const char *const aszSucc[] = {"core.c", NULL};
+
+#ifdef AUX3
+ (void) set42sig();
+#endif
+
+ ap_hook_open_logs(prefork_open_logs, NULL, aszSucc, APR_HOOK_MIDDLE);
+ /* we need to set the MPM state before other pre-config hooks use MPM query
+ * to retrieve it, so register as REALLY_FIRST
+ */
+ ap_hook_pre_config(prefork_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_to_start = atoi(arg);
+ return NULL;
+}
+
+static const char *set_min_free_servers(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_min_free = atoi(arg);
+ if (ap_daemons_min_free <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: detected MinSpareServers set to non-positive.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Resetting to 1 to avoid almost certain Apache failure.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Please read the documentation.");
+ ap_daemons_min_free = 1;
+ }
+
+ return NULL;
+}
+
+static const char *set_max_free_servers(cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_max_free = atoi(arg);
+ return NULL;
+}
+
+static const char *set_max_clients (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_limit = atoi(arg);
+ if (ap_daemons_limit > server_limit) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients of %d exceeds ServerLimit value "
+ "of %d servers,", ap_daemons_limit, server_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering MaxClients to %d. To increase, please "
+ "see the ServerLimit", server_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " directive.");
+ ap_daemons_limit = server_limit;
+ }
+ else if (ap_daemons_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require MaxClients > 0, setting to 1");
+ ap_daemons_limit = 1;
+ }
+ return NULL;
+}
+
+static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ int tmp_server_limit;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ tmp_server_limit = atoi(arg);
+ /* you cannot change ServerLimit across a restart; ignore
+ * any such attempts
+ */
+ if (first_server_limit &&
+ tmp_server_limit != server_limit) {
+ /* how do we log a message? the error log is a bit bucket at this
+ * point; we'll just have to set a flag so that ap_mpm_run()
+ * logs a warning later
+ */
+ changed_limit_at_restart = 1;
+ return NULL;
+ }
+ server_limit = tmp_server_limit;
+
+ if (server_limit > MAX_SERVER_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ServerLimit of %d exceeds compile time limit "
+ "of %d servers,", server_limit, MAX_SERVER_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ServerLimit to %d.", MAX_SERVER_LIMIT);
+ server_limit = MAX_SERVER_LIMIT;
+ }
+ else if (server_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ServerLimit > 0, setting to 1");
+ server_limit = 1;
+ }
+ return NULL;
+}
+
+static const command_rec prefork_cmds[] = {
+UNIX_DAEMON_COMMANDS,
+LISTEN_COMMANDS,
+AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF,
+ "Number of child processes launched at server startup"),
+AP_INIT_TAKE1("MinSpareServers", set_min_free_servers, NULL, RSRC_CONF,
+ "Minimum number of idle children, to handle request spikes"),
+AP_INIT_TAKE1("MaxSpareServers", set_max_free_servers, NULL, RSRC_CONF,
+ "Maximum number of idle children"),
+AP_INIT_TAKE1("MaxClients", set_max_clients, NULL, RSRC_CONF,
+ "Maximum number of children alive at the same time"),
+AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF,
+ "Maximum value of MaxClients for this run of Apache"),
+AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND,
+{ NULL }
+};
+
+module AP_MODULE_DECLARE_DATA mpm_prefork_module = {
+ MPM20_MODULE_STUFF,
+ ap_mpm_rewrite_args, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ prefork_cmds, /* command apr_table_t */
+ prefork_hooks, /* register hooks */
+};
diff --git a/server/mpm/winnt/Win9xConHook.c b/server/mpm/winnt/Win9xConHook.c
new file mode 100644
index 00000000..0dbcf80d
--- /dev/null
+++ b/server/mpm/winnt/Win9xConHook.c
@@ -0,0 +1,697 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef WIN32
+
+/*
+ * Win9xConHook.dll - a hook proc to clean up Win95/98 console behavior.
+ *
+ * It is well(?) documented by Microsoft that the Win9x HandlerRoutine
+ * hooked by the SetConsoleCtrlHandler never receives the CTRL_CLOSE_EVENT,
+ * CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT signals.
+ *
+ * It is possible to have a second window to monitor the WM_ENDSESSION
+ * message, but the close button still fails..
+ *
+ * There is a 16bit polling method for the close window option, but this
+ * is CPU intensive and requires thunking.
+ *
+ * Attempts to subclass the 'tty' console fail, since that message thread
+ * is actually owned by the 16 bit winoldap.mod process, although the
+ * window reports it is owned by the process/thread of the console app.
+ *
+ * Win9xConHook is thunks the WM_CLOSE and WM_ENDSESSION messages,
+ * first through a window hook procedure in the winoldap context, into
+ * a subclass WndProc, and on to a second hidden monitor window in the
+ * console application's context that dispatches them to the console app's
+ * registered HandlerRoutine.
+ */
+
+/* This debugging define turns on output to COM1, although you better init
+ * the port first (even using hyperterm). It's the only way to catch the
+ * goings on within system logoff/shutdown.
+ * #define DBG 1
+ */
+
+#include <windows.h>
+
+/* Variables used within any process context:
+ * hookwndmsg is a shared message to send Win9xConHook signals
+ * origwndprop is a wndprop atom to store the orig wndproc of the tty
+ * hookwndprop is a wndprop atom to store the hwnd of the hidden child
+ * is_service reminds us to unmark this process on the way out
+ */
+static UINT hookwndmsg = 0;
+static LPCTSTR origwndprop;
+static LPCTSTR hookwndprop;
+static BOOL is_service = 0;
+//static HMODULE hmodThis = NULL;
+
+/* Variables used within the tty processes' context:
+ * is_tty flags this process; -1 == unknown, 1 == if tty, 0 == if not
+ * hw_tty is the handle of the top level tty in this process context
+ * is_subclassed is toggled to assure DllMain removes the subclass on unload
+ * hmodLock is there to try and prevent this dll from being unloaded if the
+ * hook is removed while we are subclassed
+ */
+static int is_tty = -1;
+static HWND hwtty = NULL;
+static BOOL is_subclassed = 0;
+
+// This simply causes a gpfault the moment it tries to FreeLibrary within
+// the subclass procedure ... not good.
+//static HMODULE hmodLock = NULL;
+
+/* Variables used within the service or console app's context:
+ * hmodHook is the instance handle of this module for registering the hooks
+ * hhkGetMessage is the hook handle for catching Posted messages
+ * hhkGetMessage is the hook handle for catching Sent messages
+ * monitor_hwnd is the invisible window that handles our tty messages
+ * the tty_info strucure is used to pass args into the hidden window's thread
+ */
+static HMODULE hmodHook = NULL;
+static HHOOK hhkGetMessage;
+//static HHOOK hhkCallWndProc;
+static HWND monitor_hwnd = NULL;
+
+typedef struct {
+ PHANDLER_ROUTINE phandler;
+ HINSTANCE instance;
+ HWND parent;
+ INT type;
+ LPCSTR name;
+} tty_info;
+
+/* These are the GetWindowLong offsets for the hidden window's internal info
+ * gwltty_phandler is the address of the app's HandlerRoutine
+ * gwltty_ttywnd is the tty this hidden window will handle messages from
+ */
+#define gwltty_phandler 0
+#define gwltty_ttywnd 4
+
+/* Forward declaration prototypes for internal functions
+ */
+static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd);
+static LRESULT WINAPI RegisterWindows9xService(BOOL set_service);
+static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty);
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+static int HookProc(int hc, HWND *hwnd, UINT *msg,
+ WPARAM *wParam, LPARAM *lParam);
+#ifdef DBG
+static VOID DbgPrintf(LPTSTR fmt, ...);
+#endif
+
+
+/* DllMain is invoked by every process in the entire system that is hooked
+ * by our window hooks, notably the tty processes' context, and by the user
+ * who wants tty messages (the app). Keep it light and simple.
+ */
+BOOL __declspec(dllexport) APIENTRY DllMain(HINSTANCE hModule, ULONG ulReason,
+ LPVOID pctx)
+{
+ if (ulReason == DLL_PROCESS_ATTACH)
+ {
+ //hmodThis = hModule;
+ if (!hookwndmsg) {
+ origwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookOrigProc"));
+ hookwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookThunkWnd"));
+ hookwndmsg = RegisterWindowMessage("Win9xConHookMsg");
+ }
+#ifdef DBG
+// DbgPrintf("H ProcessAttach:%8.8x\r\n",
+// GetCurrentProcessId());
+#endif
+ }
+ else if ( ulReason == DLL_PROCESS_DETACH )
+ {
+#ifdef DBG
+// DbgPrintf("H ProcessDetach:%8.8x\r\n", GetCurrentProcessId());
+#endif
+ if (monitor_hwnd)
+ SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
+ if (is_subclassed)
+ SendMessage(hwtty, hookwndmsg, 0, (LPARAM)hwtty);
+ if (hmodHook)
+ {
+ if (hhkGetMessage) {
+ UnhookWindowsHookEx(hhkGetMessage);
+ hhkGetMessage = NULL;
+ }
+ //if (hhkCallWndProc) {
+ // UnhookWindowsHookEx(hhkCallWndProc);
+ // hhkCallWndProc = NULL;
+ //}
+ FreeLibrary(hmodHook);
+ hmodHook = NULL;
+ }
+ if (is_service)
+ RegisterWindows9xService(FALSE);
+ if (hookwndmsg) {
+ GlobalDeleteAtom((ATOM)origwndprop);
+ GlobalDeleteAtom((ATOM)hookwndprop);
+ hookwndmsg = 0;
+ }
+ }
+ return TRUE;
+}
+
+
+/* This group of functions are provided for the service/console app
+ * to register itself a HandlerRoutine to accept tty or service messages
+ */
+
+
+/* Exported function that creates a Win9x 'service' via a hidden window,
+ * that notifies the process via the HandlerRoutine messages.
+ */
+BOOL __declspec(dllexport) WINAPI Windows9xServiceCtrlHandler(
+ PHANDLER_ROUTINE phandler,
+ LPCSTR name)
+{
+ /* If we have not yet done so */
+ FreeConsole();
+
+ if (name)
+ {
+ DWORD tid;
+ HANDLE hThread;
+ /* NOTE: this is static so the module can continue to
+ * access these args while we go on to other things
+ */
+ static tty_info tty;
+ tty.instance = GetModuleHandle(NULL);
+ tty.phandler = phandler;
+ tty.parent = NULL;
+ tty.name = name;
+ tty.type = 2;
+ RegisterWindows9xService(TRUE);
+ hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread,
+ (LPVOID)&tty, 0, &tid);
+ if (hThread)
+ {
+ CloseHandle(hThread);
+ return TRUE;
+ }
+ }
+ else /* remove */
+ {
+ if (monitor_hwnd)
+ SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
+ RegisterWindows9xService(FALSE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/* Exported function that registers a HandlerRoutine to accept missing
+ * Win9x CTRL_EVENTs from the tty window, as NT does without a hassle.
+ * If add is 1 or 2, register the handler, if 2 also mark it as a service.
+ * If add is 0 deregister the handler, and unmark if a service
+ */
+BOOL __declspec(dllexport) WINAPI FixConsoleCtrlHandler(
+ PHANDLER_ROUTINE phandler,
+ INT add)
+{
+ HWND parent;
+
+ if (add)
+ {
+ HANDLE hThread;
+ DWORD tid;
+ /* NOTE: this is static so the module can continue to
+ * access these args while we go on to other things
+ */
+ static tty_info tty;
+ EnumWindows(EnumttyWindow, (LPARAM)&parent);
+ if (!parent) {
+#ifdef DBG
+ DbgPrintf("A EnumttyWindow failed (%d)\r\n", GetLastError());
+#endif
+ return FALSE;
+ }
+ tty.instance = GetModuleHandle(NULL);
+ tty.phandler = phandler;
+ tty.parent = parent;
+ tty.type = add;
+ if (add == 2) {
+ tty.name = "ttyService";
+ RegisterWindows9xService(TRUE);
+ }
+ else
+ tty.name = "ttyMonitor";
+ hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread,
+ (LPVOID)&tty, 0, &tid);
+ if (!hThread)
+ return FALSE;
+ CloseHandle(hThread);
+ hmodHook = LoadLibrary("Win9xConHook.dll");
+ if (hmodHook)
+ {
+ hhkGetMessage = SetWindowsHookEx(WH_GETMESSAGE,
+ (HOOKPROC)GetProcAddress(hmodHook, "GetMsgProc"), hmodHook, 0);
+ //hhkCallWndProc = SetWindowsHookEx(WH_CALLWNDPROC,
+ // (HOOKPROC)GetProcAddress(hmodHook, "CallWndProc"), hmodHook, 0);
+ }
+ return TRUE;
+ }
+ else /* remove */
+ {
+ if (monitor_hwnd) {
+ SendMessage(monitor_hwnd, WM_DESTROY, 0, 0);
+ }
+ if (hmodHook)
+ {
+ if (hhkGetMessage) {
+ UnhookWindowsHookEx(hhkGetMessage);
+ hhkGetMessage = NULL;
+ }
+ //if (hhkCallWndProc) {
+ // UnhookWindowsHookEx(hhkCallWndProc);
+ // hhkCallWndProc = NULL;
+ //}
+ FreeLibrary(hmodHook);
+ hmodHook = NULL;
+ }
+ if (is_service)
+ RegisterWindows9xService(FALSE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/* The following internal helpers are only used within the app's context
+ */
+
+/* ttyConsoleCreateThread is the process that runs within the user app's
+ * context. It creates and pumps the messages of a hidden monitor window,
+ * watching for messages from the system, or the associated subclassed tty
+ * window. Things can happen in our context that can't be done from the
+ * tty's context, and visa versa, so the subclass procedure and this hidden
+ * window work together to make it all happen.
+ */
+static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty)
+{
+ WNDCLASS wc;
+ MSG msg;
+ wc.style = CS_GLOBALCLASS;
+ wc.lpfnWndProc = ttyConsoleCtrlWndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 8;
+ wc.hInstance = NULL;
+ wc.hIcon = NULL;
+ wc.hCursor = NULL;
+ wc.hbrBackground = NULL;
+ wc.lpszMenuName = NULL;
+ if (((tty_info*)tty)->parent)
+ wc.lpszClassName = "ttyConHookChild";
+ else
+ wc.lpszClassName = "ApacheWin95ServiceMonitor";
+
+ if (!RegisterClass(&wc)) {
+#ifdef DBG
+ DbgPrintf("A proc %8.8x Error creating class %s (%d)\r\n",
+ GetCurrentProcessId(), wc.lpszClassName, GetLastError());
+#endif
+ return 0;
+ }
+
+ /* Create an invisible window */
+ monitor_hwnd = CreateWindow(wc.lpszClassName, ((tty_info*)tty)->name,
+ WS_OVERLAPPED & ~WS_VISIBLE,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ NULL, NULL,
+ ((tty_info*)tty)->instance, tty);
+
+ if (!monitor_hwnd) {
+#ifdef DBG
+ DbgPrintf("A proc %8.8x Error creating window %s %s (%d)\r\n",
+ GetCurrentProcessId(), wc.lpszClassName,
+ ((tty_info*)tty)->name, GetLastError());
+#endif
+ return 0;
+ }
+
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ /* Tag again as deleted, just in case we missed WM_DESTROY */
+ monitor_hwnd = NULL;
+ return 0;
+}
+
+
+/* This is the WndProc procedure for our invisible window.
+ * When our subclasssed tty window receives the WM_CLOSE, WM_ENDSESSION,
+ * or WM_QUERYENDSESSION messages, the message is dispatched to our hidden
+ * window (this message process), and we call the installed HandlerRoutine
+ * that was registered by the app.
+ */
+static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_CREATE)
+ {
+ tty_info *tty = (tty_info*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
+ SetWindowLong(hwnd, gwltty_phandler, (LONG)tty->phandler);
+ SetWindowLong(hwnd, gwltty_ttywnd, (LONG)tty->parent);
+#ifdef DBG
+ DbgPrintf("A proc %8.8x created %8.8x %s for tty wnd %8.8x\r\n",
+ GetCurrentProcessId(), hwnd,
+ tty->name, tty->parent);
+#endif
+ if (tty->parent) {
+ SetProp(tty->parent, hookwndprop, hwnd);
+ PostMessage(tty->parent, hookwndmsg,
+ tty->type, (LPARAM)tty->parent);
+ }
+ return 0;
+ }
+ else if (msg == WM_DESTROY)
+ {
+ HWND parent = (HWND)GetWindowLong(hwnd, gwltty_ttywnd);
+#ifdef DBG
+ DbgPrintf("A proc %8.8x destroyed %8.8x ttyConHookChild\r\n",
+ GetCurrentProcessId(), hwnd);
+#endif
+ if (parent) {
+ RemoveProp(parent, hookwndprop);
+ SendMessage(parent, hookwndmsg, 0, (LPARAM)parent);
+ }
+ monitor_hwnd = NULL;
+ }
+ else if (msg == WM_CLOSE)
+ {
+ PHANDLER_ROUTINE phandler =
+ (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
+ LRESULT rv = phandler(CTRL_CLOSE_EVENT);
+#ifdef DBG
+ DbgPrintf("A proc %8.8x invoked CTRL_CLOSE_EVENT "
+ "returning %d\r\n",
+ GetCurrentProcessId(), rv);
+#endif
+ if (rv)
+ return !rv;
+ }
+ else if ((msg == WM_QUERYENDSESSION) || (msg == WM_ENDSESSION))
+ {
+ if (lParam & ENDSESSION_LOGOFF)
+ {
+ PHANDLER_ROUTINE phandler =
+ (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
+ LRESULT rv = phandler(CTRL_LOGOFF_EVENT);
+#ifdef DBG
+ DbgPrintf("A proc %8.8x invoked CTRL_LOGOFF_EVENT "
+ "returning %d\r\n",
+ GetCurrentProcessId(), rv);
+#endif
+ if (rv)
+ return ((msg == WM_QUERYENDSESSION) ? rv : !rv);
+ }
+ else
+ {
+ PHANDLER_ROUTINE phandler =
+ (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler);
+ LRESULT rv = phandler(CTRL_SHUTDOWN_EVENT);
+#ifdef DBG
+ DbgPrintf("A proc %8.8x invoked CTRL_SHUTDOWN_EVENT "
+ "returning %d\r\n", GetCurrentProcessId(), rv);
+#endif
+ if (rv)
+ return ((msg == WM_QUERYENDSESSION) ? rv : !rv);
+ }
+ }
+ return (DefWindowProc(hwnd, msg, wParam, lParam));
+}
+
+
+/* The following internal helpers are invoked by the hooked tty and our app
+ */
+
+
+/* Register or deregister the current process as a Windows9x style service.
+ * Experience shows this call is ignored across processes, so the second
+ * arg to RegisterServiceProcess (process group id) is effectively useless.
+ */
+static LRESULT WINAPI RegisterWindows9xService(BOOL set_service)
+{
+ static HINSTANCE hkernel;
+ static DWORD (WINAPI *register_service_process)(DWORD, DWORD) = NULL;
+ BOOL rv;
+
+ if (set_service == is_service)
+ return 1;
+
+#ifdef DBG
+ DbgPrintf("R %s proc %8.8x as a service\r\n",
+ set_service ? "installing" : "removing",
+ GetCurrentProcessId());
+#endif
+
+ if (!register_service_process)
+ {
+ /* Obtain a handle to the kernel library */
+ hkernel = LoadLibrary("KERNEL32.DLL");
+ if (!hkernel)
+ return 0;
+
+ /* Find the RegisterServiceProcess function */
+ register_service_process = (DWORD (WINAPI *)(DWORD, DWORD))
+ GetProcAddress(hkernel, "RegisterServiceProcess");
+ if (register_service_process == NULL) {
+ FreeLibrary(hkernel);
+ return 0;
+ }
+ }
+
+ /* Register this process as a service */
+ rv = register_service_process(0, set_service != FALSE);
+ if (rv)
+ is_service = set_service;
+
+ if (!is_service)
+ {
+ /* Unload the kernel library */
+ FreeLibrary(hkernel);
+ register_service_process = NULL;
+ }
+ return rv;
+}
+
+
+/*
+ * This function only works when this process is the active process
+ * (e.g. once it is running a child process, it can no longer determine
+ * which console window is its own.)
+ */
+static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd)
+{
+ char tmp[8];
+ if (GetClassName(wnd, tmp, sizeof(tmp)) && !strcmp(tmp, "tty"))
+ {
+ DWORD wndproc, thisproc = GetCurrentProcessId();
+ GetWindowThreadProcessId(wnd, &wndproc);
+ if (wndproc == thisproc) {
+ *((HWND*)retwnd) = wnd;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/* The remaining code all executes --in the tty's own process context--
+ *
+ * That means special attention must be paid to what it's doing...
+ */
+
+/* Subclass message process for the tty window
+ *
+ * This code -handles- WM_CLOSE, WM_ENDSESSION and WM_QUERYENDSESSION
+ * by dispatching them to the window identified by the hookwndprop
+ * property atom set against our window. Messages are then dispatched
+ * to origwndprop property atom we set against the window when we
+ * injected this subclass. This trick did not work with simply a hook.
+ */
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ WNDPROC origproc = (WNDPROC) GetProp(hwnd, origwndprop);
+ if (!origproc)
+ return 0;
+
+ if (msg == WM_NCDESTROY
+ || (msg == hookwndmsg && !LOWORD(wParam) && (HWND)lParam == hwnd))
+ {
+ if (is_subclassed) {
+#ifdef DBG
+ DbgPrintf("W proc %08x hwnd:%08x Subclass removed\r\n",
+ GetCurrentProcessId(), hwnd);
+#endif
+ if (is_service)
+ RegisterWindows9xService(FALSE);
+ SetWindowLong(hwnd, GWL_WNDPROC, (LONG)origproc);
+ RemoveProp(hwnd, origwndprop);
+ RemoveProp(hwnd, hookwndprop);
+ is_subclassed = FALSE;
+ //if (hmodLock)
+ // FreeLibrary(hmodLock);
+ //hmodLock = NULL;
+ }
+ }
+ else if (msg == WM_CLOSE || msg == WM_ENDSESSION
+ || msg == WM_QUERYENDSESSION)
+ {
+ HWND child = (HWND)GetProp(hwnd, hookwndprop);
+ if (child) {
+#ifdef DBG
+ DbgPrintf("W proc %08x hwnd:%08x forwarded msg:%d\r\n",
+ GetCurrentProcessId(), hwnd, msg);
+#endif
+ return SendMessage(child, msg, wParam, lParam);
+ }
+ }
+ return CallWindowProc(origproc, hwnd, msg, wParam, lParam);
+}
+
+
+/* HookProc, once installed, is responsible for subclassing the system
+ * tty windows. It generally does nothing special itself, since
+ * research indicates that it cannot deal well with the messages we are
+ * interested in, that is, WM_CLOSE, WM_QUERYSHUTDOWN and WM_SHUTDOWN
+ * of the tty process.
+ *
+ * Respond and subclass only when a WM_NULL is received by the window.
+ */
+int HookProc(int hc, HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam)
+{
+ if (is_tty == -1 && *hwnd)
+ {
+ char ttybuf[8];
+ HWND htty;
+ hwtty = *hwnd;
+ while (htty = GetParent(hwtty))
+ hwtty = htty;
+ is_tty = (GetClassName(hwtty, ttybuf, sizeof(ttybuf))
+ && !strcmp(ttybuf, "tty"));
+#ifdef DBG
+ if (is_tty)
+ DbgPrintf("H proc %08x tracking hwnd %08x\r\n",
+ GetCurrentProcessId(), hwtty);
+#endif
+ }
+
+ if (*msg == hookwndmsg && *wParam && *lParam == (LPARAM)hwtty && is_tty)
+ {
+ WNDPROC origproc = (WNDPROC)GetWindowLong(hwtty, GWL_WNDPROC);
+ //char myname[MAX_PATH];
+ //if (GetModuleFileName(hmodThis, myname, sizeof(myname)))
+ // hmodLock = LoadLibrary(myname);
+ SetProp(hwtty, origwndprop, origproc);
+ SetWindowLong(hwtty, GWL_WNDPROC, (LONG)WndProc);
+ is_subclassed = TRUE;
+#ifdef DBG
+ DbgPrintf("H proc %08x hwnd:%08x Subclassed\r\n",
+ GetCurrentProcessId(), hwtty);
+#endif
+ if (LOWORD(*wParam) == 2)
+ RegisterWindows9xService(TRUE);
+ }
+
+ return -1;
+}
+
+
+/*
+ * PostMessage Hook:
+ */
+LRESULT __declspec(dllexport) CALLBACK GetMsgProc(INT hc, WPARAM wParam,
+ LPARAM lParam)
+{
+ PMSG pmsg;
+
+ pmsg = (PMSG)lParam;
+
+ if (pmsg) {
+ int rv = HookProc(hc, &pmsg->hwnd, &pmsg->message,
+ &pmsg->wParam, &pmsg->lParam);
+ if (rv != -1)
+ return rv;
+ }
+ /*
+ * CallNextHookEx apparently ignores the hhook argument, so pass NULL
+ */
+ return CallNextHookEx(NULL, hc, wParam, lParam);
+}
+
+
+/*
+ * SendMessage Hook:
+ */
+LRESULT __declspec(dllexport) CALLBACK CallWndProc(INT hc, WPARAM wParam,
+ LPARAM lParam)
+{
+ PCWPSTRUCT pcwps = (PCWPSTRUCT)lParam;
+
+ if (pcwps) {
+ int rv = HookProc(hc, &pcwps->hwnd, &pcwps->message,
+ &pcwps->wParam, &pcwps->lParam);
+ if (rv != -1)
+ return rv;
+ }
+ /*
+ * CallNextHookEx apparently ignores the hhook argument, so pass NULL
+ */
+ return CallNextHookEx(NULL, hc, wParam, lParam);
+}
+
+
+#ifdef DBG
+VOID DbgPrintf(
+ LPTSTR fmt,
+ ...
+ )
+{
+ static HANDLE mutex;
+ va_list marker;
+ TCHAR szBuf[256];
+ DWORD t;
+ HANDLE gDbgOut;
+
+ va_start(marker, fmt);
+ wvsprintf(szBuf, fmt, marker);
+ va_end(marker);
+
+ if (!mutex)
+ mutex = CreateMutex(NULL, FALSE, "Win9xConHookDbgOut");
+ WaitForSingleObject(mutex, INFINITE);
+ gDbgOut = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0,
+ NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL);
+ WriteFile(gDbgOut, szBuf, strlen(szBuf), &t, NULL);
+ CloseHandle(gDbgOut);
+ ReleaseMutex(mutex);
+}
+#endif
+
+#endif /* WIN32 */
diff --git a/server/mpm/winnt/Win9xConHook.def b/server/mpm/winnt/Win9xConHook.def
new file mode 100644
index 00000000..85ec1664
--- /dev/null
+++ b/server/mpm/winnt/Win9xConHook.def
@@ -0,0 +1,10 @@
+LIBRARY Win9xConHook
+
+EXETYPE WINDOWS
+
+EXPORTS
+ DllMain
+ GetMsgProc
+ CallWndProc
+ FixConsoleCtrlHandler
+ Windows9xServiceCtrlHandler
diff --git a/server/mpm/winnt/Win9xConHook.dsp b/server/mpm/winnt/Win9xConHook.dsp
new file mode 100644
index 00000000..77cbacfc
--- /dev/null
+++ b/server/mpm/winnt/Win9xConHook.dsp
@@ -0,0 +1,103 @@
+# Microsoft Developer Studio Project File - Name="Win9xConHook" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=Win9xConHook - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "Win9xConHook.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "Win9xConHook.mak" CFG="Win9xConHook - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "Win9xConHook - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "Win9xConHook - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "Win9xConHook - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir ".\Release"
+# PROP BASE Intermediate_Dir ".\Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "SHARED_MODULE" /Fd"Release\Win9xConHook" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "NDEBUG"
+# ADD RSC /l 0x809 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /base:"0x1c0f0000"
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /base:"0x1c0f0000" /opt:ref
+
+!ELSEIF "$(CFG)" == "Win9xConHook - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "SHARED_MODULE" /Fd"Debug\Win9xConHook" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x809 /d "_DEBUG"
+# ADD RSC /l 0x809 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /debug /base:"0x1c0f0000"
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /debug /base:"0x1c0f0000"
+
+!ENDIF
+
+# Begin Target
+
+# Name "Win9xConHook - Win32 Release"
+# Name "Win9xConHook - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\Win9xConHook.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\Win9xConHook.def
+# End Source File
+# Begin Source File
+
+SOURCE=.\Win9xConHook.h
+# End Source File
+# End Target
+# End Project
diff --git a/server/mpm/winnt/Win9xConHook.h b/server/mpm/winnt/Win9xConHook.h
new file mode 100644
index 00000000..8b42da57
--- /dev/null
+++ b/server/mpm/winnt/Win9xConHook.h
@@ -0,0 +1,66 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file Win9xConHook.h
+ * @brief ???
+ *
+ * @addtogroup APACHE_MPM_WINNT
+ * @{
+ */
+
+#ifndef AP_WIN9XCONHOOK_H
+#define AP_WIN9XCONHOOK_H
+
+#ifdef WIN32
+
+/* Windows9xServiceCtrlHandler registers a handler routine, frees the
+ * console window, and registers this process as a service in Win9x.
+ * It creats a hidden window of class "ApacheWin95ServiceMonitor"
+ * and titled by the name passed, which passes the WM_SHUTDOWN message
+ * through the given HandlerRoutine's CTRL_SHUTDOWN event.
+ * Call with name of NULL to remove the Service handler.
+ */
+BOOL WINAPI Windows9xServiceCtrlHandler(PHANDLER_ROUTINE phandler, LPCSTR name);
+
+
+/* FixConsoleControlHandler registers a handler routine with the
+ * Win9xConHook.dll, creating a hidden window and forwarding the
+ * WM_ENDSESSION and WM_CLOSE messages to the given HandlerRoutine
+ * as CTRL_SHUTDOWN_EVENT, CTRL_LOGOFF_EVENT and CTRL_CLOSE_EVENT.
+ * The application should still use SetConsoleCtrlHandler to grab
+ * the CTRL_BREAK_EVENT and CTRL_C_EVENT, if desired.
+ */
+BOOL WINAPI FixConsoleCtrlHandler(PHANDLER_ROUTINE phandler, BOOL add);
+
+
+/*
+ * Exported PostMessage Hook, never use this directly:
+ *
+ * LRESULT CALLBACK GetMsgProc(INT hc, WPARAM wParam, LPARAM lParam);
+ */
+
+
+/*
+ * Exported SendMessage Hook, never use this directly:
+ *
+ * LRESULT CALLBACK CallWndProc(INT hc, WPARAM wParam, LPARAM lParam);
+ */
+
+#endif /* WIN32 */
+
+#endif AP_WIN9XCONHOOK_H
+/** @} */
diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c
new file mode 100644
index 00000000..8996bfd8
--- /dev/null
+++ b/server/mpm/winnt/child.c
@@ -0,0 +1,1157 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef WIN32
+
+#define CORE_PRIVATE
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h" /* for read_config */
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "apr_portable.h"
+#include "apr_thread_proc.h"
+#include "apr_getopt.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_shm.h"
+#include "apr_thread_mutex.h"
+#include "ap_mpm.h"
+#include "ap_config.h"
+#include "ap_listen.h"
+#include "mpm_default.h"
+#include "mpm_winnt.h"
+#include "mpm_common.h"
+#include <malloc.h>
+#include "apr_atomic.h"
+
+/* shared with mpm_winnt.c */
+extern DWORD my_pid;
+
+/* used by parent to signal the child to start and exit */
+/* shared with mpm_winnt.c, but should be private to child.c */
+apr_proc_mutex_t *start_mutex;
+HANDLE exit_event;
+
+/* child_main() should never need to modify is_graceful!?! */
+extern int volatile is_graceful;
+
+/* Queue for managing the passing of COMP_CONTEXTs between
+ * the accept and worker threads.
+ */
+static apr_pool_t *pchild;
+static int shutdown_in_progress = 0;
+static int workers_may_exit = 0;
+static unsigned int g_blocked_threads = 0;
+static HANDLE max_requests_per_child_event;
+
+static apr_thread_mutex_t *child_lock;
+static apr_thread_mutex_t *qlock;
+static PCOMP_CONTEXT qhead = NULL;
+static PCOMP_CONTEXT qtail = NULL;
+static int num_completion_contexts = 0;
+static int max_num_completion_contexts = 0;
+static HANDLE ThreadDispatchIOCP = NULL;
+static HANDLE qwait_event = NULL;
+
+
+void mpm_recycle_completion_context(PCOMP_CONTEXT context)
+{
+ /* Recycle the completion context.
+ * - clear the ptrans pool
+ * - put the context on the queue to be consumed by the accept thread
+ * Note:
+ * context->accept_socket may be in a disconnected but reusable
+ * state so -don't- close it.
+ */
+ if (context) {
+ apr_pool_clear(context->ptrans);
+ context->next = NULL;
+ ResetEvent(context->Overlapped.hEvent);
+ apr_thread_mutex_lock(qlock);
+ if (qtail) {
+ qtail->next = context;
+ } else {
+ qhead = context;
+ SetEvent(qwait_event);
+ }
+ qtail = context;
+ apr_thread_mutex_unlock(qlock);
+ }
+}
+
+PCOMP_CONTEXT mpm_get_completion_context(void)
+{
+ apr_status_t rv;
+ PCOMP_CONTEXT context = NULL;
+
+ while (1) {
+ /* Grab a context off the queue */
+ apr_thread_mutex_lock(qlock);
+ if (qhead) {
+ context = qhead;
+ qhead = qhead->next;
+ if (!qhead)
+ qtail = NULL;
+ } else {
+ ResetEvent(qwait_event);
+ }
+ apr_thread_mutex_unlock(qlock);
+
+ if (!context) {
+ /* We failed to grab a context off the queue, consider allocating
+ * a new one out of the child pool. There may be up to
+ * (ap_threads_per_child + num_listeners) contexts in the system
+ * at once.
+ */
+ if (num_completion_contexts >= max_num_completion_contexts) {
+ /* All workers are busy, need to wait for one */
+ static int reported = 0;
+ if (!reported) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf,
+ "Server ran out of threads to serve requests. Consider "
+ "raising the ThreadsPerChild setting");
+ reported = 1;
+ }
+
+ /* Wait for a worker to free a context. Once per second, give
+ * the caller a chance to check for shutdown. If the wait
+ * succeeds, get the context off the queue. It must be available,
+ * since there's only one consumer.
+ */
+ rv = WaitForSingleObject(qwait_event, 1000);
+ if (rv == WAIT_OBJECT_0)
+ continue;
+ else /* Hopefully, WAIT_TIMEOUT */
+ return NULL;
+ } else {
+ /* Allocate another context.
+ * Note:
+ * Multiple failures in the next two steps will cause the pchild pool
+ * to 'leak' storage. I don't think this is worth fixing...
+ */
+ apr_allocator_t *allocator;
+
+ apr_thread_mutex_lock(child_lock);
+ context = (PCOMP_CONTEXT) apr_pcalloc(pchild, sizeof(COMP_CONTEXT));
+
+ context->Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (context->Overlapped.hEvent == NULL) {
+ /* Hopefully this is a temporary condition ... */
+ ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_os_error(), ap_server_conf,
+ "mpm_get_completion_context: CreateEvent failed.");
+
+ apr_thread_mutex_unlock(child_lock);
+ return NULL;
+ }
+
+ /* Create the tranaction pool */
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ rv = apr_pool_create_ex(&context->ptrans, pchild, NULL, allocator);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_WARNING, rv, ap_server_conf,
+ "mpm_get_completion_context: Failed to create the transaction pool.");
+ CloseHandle(context->Overlapped.hEvent);
+
+ apr_thread_mutex_unlock(child_lock);
+ return NULL;
+ }
+ apr_allocator_owner_set(allocator, context->ptrans);
+ apr_pool_tag(context->ptrans, "transaction");
+
+ context->accept_socket = INVALID_SOCKET;
+ context->ba = apr_bucket_alloc_create(pchild);
+ apr_atomic_inc32(&num_completion_contexts);
+
+ apr_thread_mutex_unlock(child_lock);
+ break;
+ }
+ } else {
+ /* Got a context from the queue */
+ break;
+ }
+ }
+
+ return context;
+}
+
+apr_status_t mpm_post_completion_context(PCOMP_CONTEXT context,
+ io_state_e state)
+{
+ LPOVERLAPPED pOverlapped;
+ if (context)
+ pOverlapped = &context->Overlapped;
+ else
+ pOverlapped = NULL;
+
+ PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, state, pOverlapped);
+ return APR_SUCCESS;
+}
+
+
+/*
+ * find_ready_listener()
+ * Only used by Win9* and should go away when the win9*_accept() function is
+ * reimplemented using apr_poll().
+ */
+static ap_listen_rec *head_listener;
+
+static APR_INLINE ap_listen_rec *find_ready_listener(fd_set * main_fds)
+{
+ ap_listen_rec *lr;
+ SOCKET nsd;
+
+ lr = head_listener;
+ do {
+ apr_os_sock_get(&nsd, lr->sd);
+ if (FD_ISSET(nsd, main_fds)) {
+ head_listener = lr->next;
+ if (!head_listener) {
+ head_listener = ap_listeners;
+ }
+ return lr;
+ }
+ lr = lr->next;
+ if (!lr) {
+ lr = ap_listeners;
+ }
+ } while (lr != head_listener);
+ return NULL;
+}
+
+
+/* Windows 9x specific code...
+ * Accept processing for on Windows 95/98 uses a producer/consumer queue
+ * model. A single thread accepts connections and queues the accepted socket
+ * to the accept queue for consumption by a pool of worker threads.
+ *
+ * win9x_accept()
+ * The accept threads runs this function, which accepts connections off
+ * the network and calls add_job() to queue jobs to the accept_queue.
+ * add_job()/remove_job()
+ * Add or remove an accepted socket from the list of sockets
+ * connected to clients. allowed_globals.jobmutex protects
+ * against multiple concurrent access to the linked list of jobs.
+ * win9x_get_connection()
+ * Calls remove_job() to pull a job from the accept queue. All the worker
+ * threads block on remove_job.
+ */
+
+typedef struct joblist_s {
+ struct joblist_s *next;
+ SOCKET sock;
+} joblist;
+
+typedef struct globals_s {
+ HANDLE jobsemaphore;
+ joblist *jobhead;
+ joblist *jobtail;
+ apr_thread_mutex_t *jobmutex;
+ int jobcount;
+} globals;
+
+globals allowed_globals = {NULL, NULL, NULL, NULL, 0};
+
+#define MAX_SELECT_ERRORS 100
+
+
+static void add_job(SOCKET sock)
+{
+ joblist *new_job;
+
+ new_job = (joblist *) malloc(sizeof(joblist));
+ if (new_job == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Ouch! Out of memory in add_job()!");
+ return;
+ }
+ new_job->next = NULL;
+ new_job->sock = sock;
+
+ apr_thread_mutex_lock(allowed_globals.jobmutex);
+
+ if (allowed_globals.jobtail != NULL)
+ allowed_globals.jobtail->next = new_job;
+ allowed_globals.jobtail = new_job;
+ if (!allowed_globals.jobhead)
+ allowed_globals.jobhead = new_job;
+ allowed_globals.jobcount++;
+ ReleaseSemaphore(allowed_globals.jobsemaphore, 1, NULL);
+
+ apr_thread_mutex_unlock(allowed_globals.jobmutex);
+}
+
+
+static SOCKET remove_job(void)
+{
+ joblist *job;
+ SOCKET sock;
+
+ WaitForSingleObject(allowed_globals.jobsemaphore, INFINITE);
+ apr_thread_mutex_lock(allowed_globals.jobmutex);
+
+ if (shutdown_in_progress && !allowed_globals.jobhead) {
+ apr_thread_mutex_unlock(allowed_globals.jobmutex);
+ return (INVALID_SOCKET);
+ }
+ job = allowed_globals.jobhead;
+ ap_assert(job);
+ allowed_globals.jobhead = job->next;
+ if (allowed_globals.jobhead == NULL)
+ allowed_globals.jobtail = NULL;
+ apr_thread_mutex_unlock(allowed_globals.jobmutex);
+ sock = job->sock;
+ free(job);
+
+ return (sock);
+}
+
+
+static unsigned int __stdcall win9x_accept(void * dummy)
+{
+ struct timeval tv;
+ fd_set main_fds;
+ int wait_time = 1;
+ SOCKET csd;
+ SOCKET nsd = INVALID_SOCKET;
+ int count_select_errors = 0;
+ int rc;
+ int clen;
+ ap_listen_rec *lr;
+ struct fd_set listenfds;
+#if APR_HAVE_IPV6
+ struct sockaddr_in6 sa_client;
+#else
+ struct sockaddr_in sa_client;
+#endif
+
+ /* Setup the listeners
+ * ToDo: Use apr_poll()
+ */
+ FD_ZERO(&listenfds);
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ if (lr->sd != NULL) {
+ apr_os_sock_get(&nsd, lr->sd);
+ FD_SET(nsd, &listenfds);
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "Child %d: Listening on port %d.", my_pid, lr->bind_addr->port);
+ }
+ }
+
+ head_listener = ap_listeners;
+
+ while (!shutdown_in_progress) {
+ tv.tv_sec = wait_time;
+ tv.tv_usec = 0;
+ memcpy(&main_fds, &listenfds, sizeof(fd_set));
+
+ /* First parameter of select() is ignored on Windows */
+ rc = select(0, &main_fds, NULL, NULL, &tv);
+
+ if (rc == 0 || (rc == SOCKET_ERROR && APR_STATUS_IS_EINTR(apr_get_netos_error()))) {
+ count_select_errors = 0; /* reset count of errors */
+ continue;
+ }
+ else if (rc == SOCKET_ERROR) {
+ /* A "real" error occurred, log it and increment the count of
+ * select errors. This count is used to ensure we don't go into
+ * a busy loop of continuous errors.
+ */
+ ap_log_error(APLOG_MARK, APLOG_INFO, apr_get_netos_error(), ap_server_conf,
+ "select failed with error %d", apr_get_netos_error());
+ count_select_errors++;
+ if (count_select_errors > MAX_SELECT_ERRORS) {
+ shutdown_in_progress = 1;
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), ap_server_conf,
+ "Too many errors in select loop. Child process exiting.");
+ break;
+ }
+ } else {
+ ap_listen_rec *lr;
+
+ lr = find_ready_listener(&main_fds);
+ if (lr != NULL) {
+ /* fetch the native socket descriptor */
+ apr_os_sock_get(&nsd, lr->sd);
+ }
+ }
+
+ do {
+ clen = sizeof(sa_client);
+ csd = accept(nsd, (struct sockaddr *) &sa_client, &clen);
+ } while (csd < 0 && APR_STATUS_IS_EINTR(apr_get_netos_error()));
+
+ if (csd < 0) {
+ if (APR_STATUS_IS_ECONNABORTED(apr_get_netos_error())) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), ap_server_conf,
+ "accept: (client socket)");
+ }
+ }
+ else {
+ add_job(csd);
+ }
+ }
+ SetEvent(exit_event);
+ return 0;
+}
+
+
+static PCOMP_CONTEXT win9x_get_connection(PCOMP_CONTEXT context)
+{
+ apr_os_sock_info_t sockinfo;
+ int len, salen;
+#if APR_HAVE_IPV6
+ salen = sizeof(struct sockaddr_in6);
+#else
+ salen = sizeof(struct sockaddr_in);
+#endif
+
+
+ if (context == NULL) {
+ /* allocate the completion context and the transaction pool */
+ apr_allocator_t *allocator;
+ apr_thread_mutex_lock(child_lock);
+ context = apr_pcalloc(pchild, sizeof(COMP_CONTEXT));
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ apr_pool_create_ex(&context->ptrans, pchild, NULL, allocator);
+ apr_allocator_owner_set(allocator, context->ptrans);
+ apr_pool_tag(context->ptrans, "transaction");
+ context->ba = apr_bucket_alloc_create(pchild);
+ apr_thread_mutex_unlock(child_lock);
+ }
+
+ while (1) {
+ apr_pool_clear(context->ptrans);
+ context->accept_socket = remove_job();
+ if (context->accept_socket == INVALID_SOCKET) {
+ return NULL;
+ }
+ len = salen;
+ context->sa_server = apr_palloc(context->ptrans, len);
+ if (getsockname(context->accept_socket,
+ context->sa_server, &len)== SOCKET_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf,
+ "getsockname failed");
+ continue;
+ }
+ len = salen;
+ context->sa_client = apr_palloc(context->ptrans, len);
+ if ((getpeername(context->accept_socket,
+ context->sa_client, &len)) == SOCKET_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf,
+ "getpeername failed");
+ memset(&context->sa_client, '\0', sizeof(context->sa_client));
+ }
+ sockinfo.os_sock = &context->accept_socket;
+ sockinfo.local = context->sa_server;
+ sockinfo.remote = context->sa_client;
+ sockinfo.family = context->sa_server->sa_family;
+ sockinfo.type = SOCK_STREAM;
+ apr_os_sock_make(&context->sock, &sockinfo, context->ptrans);
+
+ return context;
+ }
+}
+
+
+/* Windows NT/2000 specific code...
+ * Accept processing for on Windows NT uses a producer/consumer queue
+ * model. An accept thread accepts connections off the network then issues
+ * PostQueuedCompletionStatus() to awake a thread blocked on the ThreadDispatch
+ * IOCompletionPort.
+ *
+ * winnt_accept()
+ * One or more accept threads run in this function, each of which accepts
+ * connections off the network and calls PostQueuedCompletionStatus() to
+ * queue an io completion packet to the ThreadDispatch IOCompletionPort.
+ * winnt_get_connection()
+ * Worker threads block on the ThreadDispatch IOCompletionPort awaiting
+ * connections to service.
+ */
+#define MAX_ACCEPTEX_ERR_COUNT 100
+static unsigned int __stdcall winnt_accept(void *lr_)
+{
+ ap_listen_rec *lr = (ap_listen_rec *)lr_;
+ apr_os_sock_info_t sockinfo;
+ PCOMP_CONTEXT context = NULL;
+ DWORD BytesRead;
+ SOCKET nlsd;
+ int rv, err_count = 0;
+#if APR_HAVE_IPV6
+ SOCKADDR_STORAGE ss_listen;
+ int namelen = sizeof(ss_listen);
+#endif
+
+ apr_os_sock_get(&nlsd, lr->sd);
+
+#if APR_HAVE_IPV6
+ if (getsockname(nlsd, (struct sockaddr *)&ss_listen, &namelen) == SOCKET_ERROR) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_netos_error(), ap_server_conf,
+ "winnt_accept: getsockname error on listening socket, is IPv6 available?");
+ return 1;
+ }
+#endif
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "Child %d: Starting thread to listen on port %d.", my_pid, lr->bind_addr->port);
+ while (!shutdown_in_progress) {
+ if (!context) {
+ context = mpm_get_completion_context();
+ if (!context) {
+ /* Temporary resource constraint? */
+ Sleep(0);
+ continue;
+ }
+ }
+
+ /* Create and initialize the accept socket */
+#if APR_HAVE_IPV6
+ if (context->accept_socket == INVALID_SOCKET) {
+ context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, IPPROTO_TCP);
+ context->socket_family = ss_listen.ss_family;
+ }
+ else if (context->socket_family != ss_listen.ss_family) {
+ closesocket(context->accept_socket);
+ context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, IPPROTO_TCP);
+ context->socket_family = ss_listen.ss_family;
+ }
+
+ if (context->accept_socket == INVALID_SOCKET) {
+ ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_netos_error(), ap_server_conf,
+ "winnt_accept: Failed to allocate an accept socket. "
+ "Temporary resource constraint? Try again.");
+ Sleep(100);
+ continue;
+ }
+#else
+ if (context->accept_socket == INVALID_SOCKET) {
+ context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (context->accept_socket == INVALID_SOCKET) {
+ /* Another temporary condition? */
+ ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_netos_error(), ap_server_conf,
+ "winnt_accept: Failed to allocate an accept socket. "
+ "Temporary resource constraint? Try again.");
+ Sleep(100);
+ continue;
+ }
+ }
+#endif
+ /* AcceptEx on the completion context. The completion context will be
+ * signaled when a connection is accepted.
+ */
+ if (!AcceptEx(nlsd, context->accept_socket,
+ context->buff,
+ 0,
+ PADDED_ADDR_SIZE,
+ PADDED_ADDR_SIZE,
+ &BytesRead,
+ &context->Overlapped)) {
+ rv = apr_get_netos_error();
+ if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) ||
+ (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) {
+ /* We can get here when:
+ * 1) the client disconnects early
+ * 2) TransmitFile does not properly recycle the accept socket (typically
+ * because the client disconnected)
+ * 3) there is VPN or Firewall software installed with buggy AcceptEx implementation
+ * 4) the webserver is using a dynamic address that has changed
+ */
+ ++err_count;
+ closesocket(context->accept_socket);
+ context->accept_socket = INVALID_SOCKET;
+ if (err_count > MAX_ACCEPTEX_ERR_COUNT) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,
+ "Child %d: Encountered too many errors accepting client connections. "
+ "Possible causes: dynamic address renewal, or incompatible VPN or firewall software. "
+ "Try using the Win32DisableAcceptEx directive.", my_pid);
+ err_count = 0;
+ }
+ continue;
+ }
+ else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) &&
+ (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) {
+ ++err_count;
+ if (err_count > MAX_ACCEPTEX_ERR_COUNT) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf,
+ "Child %d: Encountered too many errors accepting client connections. "
+ "Possible causes: Unknown. "
+ "Try using the Win32DisableAcceptEx directive.", my_pid);
+ err_count = 0;
+ }
+ closesocket(context->accept_socket);
+ context->accept_socket = INVALID_SOCKET;
+ continue;
+ }
+ err_count = 0;
+
+ /* Wait for pending i/o.
+ * Wake up once per second to check for shutdown .
+ * XXX: We should be waiting on exit_event instead of polling
+ */
+ while (1) {
+ rv = WaitForSingleObject(context->Overlapped.hEvent, 1000);
+ if (rv == WAIT_OBJECT_0) {
+ if (context->accept_socket == INVALID_SOCKET) {
+ /* socket already closed */
+ break;
+ }
+ if (!GetOverlappedResult((HANDLE)context->accept_socket,
+ &context->Overlapped,
+ &BytesRead, FALSE)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ apr_get_os_error(), ap_server_conf,
+ "winnt_accept: Asynchronous AcceptEx failed.");
+ closesocket(context->accept_socket);
+ context->accept_socket = INVALID_SOCKET;
+ }
+ break;
+ }
+ /* WAIT_TIMEOUT */
+ if (shutdown_in_progress) {
+ closesocket(context->accept_socket);
+ context->accept_socket = INVALID_SOCKET;
+ break;
+ }
+ }
+ if (context->accept_socket == INVALID_SOCKET) {
+ continue;
+ }
+ }
+ err_count = 0;
+ /* Inherit the listen socket settings. Required for
+ * shutdown() to work
+ */
+ if (setsockopt(context->accept_socket, SOL_SOCKET,
+ SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd,
+ sizeof(nlsd))) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf,
+ "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed.");
+ /* Not a failure condition. Keep running. */
+ }
+
+ /* Get the local & remote address */
+ GetAcceptExSockaddrs(context->buff,
+ 0,
+ PADDED_ADDR_SIZE,
+ PADDED_ADDR_SIZE,
+ &context->sa_server,
+ &context->sa_server_len,
+ &context->sa_client,
+ &context->sa_client_len);
+
+ sockinfo.os_sock = &context->accept_socket;
+ sockinfo.local = context->sa_server;
+ sockinfo.remote = context->sa_client;
+ sockinfo.family = context->sa_server->sa_family;
+ sockinfo.type = SOCK_STREAM;
+ apr_os_sock_make(&context->sock, &sockinfo, context->ptrans);
+
+ /* When a connection is received, send an io completion notification to
+ * the ThreadDispatchIOCP. This function could be replaced by
+ * mpm_post_completion_context(), but why do an extra function call...
+ */
+ PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_CONNECTION_ACCEPTED,
+ &context->Overlapped);
+ context = NULL;
+ }
+ if (!shutdown_in_progress) {
+ /* Yow, hit an irrecoverable error! Tell the child to die. */
+ SetEvent(exit_event);
+ }
+ ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf,
+ "Child %d: Accept thread exiting.", my_pid);
+ return 0;
+}
+
+
+static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context)
+{
+ int rc;
+ DWORD BytesRead;
+ LPOVERLAPPED pol;
+#ifdef _WIN64
+ ULONG_PTR CompKey;
+#else
+ DWORD CompKey;
+#endif
+
+ mpm_recycle_completion_context(context);
+
+ apr_atomic_inc32(&g_blocked_threads);
+ while (1) {
+ if (workers_may_exit) {
+ apr_atomic_dec32(&g_blocked_threads);
+ return NULL;
+ }
+ rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, &CompKey,
+ &pol, INFINITE);
+ if (!rc) {
+ rc = apr_get_os_error();
+ ap_log_error(APLOG_MARK,APLOG_DEBUG, rc, ap_server_conf,
+ "Child %d: GetQueuedComplationStatus returned %d", my_pid, rc);
+ continue;
+ }
+
+ switch (CompKey) {
+ case IOCP_CONNECTION_ACCEPTED:
+ context = CONTAINING_RECORD(pol, COMP_CONTEXT, Overlapped);
+ break;
+ case IOCP_SHUTDOWN:
+ apr_atomic_dec32(&g_blocked_threads);
+ return NULL;
+ default:
+ apr_atomic_dec32(&g_blocked_threads);
+ return NULL;
+ }
+ break;
+ }
+ apr_atomic_dec32(&g_blocked_threads);
+
+ return context;
+}
+
+
+/*
+ * worker_main()
+ * Main entry point for the worker threads. Worker threads block in
+ * win*_get_connection() awaiting a connection to service.
+ */
+static unsigned int __stdcall worker_main(void *thread_num_val)
+{
+ static int requests_this_child = 0;
+ PCOMP_CONTEXT context = NULL;
+ int thread_num = (int)thread_num_val;
+ ap_sb_handle_t *sbh;
+
+ while (1) {
+ conn_rec *c;
+ apr_int32_t disconnected;
+
+ ap_update_child_status_from_indexes(0, thread_num, SERVER_READY, NULL);
+
+ /* Grab a connection off the network */
+ if (use_acceptex) {
+ context = winnt_get_connection(context);
+ }
+ else {
+ context = win9x_get_connection(context);
+ }
+
+ if (!context) {
+ /* Time for the thread to exit */
+ break;
+ }
+
+ /* Have we hit MaxRequestPerChild connections? */
+ if (ap_max_requests_per_child) {
+ requests_this_child++;
+ if (requests_this_child > ap_max_requests_per_child) {
+ SetEvent(max_requests_per_child_event);
+ }
+ }
+
+ ap_create_sb_handle(&sbh, context->ptrans, 0, thread_num);
+ c = ap_run_create_connection(context->ptrans, ap_server_conf,
+ context->sock, thread_num, sbh,
+ context->ba);
+
+ if (c) {
+ ap_process_connection(c, context->sock);
+ apr_socket_opt_get(context->sock, APR_SO_DISCONNECTED,
+ &disconnected);
+ if (!disconnected) {
+ context->accept_socket = INVALID_SOCKET;
+ ap_lingering_close(c);
+ }
+ else if (!use_acceptex) {
+ /* If the socket is disconnected but we are not using acceptex,
+ * we cannot reuse the socket. Disconnected sockets are removed
+ * from the apr_socket_t struct by apr_sendfile() to prevent the
+ * socket descriptor from being inadvertently closed by a call
+ * to apr_socket_close(), so close it directly.
+ */
+ closesocket(context->accept_socket);
+ context->accept_socket = INVALID_SOCKET;
+ }
+ }
+ else {
+ /* ap_run_create_connection closes the socket on failure */
+ context->accept_socket = INVALID_SOCKET;
+ }
+ }
+
+ ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD,
+ (request_rec *) NULL);
+
+ return 0;
+}
+
+
+static void cleanup_thread(HANDLE *handles, int *thread_cnt, int thread_to_clean)
+{
+ int i;
+
+ CloseHandle(handles[thread_to_clean]);
+ for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++)
+ handles[i] = handles[i + 1];
+ (*thread_cnt)--;
+}
+
+
+/*
+ * child_main()
+ * Entry point for the main control thread for the child process.
+ * This thread creates the accept thread, worker threads and
+ * monitors the child process for maintenance and shutdown
+ * events.
+ */
+static void create_listener_thread()
+{
+ int tid;
+ int num_listeners = 0;
+ if (!use_acceptex) {
+ _beginthreadex(NULL, 0, win9x_accept,
+ NULL, 0, &tid);
+ } else {
+ /* Start an accept thread per listener
+ * XXX: Why would we have a NULL sd in our listeners?
+ */
+ ap_listen_rec *lr;
+
+ /* Number of completion_contexts allowed in the system is
+ * (ap_threads_per_child + num_listeners). We need the additional
+ * completion contexts to prevent server hangs when ThreadsPerChild
+ * is configured to something less than or equal to the number
+ * of listeners. This is not a usual case, but people have
+ * encountered it.
+ * */
+ for (lr = ap_listeners; lr ; lr = lr->next) {
+ num_listeners++;
+ }
+ max_num_completion_contexts = ap_threads_per_child + num_listeners;
+
+ /* Now start a thread per listener */
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ if (lr->sd != NULL) {
+ _beginthreadex(NULL, 1000, winnt_accept,
+ (void *) lr, 0, &tid);
+ }
+ }
+ }
+}
+
+
+void child_main(apr_pool_t *pconf)
+{
+ apr_status_t status;
+ apr_hash_t *ht;
+ ap_listen_rec *lr;
+ HANDLE child_events[2];
+ int threads_created = 0;
+ int listener_started = 0;
+ int tid;
+ HANDLE *child_handles;
+ int rv;
+ time_t end_time;
+ int i;
+ int cld;
+
+ apr_pool_create(&pchild, pconf);
+ apr_pool_tag(pchild, "pchild");
+
+ ap_run_child_init(pchild, ap_server_conf);
+ ht = apr_hash_make(pchild);
+
+ /* Initialize the child_events */
+ max_requests_per_child_event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!max_requests_per_child_event) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: Failed to create a max_requests event.", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+ child_events[0] = exit_event;
+ child_events[1] = max_requests_per_child_event;
+
+ allowed_globals.jobsemaphore = CreateSemaphore(NULL, 0, 1000000, NULL);
+ apr_thread_mutex_create(&allowed_globals.jobmutex,
+ APR_THREAD_MUTEX_DEFAULT, pchild);
+
+ /*
+ * Wait until we have permission to start accepting connections.
+ * start_mutex is used to ensure that only one child ever
+ * goes into the listen/accept loop at once.
+ */
+ status = apr_proc_mutex_lock(start_mutex);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, status, ap_server_conf,
+ "Child %d: Failed to acquire the start_mutex. Process will exit.", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Acquired the start mutex.", my_pid);
+
+ /*
+ * Create the worker thread dispatch IOCompletionPort
+ * on Windows NT/2000
+ */
+ if (use_acceptex) {
+ /* Create the worker thread dispatch IOCP */
+ ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
+ NULL,
+ 0,
+ 0); /* CONCURRENT ACTIVE THREADS */
+ apr_thread_mutex_create(&qlock, APR_THREAD_MUTEX_DEFAULT, pchild);
+ qwait_event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!qwait_event) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: Failed to create a qwait event.", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+ }
+
+ /*
+ * Create the pool of worker threads
+ */
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Starting %d worker threads.", my_pid, ap_threads_per_child);
+ child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child * sizeof(HANDLE));
+ apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild);
+
+ while (1) {
+ for (i = 0; i < ap_threads_per_child; i++) {
+ int *score_idx;
+ int status = ap_scoreboard_image->servers[0][i].status;
+ if (status != SERVER_GRACEFUL && status != SERVER_DEAD) {
+ continue;
+ }
+ ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL);
+ child_handles[i] = (HANDLE) _beginthreadex(NULL, (unsigned)ap_thread_stacksize,
+ worker_main, (void *) i, 0, &tid);
+ if (child_handles[i] == 0) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: _beginthreadex failed. Unable to create all worker threads. "
+ "Created %d of the %d threads requested with the ThreadsPerChild configuration directive.",
+ my_pid, threads_created, ap_threads_per_child);
+ ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
+ goto shutdown;
+ }
+ threads_created++;
+ /* Save the score board index in ht keyed to the thread handle. We need this
+ * when cleaning up threads down below...
+ */
+ apr_thread_mutex_lock(child_lock);
+ score_idx = apr_pcalloc(pchild, sizeof(int));
+ *score_idx = i;
+ apr_hash_set(ht, &child_handles[i], sizeof(HANDLE), score_idx);
+ apr_thread_mutex_unlock(child_lock);
+ }
+ /* Start the listener only when workers are available */
+ if (!listener_started && threads_created) {
+ create_listener_thread();
+ listener_started = 1;
+ winnt_mpm_state = AP_MPMQ_RUNNING;
+ }
+ if (threads_created == ap_threads_per_child) {
+ break;
+ }
+ /* Check to see if the child has been told to exit */
+ if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) {
+ break;
+ }
+ /* wait for previous generation to clean up an entry in the scoreboard */
+ apr_sleep(1 * APR_USEC_PER_SEC);
+ }
+
+ /* Wait for one of three events:
+ * exit_event:
+ * The exit_event is signaled by the parent process to notify
+ * the child that it is time to exit.
+ *
+ * max_requests_per_child_event:
+ * This event is signaled by the worker threads to indicate that
+ * the process has handled MaxRequestsPerChild connections.
+ *
+ * TIMEOUT:
+ * To do periodic maintenance on the server (check for thread exits,
+ * number of completion contexts, etc.)
+ *
+ * XXX: thread exits *aren't* being checked.
+ *
+ * XXX: other_child - we need the process handles to the other children
+ * in order to map them to apr_proc_other_child_read (which is not
+ * named well, it's more like a_p_o_c_died.)
+ *
+ * XXX: however - if we get a_p_o_c handle inheritance working, and
+ * the parent process creates other children and passes the pipes
+ * to our worker processes, then we have no business doing such
+ * things in the child_main loop, but should happen in master_main.
+ */
+ while (1) {
+#if !APR_HAS_OTHER_CHILD
+ rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, INFINITE);
+ cld = rv - WAIT_OBJECT_0;
+#else
+ rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, 1000);
+ cld = rv - WAIT_OBJECT_0;
+ if (rv == WAIT_TIMEOUT) {
+ apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING);
+ }
+ else
+#endif
+ if (rv == WAIT_FAILED) {
+ /* Something serious is wrong */
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: WAIT_FAILED -- shutting down server", my_pid);
+ break;
+ }
+ else if (cld == 0) {
+ /* Exit event was signaled */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Exit event signaled. Child process is ending.", my_pid);
+ break;
+ }
+ else {
+ /* MaxRequestsPerChild event set by the worker threads.
+ * Signal the parent to restart
+ */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Process exiting because it reached "
+ "MaxRequestsPerChild. Signaling the parent to "
+ "restart a new child process.", my_pid);
+ ap_signal_parent(SIGNAL_PARENT_RESTART);
+ break;
+ }
+ }
+
+ /*
+ * Time to shutdown the child process
+ */
+
+ shutdown:
+
+ winnt_mpm_state = AP_MPMQ_STOPPING;
+ /* Setting is_graceful will cause threads handling keep-alive connections
+ * to close the connection after handling the current request.
+ */
+ is_graceful = 1;
+
+ /* Close the listening sockets. Note, we must close the listeners
+ * before closing any accept sockets pending in AcceptEx to prevent
+ * memory leaks in the kernel.
+ */
+ for (lr = ap_listeners; lr ; lr = lr->next) {
+ apr_socket_close(lr->sd);
+ }
+
+ /* Shutdown listener threads and pending AcceptEx socksts
+ * but allow the worker threads to continue consuming from
+ * the queue of accepted connections.
+ */
+ shutdown_in_progress = 1;
+
+ Sleep(1000);
+
+ /* Tell the worker threads to exit */
+ workers_may_exit = 1;
+
+ /* Release the start_mutex to let the new process (in the restart
+ * scenario) a chance to begin accepting and servicing requests
+ */
+ rv = apr_proc_mutex_unlock(start_mutex);
+ if (rv == APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, rv, ap_server_conf,
+ "Child %d: Released the start mutex", my_pid);
+ }
+ else {
+ ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf,
+ "Child %d: Failure releasing the start mutex", my_pid);
+ }
+
+ /* Shutdown the worker threads */
+ if (!use_acceptex) {
+ for (i = 0; i < threads_created; i++) {
+ add_job(INVALID_SOCKET);
+ }
+ }
+ else { /* Windows NT/2000 */
+ /* Post worker threads blocked on the ThreadDispatch IOCompletion port */
+ while (g_blocked_threads > 0) {
+ ap_log_error(APLOG_MARK,APLOG_INFO, APR_SUCCESS, ap_server_conf,
+ "Child %d: %d threads blocked on the completion port", my_pid, g_blocked_threads);
+ for (i=g_blocked_threads; i > 0; i--) {
+ PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_SHUTDOWN, NULL);
+ }
+ Sleep(1000);
+ }
+ /* Empty the accept queue of completion contexts */
+ apr_thread_mutex_lock(qlock);
+ while (qhead) {
+ CloseHandle(qhead->Overlapped.hEvent);
+ closesocket(qhead->accept_socket);
+ qhead = qhead->next;
+ }
+ apr_thread_mutex_unlock(qlock);
+ }
+
+ /* Give busy worker threads a chance to service their connections */
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Waiting for %d worker threads to exit.", my_pid, threads_created);
+ end_time = time(NULL) + 180;
+ while (threads_created) {
+ rv = wait_for_many_objects(threads_created, child_handles, (DWORD)(end_time - time(NULL)));
+ if (rv != WAIT_TIMEOUT) {
+ rv = rv - WAIT_OBJECT_0;
+ ap_assert((rv >= 0) && (rv < threads_created));
+ cleanup_thread(child_handles, &threads_created, rv);
+ continue;
+ }
+ break;
+ }
+
+ /* Kill remaining threads off the hard way */
+ if (threads_created) {
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Terminating %d threads that failed to exit.",
+ my_pid, threads_created);
+ }
+ for (i = 0; i < threads_created; i++) {
+ int *score_idx;
+ TerminateThread(child_handles[i], 1);
+ CloseHandle(child_handles[i]);
+ /* Reset the scoreboard entry for the thread we just whacked */
+ score_idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE));
+ ap_update_child_status_from_indexes(0, *score_idx, SERVER_DEAD, NULL);
+ }
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: All worker threads have exited.", my_pid);
+
+ CloseHandle(allowed_globals.jobsemaphore);
+ apr_thread_mutex_destroy(allowed_globals.jobmutex);
+ apr_thread_mutex_destroy(child_lock);
+
+ if (use_acceptex) {
+ apr_thread_mutex_destroy(qlock);
+ CloseHandle(qwait_event);
+ }
+
+ apr_pool_destroy(pchild);
+ CloseHandle(exit_event);
+}
+
+#endif /* def WIN32 */
diff --git a/server/mpm/winnt/mpm.h b/server/mpm/winnt/mpm.h
new file mode 100644
index 00000000..536ee6aa
--- /dev/null
+++ b/server/mpm/winnt/mpm.h
@@ -0,0 +1,49 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file winnt/mpm.h
+ * @brief MPM for Windows NT
+ *
+ * this is the place to make declarations that are MPM specific but that must be
+ * shared with non-mpm specific code in the server. Hummm, perhaps we can
+ * move most of this stuff to mpm_common.h?
+ *
+ * @defgroup APACHE_MPM_WINNT WinNT MPM
+ * @ingroup APACHE_OS_WIN32 APACHE_MPM
+ * @{
+ */
+
+#ifndef APACHE_MPM_H
+#define APACHE_MPM_H
+
+#include "scoreboard.h"
+
+#define MPM_NAME "WinNT"
+
+#define AP_MPM_WANT_SET_PIDFILE
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_WANT_SET_COREDUMPDIR
+#define AP_MPM_WANT_SET_SCOREBOARD
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+#define AP_MPM_WANT_SET_STACKSIZE
+
+extern int ap_threads_per_child;
+extern int ap_thread_limit;
+extern server_rec *ap_server_conf;
+
+#endif /* APACHE_MPM_H */
+/** @} */
diff --git a/server/mpm/winnt/mpm_default.h b/server/mpm/winnt/mpm_default.h
new file mode 100644
index 00000000..990eb47a
--- /dev/null
+++ b/server/mpm/winnt/mpm_default.h
@@ -0,0 +1,89 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file winnt/mpm_default.h
+ * @brief win32 MPM defaults
+ *
+ * @addtogroup APACHE_MPM_WINNT
+ * @{
+ */
+
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* Default limit on the maximum setting of the ThreadsPerChild configuration
+ * directive. This limit can be overridden with the ThreadLimit directive.
+ * This limit directly influences the amount of shared storage that is allocated
+ * for the scoreboard. DEFAULT_THREAD_LIMIT represents a good compromise
+ * between scoreboard size and the ability of the server to handle the most
+ * common installation requirements.
+ */
+#ifndef DEFAULT_THREAD_LIMIT
+#define DEFAULT_THREAD_LIMIT 1920
+#endif
+
+/* The ThreadLimit directive can be used to override the DEFAULT_THREAD_LIMIT.
+ * ThreadLimit cannot be tuned larger than MAX_THREAD_LIMIT.
+ * This is a sort of compile-time limit to help catch typos.
+ */
+#ifndef MAX_THREAD_LIMIT
+#define MAX_THREAD_LIMIT 15000
+#endif
+
+/* Number of threads started in the child process in the absence
+ * of a ThreadsPerChild configuration directive
+ */
+#ifndef DEFAULT_THREADS_PER_CHILD
+#define DEFAULT_THREADS_PER_CHILD 64
+#endif
+
+/* Max number of child processes allowed.
+ */
+#define HARD_SERVER_LIMIT 1
+
+/* Number of servers to spawn off by default
+ */
+#ifndef DEFAULT_NUM_DAEMON
+#define DEFAULT_NUM_DAEMON 1
+#endif
+
+/* Check for definition of DEFAULT_REL_RUNTIMEDIR */
+#ifndef DEFAULT_REL_RUNTIMEDIR
+#define DEFAULT_REL_RUNTIMEDIR "logs"
+#endif
+
+/* Where the main/parent process's pid is logged */
+#ifndef DEFAULT_PIDLOG
+#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+#endif
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If <= 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD
+#define DEFAULT_MAX_REQUESTS_PER_CHILD 0
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/winnt/mpm_winnt.c b/server/mpm/winnt/mpm_winnt.c
new file mode 100644
index 00000000..03985354
--- /dev/null
+++ b/server/mpm/winnt/mpm_winnt.c
@@ -0,0 +1,1724 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef WIN32
+
+#define CORE_PRIVATE
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h" /* for read_config */
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "apr_portable.h"
+#include "apr_thread_proc.h"
+#include "apr_getopt.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_shm.h"
+#include "apr_thread_mutex.h"
+#include "ap_mpm.h"
+#include "ap_config.h"
+#include "ap_listen.h"
+#include "mpm_default.h"
+#include "mpm_winnt.h"
+#include "mpm_common.h"
+#include <malloc.h>
+#include "apr_atomic.h"
+
+
+/* scoreboard.c does the heavy lifting; all we do is create the child
+ * score by moving a handle down the pipe into the child's stdin.
+ */
+extern apr_shm_t *ap_scoreboard_shm;
+server_rec *ap_server_conf;
+
+/* Definitions of WINNT MPM specific config globals */
+static HANDLE shutdown_event; /* used to signal the parent to shutdown */
+static HANDLE restart_event; /* used to signal the parent to restart */
+
+static char ap_coredump_dir[MAX_STRING_LEN];
+
+static int one_process = 0;
+static char const* signal_arg = NULL;
+
+OSVERSIONINFO osver; /* VER_PLATFORM_WIN32_NT */
+
+static DWORD parent_pid;
+DWORD my_pid;
+
+int ap_threads_per_child = 0;
+int use_acceptex = 1;
+static int thread_limit = DEFAULT_THREAD_LIMIT;
+static int first_thread_limit = 0;
+static int changed_limit_at_restart;
+int winnt_mpm_state = AP_MPMQ_STARTING;
+
+/* ap_my_generation are used by the scoreboard code */
+ap_generation_t volatile ap_my_generation=0;
+
+
+/* shared by service.c as global, although
+ * perhaps it should be private.
+ */
+apr_pool_t *pconf;
+
+
+/* definitions from child.c */
+void child_main(apr_pool_t *pconf);
+
+/* used by parent to signal the child to start and exit
+ * NOTE: these are not sophisticated enough for multiple children
+ * so they ultimately should not be shared with child.c
+ */
+extern apr_proc_mutex_t *start_mutex;
+extern HANDLE exit_event;
+
+
+/* Stub functions until this MPM supports the connection status API */
+
+AP_DECLARE(void) ap_update_connection_status(long conn_id, const char *key, \
+ const char *value)
+{
+ /* NOP */
+}
+
+AP_DECLARE(void) ap_reset_connection_status(long conn_id)
+{
+ /* NOP */
+}
+
+AP_DECLARE(apr_array_header_t *) ap_get_status_table(apr_pool_t *p)
+{
+ /* NOP */
+ return NULL;
+}
+
+/*
+ * Command processors
+ */
+
+static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_per_child = atoi(arg);
+ if (ap_threads_per_child > thread_limit) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ThreadsPerChild of %d exceeds ThreadLimit "
+ "value of %d threads,", ap_threads_per_child,
+ thread_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ThreadsPerChild to %d. To increase, please"
+ " see the", thread_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " ThreadLimit directive.");
+ ap_threads_per_child = thread_limit;
+ }
+ else if (ap_threads_per_child < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ThreadsPerChild > 0, setting to 1");
+ ap_threads_per_child = 1;
+ }
+ return NULL;
+}
+static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ int tmp_thread_limit;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ tmp_thread_limit = atoi(arg);
+ /* you cannot change ThreadLimit across a restart; ignore
+ * any such attempts
+ */
+ if (first_thread_limit &&
+ tmp_thread_limit != thread_limit) {
+ /* how do we log a message? the error log is a bit bucket at this
+ * point; we'll just have to set a flag so that ap_mpm_run()
+ * logs a warning later
+ */
+ changed_limit_at_restart = 1;
+ return NULL;
+ }
+ thread_limit = tmp_thread_limit;
+
+ if (thread_limit > MAX_THREAD_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ThreadLimit of %d exceeds compile time limit "
+ "of %d threads,", thread_limit, MAX_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ThreadLimit to %d.", MAX_THREAD_LIMIT);
+ thread_limit = MAX_THREAD_LIMIT;
+ }
+ else if (thread_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ThreadLimit > 0, setting to 1");
+ thread_limit = 1;
+ }
+ return NULL;
+}
+static const char *set_disable_acceptex(cmd_parms *cmd, void *dummy, char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ if (use_acceptex) {
+ use_acceptex = 0;
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL,
+ "Disabled use of AcceptEx() WinSock2 API");
+ }
+ return NULL;
+}
+
+static const command_rec winnt_cmds[] = {
+LISTEN_COMMANDS,
+AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF,
+ "Number of threads each child creates" ),
+AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF,
+ "Maximum worker threads in a server for this run of Apache"),
+AP_INIT_NO_ARGS("Win32DisableAcceptEx", set_disable_acceptex, NULL, RSRC_CONF,
+ "Disable use of the high performance AcceptEx WinSock2 API to work around buggy VPN or Firewall software"),
+
+{ NULL }
+};
+
+
+/*
+ * Signalling Apache on NT.
+ *
+ * Under Unix, Apache can be told to shutdown or restart by sending various
+ * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so
+ * we use "events" instead. The parent apache process goes into a loop
+ * where it waits forever for a set of events. Two of those events are
+ * called
+ *
+ * apPID_shutdown
+ * apPID_restart
+ *
+ * (where PID is the PID of the apache parent process). When one of these
+ * is signalled, the Apache parent performs the appropriate action. The events
+ * can become signalled through internal Apache methods (e.g. if the child
+ * finds a fatal error and needs to kill its parent), via the service
+ * control manager (the control thread will signal the shutdown event when
+ * requested to stop the Apache service), from the -k Apache command line,
+ * or from any external program which finds the Apache PID from the
+ * httpd.pid file.
+ *
+ * The signal_parent() function, below, is used to signal one of these events.
+ * It can be called by any child or parent process, since it does not
+ * rely on global variables.
+ *
+ * On entry, type gives the event to signal. 0 means shutdown, 1 means
+ * graceful restart.
+ */
+/*
+ * Initialise the signal names, in the global variables signal_name_prefix,
+ * signal_restart_name and signal_shutdown_name.
+ */
+#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */
+char signal_name_prefix[MAX_SIGNAL_NAME];
+char signal_restart_name[MAX_SIGNAL_NAME];
+char signal_shutdown_name[MAX_SIGNAL_NAME];
+void setup_signal_names(char *prefix)
+{
+ apr_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix);
+ apr_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name),
+ "%s_shutdown", signal_name_prefix);
+ apr_snprintf(signal_restart_name, sizeof(signal_restart_name),
+ "%s_restart", signal_name_prefix);
+}
+
+int volatile is_graceful = 0;
+
+AP_DECLARE(int) ap_graceful_stop_signalled(void)
+{
+ return is_graceful;
+}
+
+AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type)
+{
+ HANDLE e;
+ char *signal_name;
+
+ if (parent_pid == my_pid) {
+ switch(type) {
+ case SIGNAL_PARENT_SHUTDOWN:
+ {
+ SetEvent(shutdown_event);
+ break;
+ }
+ /* This MPM supports only graceful restarts right now */
+ case SIGNAL_PARENT_RESTART:
+ case SIGNAL_PARENT_RESTART_GRACEFUL:
+ {
+ is_graceful = 1;
+ SetEvent(restart_event);
+ break;
+ }
+ }
+ return;
+ }
+
+ switch(type) {
+ case SIGNAL_PARENT_SHUTDOWN:
+ {
+ signal_name = signal_shutdown_name;
+ break;
+ }
+ /* This MPM supports only graceful restarts right now */
+ case SIGNAL_PARENT_RESTART:
+ case SIGNAL_PARENT_RESTART_GRACEFUL:
+ {
+ signal_name = signal_restart_name;
+ is_graceful = 1;
+ break;
+ }
+ default:
+ return;
+ }
+
+ e = OpenEvent(EVENT_MODIFY_STATE, FALSE, signal_name);
+ if (!e) {
+ /* Um, problem, can't signal the parent, which means we can't
+ * signal ourselves to die. Ignore for now...
+ */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf,
+ "OpenEvent on %s event", signal_name);
+ return;
+ }
+ if (SetEvent(e) == 0) {
+ /* Same problem as above */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf,
+ "SetEvent on %s event", signal_name);
+ CloseHandle(e);
+ return;
+ }
+ CloseHandle(e);
+}
+
+
+/*
+ * Passed the following handles [in sync with send_handles_to_child()]
+ *
+ * ready event [signal the parent immediately, then close]
+ * exit event [save to poll later]
+ * start mutex [signal from the parent to begin accept()]
+ * scoreboard shm handle [to recreate the ap_scoreboard]
+ */
+void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event,
+ apr_proc_mutex_t **child_start_mutex,
+ apr_shm_t **scoreboard_shm)
+{
+ HANDLE pipe;
+ HANDLE hScore;
+ HANDLE ready_event;
+ HANDLE os_start;
+ DWORD BytesRead;
+ void *sb_shared;
+ apr_status_t rv;
+
+ pipe = GetStdHandle(STD_INPUT_HANDLE);
+ if (!ReadFile(pipe, &ready_event, sizeof(HANDLE),
+ &BytesRead, (LPOVERLAPPED) NULL)
+ || (BytesRead != sizeof(HANDLE))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: Unable to retrieve the ready event from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+
+ SetEvent(ready_event);
+ CloseHandle(ready_event);
+
+ if (!ReadFile(pipe, child_exit_event, sizeof(HANDLE),
+ &BytesRead, (LPOVERLAPPED) NULL)
+ || (BytesRead != sizeof(HANDLE))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: Unable to retrieve the exit event from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+
+ if (!ReadFile(pipe, &os_start, sizeof(os_start),
+ &BytesRead, (LPOVERLAPPED) NULL)
+ || (BytesRead != sizeof(os_start))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: Unable to retrieve the start_mutex from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+ *child_start_mutex = NULL;
+ if ((rv = apr_os_proc_mutex_put(child_start_mutex, &os_start, s->process->pool))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Child %d: Unable to access the start_mutex from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+
+ if (!ReadFile(pipe, &hScore, sizeof(hScore),
+ &BytesRead, (LPOVERLAPPED) NULL)
+ || (BytesRead != sizeof(hScore))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Child %d: Unable to retrieve the scoreboard from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+ *scoreboard_shm = NULL;
+ if ((rv = apr_os_shm_put(scoreboard_shm, &hScore, s->process->pool))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Child %d: Unable to access the scoreboard from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+
+ rv = ap_reopen_scoreboard(s->process->pool, scoreboard_shm, 1);
+ if (rv || !(sb_shared = apr_shm_baseaddr_get(*scoreboard_shm))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "Child %d: Unable to reopen the scoreboard from the parent", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+ /* We must 'initialize' the scoreboard to relink all the
+ * process-local pointer arrays into the shared memory block.
+ */
+ ap_init_scoreboard(sb_shared);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Child %d: Retrieved our scoreboard from the parent.", my_pid);
+}
+
+
+static int send_handles_to_child(apr_pool_t *p,
+ HANDLE child_ready_event,
+ HANDLE child_exit_event,
+ apr_proc_mutex_t *child_start_mutex,
+ apr_shm_t *scoreboard_shm,
+ HANDLE hProcess,
+ apr_file_t *child_in)
+{
+ apr_status_t rv;
+ HANDLE hCurrentProcess = GetCurrentProcess();
+ HANDLE hDup;
+ HANDLE os_start;
+ HANDLE hScore;
+ apr_size_t BytesWritten;
+
+ if (!DuplicateHandle(hCurrentProcess, child_ready_event, hProcess, &hDup,
+ EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Unable to duplicate the ready event handle for the child");
+ return -1;
+ }
+ if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to send the exit event handle to the child");
+ return -1;
+ }
+ if (!DuplicateHandle(hCurrentProcess, child_exit_event, hProcess, &hDup,
+ EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Unable to duplicate the exit event handle for the child");
+ return -1;
+ }
+ if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to send the exit event handle to the child");
+ return -1;
+ }
+ if ((rv = apr_os_proc_mutex_get(&os_start, child_start_mutex)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to retrieve the start mutex for the child");
+ return -1;
+ }
+ if (!DuplicateHandle(hCurrentProcess, os_start, hProcess, &hDup,
+ SYNCHRONIZE, FALSE, 0)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Unable to duplicate the start mutex to the child");
+ return -1;
+ }
+ if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to send the start mutex to the child");
+ return -1;
+ }
+ if ((rv = apr_os_shm_get(&hScore, scoreboard_shm)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to retrieve the scoreboard handle for the child");
+ return -1;
+ }
+ if (!DuplicateHandle(hCurrentProcess, hScore, hProcess, &hDup,
+ FILE_MAP_READ | FILE_MAP_WRITE, FALSE, 0)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Unable to duplicate the scoreboard handle to the child");
+ return -1;
+ }
+ if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to send the scoreboard handle to the child");
+ return -1;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Parent: Sent the scoreboard to the child");
+ return 0;
+}
+
+
+/*
+ * get_listeners_from_parent()
+ * The listen sockets are opened in the parent. This function, which runs
+ * exclusively in the child process, receives them from the parent and
+ * makes them availeble in the child.
+ */
+void get_listeners_from_parent(server_rec *s)
+{
+ WSAPROTOCOL_INFO WSAProtocolInfo;
+ HANDLE pipe;
+ ap_listen_rec *lr;
+ DWORD BytesRead;
+ int lcnt = 0;
+ SOCKET nsd;
+
+ /* Set up a default listener if necessary */
+ if (ap_listeners == NULL) {
+ ap_listen_rec *lr;
+ lr = apr_palloc(s->process->pool, sizeof(ap_listen_rec));
+ lr->sd = NULL;
+ lr->next = ap_listeners;
+ ap_listeners = lr;
+ }
+
+ /* Open the pipe to the parent process to receive the inherited socket
+ * data. The sockets have been set to listening in the parent process.
+ */
+ pipe = GetStdHandle(STD_INPUT_HANDLE);
+
+ for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) {
+ if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO),
+ &BytesRead, (LPOVERLAPPED) NULL)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "setup_inherited_listeners: Unable to read socket data from parent");
+ exit(APEXIT_CHILDINIT);
+ }
+ nsd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
+ &WSAProtocolInfo, 0, 0);
+ if (nsd == INVALID_SOCKET) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf,
+ "Child %d: setup_inherited_listeners(), WSASocket failed to open the inherited socket.", my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
+ HANDLE hProcess = GetCurrentProcess();
+ HANDLE dup;
+ if (DuplicateHandle(hProcess, (HANDLE) nsd, hProcess, &dup,
+ 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+ closesocket(nsd);
+ nsd = (SOCKET) dup;
+ }
+ }
+ else {
+ /* A different approach. Many users report errors such as
+ * (32538)An operation was attempted on something that is not
+ * a socket. : Parent: WSADuplicateSocket failed...
+ *
+ * This appears that the duplicated handle is no longer recognized
+ * as a socket handle. SetHandleInformation should overcome that
+ * problem by not altering the handle identifier. But this won't
+ * work on 9x - it's unsupported.
+ */
+ if (!SetHandleInformation((HANDLE)nsd, HANDLE_FLAG_INHERIT, 0)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf,
+ "set_listeners_noninheritable: SetHandleInformation failed.");
+ }
+ }
+ apr_os_sock_put(&lr->sd, &nsd, s->process->pool);
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Child %d: retrieved %d listeners from parent", my_pid, lcnt);
+}
+
+
+static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId,
+ apr_file_t *child_in)
+{
+ apr_status_t rv;
+ int lcnt = 0;
+ ap_listen_rec *lr;
+ LPWSAPROTOCOL_INFO lpWSAProtocolInfo;
+ apr_size_t BytesWritten;
+
+ /* Run the chain of open sockets. For each socket, duplicate it
+ * for the target process then send the WSAPROTOCOL_INFO
+ * (returned by dup socket) to the child.
+ */
+ for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) {
+ apr_os_sock_t nsd;
+ lpWSAProtocolInfo = apr_pcalloc(p, sizeof(WSAPROTOCOL_INFO));
+ apr_os_sock_get(&nsd,lr->sd);
+ ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf,
+ "Parent: Duplicating socket %d and sending it to child process %d",
+ nsd, dwProcessId);
+ if (WSADuplicateSocket(nsd, dwProcessId,
+ lpWSAProtocolInfo) == SOCKET_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf,
+ "Parent: WSADuplicateSocket failed for socket %d. Check the FAQ.", lr->sd );
+ return -1;
+ }
+
+ if ((rv = apr_file_write_full(child_in, lpWSAProtocolInfo,
+ sizeof(WSAPROTOCOL_INFO), &BytesWritten))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to write duplicated socket %d to the child.", lr->sd );
+ return -1;
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "Parent: Sent %d listeners to child %d", lcnt, dwProcessId);
+ return 0;
+}
+
+enum waitlist_e {
+ waitlist_ready = 0,
+ waitlist_term = 1
+};
+
+static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_event,
+ DWORD *child_pid)
+{
+ /* These NEVER change for the lifetime of this parent
+ */
+ static char **args = NULL;
+ static char **env = NULL;
+ static char pidbuf[28];
+
+ apr_status_t rv;
+ apr_pool_t *ptemp;
+ apr_procattr_t *attr;
+ apr_file_t *child_out;
+ apr_file_t *child_err;
+ apr_proc_t new_child;
+ HANDLE hExitEvent;
+ HANDLE waitlist[2]; /* see waitlist_e */
+ char *cmd;
+ char *cwd;
+
+ apr_pool_create_ex(&ptemp, p, NULL, NULL);
+
+ /* Build the command line. Should look something like this:
+ * C:/apache/bin/apache.exe -f ap_server_confname
+ * First, get the path to the executable...
+ */
+ apr_procattr_create(&attr, ptemp);
+ apr_procattr_cmdtype_set(attr, APR_PROGRAM);
+ apr_procattr_detach_set(attr, 1);
+ if (((rv = apr_filepath_get(&cwd, 0, ptemp)) != APR_SUCCESS)
+ || ((rv = apr_procattr_dir_set(attr, cwd)) != APR_SUCCESS)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Failed to get the current path");
+ }
+
+ if (!args) {
+ /* Build the args array, only once since it won't change
+ * for the lifetime of this parent process.
+ */
+ if ((rv = ap_os_proc_filepath(&cmd, ptemp))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf,
+ "Parent: Failed to get full path of %s",
+ ap_server_conf->process->argv[0]);
+ apr_pool_destroy(ptemp);
+ return -1;
+ }
+
+ args = malloc((ap_server_conf->process->argc + 1) * sizeof (char*));
+ memcpy(args + 1, ap_server_conf->process->argv + 1,
+ (ap_server_conf->process->argc - 1) * sizeof (char*));
+ args[0] = malloc(strlen(cmd) + 1);
+ strcpy(args[0], cmd);
+ args[ap_server_conf->process->argc] = NULL;
+ }
+ else {
+ cmd = args[0];
+ }
+
+ /* Create a pipe to send handles to the child */
+ if ((rv = apr_procattr_io_set(attr, APR_FULL_BLOCK,
+ APR_NO_PIPE, APR_NO_PIPE)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to create child stdin pipe.");
+ apr_pool_destroy(ptemp);
+ return -1;
+ }
+
+ /* Open a null handle to soak info from the child */
+ if (((rv = apr_file_open(&child_out, "NUL", APR_READ | APR_WRITE,
+ APR_OS_DEFAULT, ptemp)) != APR_SUCCESS)
+ || ((rv = apr_procattr_child_out_set(attr, child_out, NULL))
+ != APR_SUCCESS)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to connect child stdout to NUL.");
+ apr_pool_destroy(ptemp);
+ return -1;
+ }
+
+ /* Connect the child's initial stderr to our main server error log
+ * or share our own stderr handle.
+ */
+ if (ap_server_conf->error_log) {
+ child_err = ap_server_conf->error_log;
+ }
+ else {
+ rv = apr_file_open_stderr(&child_err, ptemp);
+ }
+ if (rv == APR_SUCCESS) {
+ if ((rv = apr_procattr_child_err_set(attr, child_err, NULL))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Unable to connect child stderr.");
+ apr_pool_destroy(ptemp);
+ return -1;
+ }
+ }
+
+ /* Create the child_ready_event */
+ waitlist[waitlist_ready] = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!waitlist[waitlist_ready]) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Could not create ready event for child process");
+ apr_pool_destroy (ptemp);
+ return -1;
+ }
+
+ /* Create the child_exit_event */
+ hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!hExitEvent) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Could not create exit event for child process");
+ apr_pool_destroy(ptemp);
+ CloseHandle(waitlist[waitlist_ready]);
+ return -1;
+ }
+
+ if (!env)
+ {
+ /* Build the env array, only once since it won't change
+ * for the lifetime of this parent process.
+ */
+ int envc;
+ for (envc = 0; _environ[envc]; ++envc) {
+ ;
+ }
+ env = malloc((envc + 2) * sizeof (char*));
+ memcpy(env, _environ, envc * sizeof (char*));
+ apr_snprintf(pidbuf, sizeof(pidbuf), "AP_PARENT_PID=%i", parent_pid);
+ env[envc] = pidbuf;
+ env[envc + 1] = NULL;
+ }
+
+ rv = apr_proc_create(&new_child, cmd, args, env, attr, ptemp);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Parent: Failed to create the child process.");
+ apr_pool_destroy(ptemp);
+ CloseHandle(hExitEvent);
+ CloseHandle(waitlist[waitlist_ready]);
+ CloseHandle(new_child.hproc);
+ return -1;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Parent: Created child process %d", new_child.pid);
+
+ if (send_handles_to_child(ptemp, waitlist[waitlist_ready], hExitEvent,
+ start_mutex, ap_scoreboard_shm,
+ new_child.hproc, new_child.in)) {
+ /*
+ * This error is fatal, mop up the child and move on
+ * We toggle the child's exit event to cause this child
+ * to quit even as it is attempting to start.
+ */
+ SetEvent(hExitEvent);
+ apr_pool_destroy(ptemp);
+ CloseHandle(hExitEvent);
+ CloseHandle(waitlist[waitlist_ready]);
+ CloseHandle(new_child.hproc);
+ return -1;
+ }
+
+ /* Important:
+ * Give the child process a chance to run before dup'ing the sockets.
+ * We have already set the listening sockets noninheritable, but if
+ * WSADuplicateSocket runs before the child process initializes
+ * the listeners will be inherited anyway.
+ */
+ waitlist[waitlist_term] = new_child.hproc;
+ rv = WaitForMultipleObjects(2, waitlist, FALSE, INFINITE);
+ CloseHandle(waitlist[waitlist_ready]);
+ if (rv != WAIT_OBJECT_0) {
+ /*
+ * Outch... that isn't a ready signal. It's dead, Jim!
+ */
+ SetEvent(hExitEvent);
+ apr_pool_destroy(ptemp);
+ CloseHandle(hExitEvent);
+ CloseHandle(new_child.hproc);
+ return -1;
+ }
+
+ if (send_listeners_to_child(ptemp, new_child.pid, new_child.in)) {
+ /*
+ * This error is fatal, mop up the child and move on
+ * We toggle the child's exit event to cause this child
+ * to quit even as it is attempting to start.
+ */
+ SetEvent(hExitEvent);
+ apr_pool_destroy(ptemp);
+ CloseHandle(hExitEvent);
+ CloseHandle(new_child.hproc);
+ return -1;
+ }
+
+ *child_exit_event = hExitEvent;
+ *child_proc = new_child.hproc;
+ *child_pid = new_child.pid;
+
+ return 0;
+}
+
+/***********************************************************************
+ * master_main()
+ * master_main() runs in the parent process. It creates the child
+ * process which handles HTTP requests then waits on one of three
+ * events:
+ *
+ * restart_event
+ * -------------
+ * The restart event causes master_main to start a new child process and
+ * tells the old child process to exit (by setting the child_exit_event).
+ * The restart event is set as a result of one of the following:
+ * 1. An apache -k restart command on the command line
+ * 2. A command received from Windows service manager which gets
+ * translated into an ap_signal_parent(SIGNAL_PARENT_RESTART)
+ * call by code in service.c.
+ * 3. The child process calling ap_signal_parent(SIGNAL_PARENT_RESTART)
+ * as a result of hitting MaxRequestsPerChild.
+ *
+ * shutdown_event
+ * --------------
+ * The shutdown event causes master_main to tell the child process to
+ * exit and that the server is shutting down. The shutdown event is
+ * set as a result of one of the following:
+ * 1. An apache -k shutdown command on the command line
+ * 2. A command received from Windows service manager which gets
+ * translated into an ap_signal_parent(SIGNAL_PARENT_SHUTDOWN)
+ * call by code in service.c.
+ *
+ * child process handle
+ * --------------------
+ * The child process handle will be signaled if the child process
+ * exits for any reason. In a normal running server, the signaling
+ * of this event means that the child process has exited prematurely
+ * due to a seg fault or other irrecoverable error. For server
+ * robustness, master_main will restart the child process under this
+ * condtion.
+ *
+ * master_main uses the child_exit_event to signal the child process
+ * to exit.
+ **********************************************************************/
+#define NUM_WAIT_HANDLES 3
+#define CHILD_HANDLE 0
+#define SHUTDOWN_HANDLE 1
+#define RESTART_HANDLE 2
+static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_event)
+{
+ int rv, cld;
+ int restart_pending;
+ int shutdown_pending;
+ HANDLE child_exit_event;
+ HANDLE event_handles[NUM_WAIT_HANDLES];
+ DWORD child_pid;
+
+ restart_pending = shutdown_pending = 0;
+
+ event_handles[SHUTDOWN_HANDLE] = shutdown_event;
+ event_handles[RESTART_HANDLE] = restart_event;
+
+ /* Create a single child process */
+ rv = create_process(pconf, &event_handles[CHILD_HANDLE],
+ &child_exit_event, &child_pid);
+ if (rv < 0)
+ {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "master_main: create child process failed. Exiting.");
+ shutdown_pending = 1;
+ goto die_now;
+ }
+ if (!strcasecmp(signal_arg, "runservice")) {
+ mpm_service_started();
+ }
+
+ /* Update the scoreboard. Note that there is only a single active
+ * child at once.
+ */
+ ap_scoreboard_image->parent[0].quiescing = 0;
+ ap_scoreboard_image->parent[0].pid = child_pid;
+
+ /* Wait for shutdown or restart events or for child death */
+ winnt_mpm_state = AP_MPMQ_RUNNING;
+ rv = WaitForMultipleObjects(NUM_WAIT_HANDLES, (HANDLE *) event_handles, FALSE, INFINITE);
+ cld = rv - WAIT_OBJECT_0;
+ if (rv == WAIT_FAILED) {
+ /* Something serious is wrong */
+ ap_log_error(APLOG_MARK,APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "master_main: WaitForMultipeObjects WAIT_FAILED -- doing server shutdown");
+ shutdown_pending = 1;
+ }
+ else if (rv == WAIT_TIMEOUT) {
+ /* Hey, this cannot happen */
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s,
+ "master_main: WaitForMultipeObjects with INFINITE wait exited with WAIT_TIMEOUT");
+ shutdown_pending = 1;
+ }
+ else if (cld == SHUTDOWN_HANDLE) {
+ /* shutdown_event signalled */
+ shutdown_pending = 1;
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, s,
+ "Parent: Received shutdown signal -- Shutting down the server.");
+ if (ResetEvent(shutdown_event) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s,
+ "ResetEvent(shutdown_event)");
+ }
+ }
+ else if (cld == RESTART_HANDLE) {
+ /* Received a restart event. Prepare the restart_event to be reused
+ * then signal the child process to exit.
+ */
+ restart_pending = 1;
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
+ "Parent: Received restart signal -- Restarting the server.");
+ if (ResetEvent(restart_event) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s,
+ "Parent: ResetEvent(restart_event) failed.");
+ }
+ if (SetEvent(child_exit_event) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s,
+ "Parent: SetEvent for child process %d failed.",
+ event_handles[CHILD_HANDLE]);
+ }
+ /* Don't wait to verify that the child process really exits,
+ * just move on with the restart.
+ */
+ CloseHandle(event_handles[CHILD_HANDLE]);
+ event_handles[CHILD_HANDLE] = NULL;
+ }
+ else {
+ /* The child process exited prematurely due to a fatal error. */
+ DWORD exitcode;
+ if (!GetExitCodeProcess(event_handles[CHILD_HANDLE], &exitcode)) {
+ /* HUH? We did exit, didn't we? */
+ exitcode = APEXIT_CHILDFATAL;
+ }
+ if ( exitcode == APEXIT_CHILDFATAL
+ || exitcode == APEXIT_CHILDINIT
+ || exitcode == APEXIT_INIT) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf,
+ "Parent: child process exited with status %u -- Aborting.", exitcode);
+ shutdown_pending = 1;
+ }
+ else {
+ int i;
+ restart_pending = 1;
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Parent: child process exited with status %u -- Restarting.", exitcode);
+ for (i = 0; i < ap_threads_per_child; i++) {
+ ap_update_child_status_from_indexes(0, i, SERVER_DEAD, NULL);
+ }
+ }
+ CloseHandle(event_handles[CHILD_HANDLE]);
+ event_handles[CHILD_HANDLE] = NULL;
+ }
+ if (restart_pending) {
+ ++ap_my_generation;
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+ }
+die_now:
+ if (shutdown_pending)
+ {
+ int timeout = 30000; /* Timeout is milliseconds */
+ winnt_mpm_state = AP_MPMQ_STOPPING;
+
+ /* This shutdown is only marginally graceful. We will give the
+ * child a bit of time to exit gracefully. If the time expires,
+ * the child will be wacked.
+ */
+ if (!strcasecmp(signal_arg, "runservice")) {
+ mpm_service_stopping();
+ }
+ /* Signal the child processes to exit */
+ if (SetEvent(child_exit_event) == 0) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_os_error(), ap_server_conf,
+ "Parent: SetEvent for child process %d failed", event_handles[CHILD_HANDLE]);
+ }
+ if (event_handles[CHILD_HANDLE]) {
+ rv = WaitForSingleObject(event_handles[CHILD_HANDLE], timeout);
+ if (rv == WAIT_OBJECT_0) {
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Parent: Child process exited successfully.");
+ CloseHandle(event_handles[CHILD_HANDLE]);
+ event_handles[CHILD_HANDLE] = NULL;
+ }
+ else {
+ ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Parent: Forcing termination of child process %d ", event_handles[CHILD_HANDLE]);
+ TerminateProcess(event_handles[CHILD_HANDLE], 1);
+ CloseHandle(event_handles[CHILD_HANDLE]);
+ event_handles[CHILD_HANDLE] = NULL;
+ }
+ }
+ return 0; /* Tell the caller we do not want to restart */
+ }
+ winnt_mpm_state = AP_MPMQ_STARTING;
+ return 1; /* Tell the caller we want a restart */
+}
+
+/* service_nt_main_fn needs to append the StartService() args
+ * outside of our call stack and thread as the service starts...
+ */
+apr_array_header_t *mpm_new_argv;
+
+/* Remember service_to_start failures to log and fail in pre_config.
+ * Remember inst_argc and inst_argv for installing or starting the
+ * service after we preflight the config.
+ */
+
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch(query_code){
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = MAXIMUM_WAIT_OBJECTS;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_STATIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_NOT_SUPPORTED;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = HARD_SERVER_LIMIT;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = thread_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_THREADS:
+ *result = ap_threads_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_THREADS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_THREADS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MPM_STATE:
+ *result = winnt_mpm_state;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+#define SERVICE_UNSET (-1)
+static apr_status_t service_set = SERVICE_UNSET;
+static apr_status_t service_to_start_success;
+static int inst_argc;
+static const char * const *inst_argv;
+static char *service_name = NULL;
+
+void winnt_rewrite_args(process_rec *process)
+{
+ /* Handle the following SCM aspects in this phase:
+ *
+ * -k runservice [transition for WinNT, nothing for Win9x]
+ * -k install
+ * -k config
+ * -k uninstall
+ * -k stop
+ * -k shutdown (same as -k stop). Maintained for backward compatability.
+ *
+ * We can't leave this phase until we know our identity
+ * and modify the command arguments appropriately.
+ *
+ * We do not care if the .conf file exists or is parsable when
+ * attempting to stop or uninstall a service.
+ */
+ apr_status_t rv;
+ char *def_server_root;
+ char *binpath;
+ char optbuf[3];
+ const char *optarg;
+ int fixed_args;
+ char *pid;
+ apr_getopt_t *opt;
+ int running_as_service = 1;
+ int errout = 0;
+
+ pconf = process->pconf;
+
+ osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&osver);
+
+ /* AP_PARENT_PID is only valid in the child */
+ pid = getenv("AP_PARENT_PID");
+ if (pid)
+ {
+ /* This is the child */
+ my_pid = GetCurrentProcessId();
+ parent_pid = (DWORD) atol(pid);
+
+ /* Prevent holding open the (nonexistant) console */
+ ap_real_exit_code = 0;
+
+ /* The parent is responsible for providing the
+ * COMPLETE ARGUMENTS REQUIRED to the child.
+ *
+ * No further argument parsing is needed, but
+ * for good measure we will provide a simple
+ * signal string for later testing.
+ */
+ signal_arg = "runchild";
+ return;
+ }
+
+ /* This is the parent, we have a long way to go :-) */
+ parent_pid = my_pid = GetCurrentProcessId();
+
+ /* This behavior is voided by setting real_exit_code to 0 */
+ atexit(hold_console_open_on_error);
+
+ /* Rewrite process->argv[];
+ *
+ * strip out -k signal into signal_arg
+ * strip out -n servicename and set the names
+ * add default -d serverroot from the path of this executable
+ *
+ * The end result will look like:
+ *
+ * The invocation command (%0)
+ * The -d serverroot default from the running executable
+ * The requested service's (-n) registry ConfigArgs
+ * The WinNT SCM's StartService() args
+ */
+ if ((rv = ap_os_proc_filepath(&binpath, process->pconf))
+ != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL,
+ "Failed to get the full path of %s", process->argv[0]);
+ exit(APEXIT_INIT);
+ }
+ /* WARNING: There is an implict assumption here that the
+ * executable resides in ServerRoot or ServerRoot\bin
+ */
+ def_server_root = (char *) apr_filepath_name_get(binpath);
+ if (def_server_root > binpath) {
+ *(def_server_root - 1) = '\0';
+ def_server_root = (char *) apr_filepath_name_get(binpath);
+ if (!strcasecmp(def_server_root, "bin"))
+ *(def_server_root - 1) = '\0';
+ }
+ apr_filepath_merge(&def_server_root, NULL, binpath,
+ APR_FILEPATH_TRUENAME, process->pool);
+
+ /* Use process->pool so that the rewritten argv
+ * lasts for the lifetime of the server process,
+ * because pconf will be destroyed after the
+ * initial pre-flight of the config parser.
+ */
+ mpm_new_argv = apr_array_make(process->pool, process->argc + 2,
+ sizeof(const char *));
+ *(const char **)apr_array_push(mpm_new_argv) = process->argv[0];
+ *(const char **)apr_array_push(mpm_new_argv) = "-d";
+ *(const char **)apr_array_push(mpm_new_argv) = def_server_root;
+
+ fixed_args = mpm_new_argv->nelts;
+
+ optbuf[0] = '-';
+ optbuf[2] = '\0';
+ apr_getopt_init(&opt, process->pool, process->argc, (char**) process->argv);
+ opt->errfn = NULL;
+ while ((rv = apr_getopt(opt, "wn:k:" AP_SERVER_BASEARGS,
+ optbuf + 1, &optarg)) == APR_SUCCESS) {
+ switch (optbuf[1]) {
+
+ /* Shortcuts; include the -w option to hold the window open on error.
+ * This must not be toggled once we reset ap_real_exit_code to 0!
+ */
+ case 'w':
+ if (ap_real_exit_code)
+ ap_real_exit_code = 2;
+ break;
+
+ case 'n':
+ service_set = mpm_service_set_name(process->pool, &service_name,
+ optarg);
+ break;
+
+ case 'k':
+ signal_arg = optarg;
+ break;
+
+ case 'E':
+ errout = 1;
+ /* Fall through so the Apache main() handles the 'E' arg */
+ default:
+ *(const char **)apr_array_push(mpm_new_argv) =
+ apr_pstrdup(process->pool, optbuf);
+
+ if (optarg) {
+ *(const char **)apr_array_push(mpm_new_argv) = optarg;
+ }
+ break;
+ }
+ }
+
+ /* back up to capture the bad argument */
+ if (rv == APR_BADCH || rv == APR_BADARG) {
+ opt->ind--;
+ }
+
+ while (opt->ind < opt->argc) {
+ *(const char **)apr_array_push(mpm_new_argv) =
+ apr_pstrdup(process->pool, opt->argv[opt->ind++]);
+ }
+
+ /* Track the number of args actually entered by the user */
+ inst_argc = mpm_new_argv->nelts - fixed_args;
+
+ /* Provide a default 'run' -k arg to simplify signal_arg tests */
+ if (!signal_arg)
+ {
+ signal_arg = "run";
+ running_as_service = 0;
+ }
+
+ if (!strcasecmp(signal_arg, "runservice"))
+ {
+ /* Start the NT Service _NOW_ because the WinNT SCM is
+ * expecting us to rapidly assume control of our own
+ * process, the SCM will tell us our service name, and
+ * may have extra StartService() command arguments to
+ * add for us.
+ *
+ * The SCM will generally invoke the executable with
+ * the c:\win\system32 default directory. This is very
+ * lethal if folks use ServerRoot /foopath on windows
+ * without a drive letter. Change to the default root
+ * (path to apache root, above /bin) for safety.
+ */
+ apr_filepath_set(def_server_root, process->pool);
+
+ /* Any other process has a console, so we don't to begin
+ * a Win9x service until the configuration is parsed and
+ * any command line errors are reported.
+ *
+ * We hold the return value so that we can die in pre_config
+ * after logging begins, and the failure can land in the log.
+ */
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ if (!errout) {
+ mpm_nt_eventlog_stderr_open(service_name, process->pool);
+ }
+ service_to_start_success = mpm_service_to_start(&service_name,
+ process->pool);
+ if (service_to_start_success == APR_SUCCESS) {
+ service_set = APR_SUCCESS;
+ }
+ }
+ }
+
+ /* Get the default for any -k option, except run */
+ if (service_set == SERVICE_UNSET && strcasecmp(signal_arg, "run")) {
+ service_set = mpm_service_set_name(process->pool, &service_name,
+ AP_DEFAULT_SERVICE_NAME);
+ }
+
+ if (!strcasecmp(signal_arg, "install")) /* -k install */
+ {
+ if (service_set == APR_SUCCESS)
+ {
+ ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL,
+ "%s: Service is already installed.", service_name);
+ exit(APEXIT_INIT);
+ }
+ }
+ else if (running_as_service)
+ {
+ if (service_set == APR_SUCCESS)
+ {
+ /* Attempt to Uninstall, or stop, before
+ * we can read the arguments or .conf files
+ */
+ if (!strcasecmp(signal_arg, "uninstall")) {
+ rv = mpm_service_uninstall();
+ exit(rv);
+ }
+
+ if ((!strcasecmp(signal_arg, "stop")) ||
+ (!strcasecmp(signal_arg, "shutdown"))) {
+ mpm_signal_service(process->pool, 0);
+ exit(0);
+ }
+
+ rv = mpm_merge_service_args(process->pool, mpm_new_argv,
+ fixed_args);
+ if (rv == APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_INFO, 0, NULL,
+ "Using ConfigArgs of the installed service "
+ "\"%s\".", service_name);
+ }
+ else {
+ ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL,
+ "No installed ConfigArgs for the service "
+ "\"%s\", using Apache defaults.", service_name);
+ }
+ }
+ else
+ {
+ ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL,
+ "No installed service named \"%s\".", service_name);
+ exit(APEXIT_INIT);
+ }
+ }
+ if (strcasecmp(signal_arg, "install") && service_set && service_set != SERVICE_UNSET)
+ {
+ ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL,
+ "No installed service named \"%s\".", service_name);
+ exit(APEXIT_INIT);
+ }
+
+ /* Track the args actually entered by the user.
+ * These will be used for the -k install parameters, as well as
+ * for the -k start service override arguments.
+ */
+ inst_argv = (const char * const *)mpm_new_argv->elts
+ + mpm_new_argv->nelts - inst_argc;
+
+ /* Now, do service install or reconfigure then proceed to
+ * post_config to test the installed configuration.
+ */
+ if (!strcasecmp(signal_arg, "config")) { /* -k config */
+ /* Reconfigure the service */
+ rv = mpm_service_install(process->pool, inst_argc, inst_argv, 1);
+ if (rv != APR_SUCCESS) {
+ exit(rv);
+ }
+
+ fprintf(stderr,"Testing httpd.conf....\n");
+ fprintf(stderr,"Errors reported here must be corrected before the "
+ "service can be started.\n");
+ }
+ else if (!strcasecmp(signal_arg, "install")) { /* -k install */
+ /* Install the service */
+ rv = mpm_service_install(process->pool, inst_argc, inst_argv, 0);
+ if (rv != APR_SUCCESS) {
+ exit(rv);
+ }
+
+ fprintf(stderr,"Testing httpd.conf....\n");
+ fprintf(stderr,"Errors reported here must be corrected before the "
+ "service can be started.\n");
+ }
+
+ process->argc = mpm_new_argv->nelts;
+ process->argv = (const char * const *) mpm_new_argv->elts;
+}
+
+
+static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+ /* Handle the following SCM aspects in this phase:
+ *
+ * -k runservice [WinNT errors logged from rewrite_args]
+ */
+
+ /* Initialize shared static objects.
+ * TODO: Put config related statics into an sconf structure.
+ */
+ pconf = pconf_;
+
+ if (ap_exists_config_define("ONE_PROCESS") ||
+ ap_exists_config_define("DEBUG"))
+ one_process = -1;
+
+ if (!strcasecmp(signal_arg, "runservice")
+ && (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ && (service_to_start_success != APR_SUCCESS)) {
+ ap_log_error(APLOG_MARK,APLOG_CRIT, service_to_start_success, NULL,
+ "%s: Unable to start the service manager.",
+ service_name);
+ exit(APEXIT_INIT);
+ }
+
+ /* Win9x: disable AcceptEx */
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
+ use_acceptex = 0;
+ }
+
+ ap_listen_pre_config();
+ ap_threads_per_child = DEFAULT_THREADS_PER_CHILD;
+ ap_pid_fname = DEFAULT_PIDLOG;
+ ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+ /* use_acceptex which is enabled by default is not available on Win9x.
+ */
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
+ use_acceptex = 0;
+ }
+
+ apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir));
+
+ return OK;
+}
+
+static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec* s)
+{
+ static int restart_num = 0;
+ apr_status_t rv = 0;
+
+ /* Handle the following SCM aspects in this phase:
+ *
+ * -k install (catch and exit as install was handled in rewrite_args)
+ * -k config (catch and exit as config was handled in rewrite_args)
+ * -k start
+ * -k restart
+ * -k runservice [Win95, only once - after we parsed the config]
+ *
+ * because all of these signals are useful _only_ if there
+ * is a valid conf\httpd.conf environment to start.
+ *
+ * We reached this phase by avoiding errors that would cause
+ * these options to fail unexpectedly in another process.
+ */
+
+ if (!strcasecmp(signal_arg, "install")) {
+ /* Service install happens in the rewrite_args hooks. If we
+ * made it this far, the server configuration is clean and the
+ * service will successfully start.
+ */
+ apr_pool_destroy(s->process->pool);
+ apr_terminate();
+ exit(0);
+ }
+ if (!strcasecmp(signal_arg, "config")) {
+ /* Service reconfiguration happens in the rewrite_args hooks. If we
+ * made it this far, the server configuration is clean and the
+ * service will successfully start.
+ */
+ apr_pool_destroy(s->process->pool);
+ apr_terminate();
+ exit(0);
+ }
+
+ if (!strcasecmp(signal_arg, "start")) {
+ ap_listen_rec *lr;
+
+ /* Close the listening sockets. */
+ for (lr = ap_listeners; lr; lr = lr->next) {
+ apr_socket_close(lr->sd);
+ lr->active = 0;
+ }
+ rv = mpm_service_start(ptemp, inst_argc, inst_argv);
+ apr_pool_destroy(s->process->pool);
+ apr_terminate();
+ exit (rv);
+ }
+
+ if (!strcasecmp(signal_arg, "restart")) {
+ mpm_signal_service(ptemp, 1);
+ apr_pool_destroy(s->process->pool);
+ apr_terminate();
+ exit (rv);
+ }
+
+ if (parent_pid == my_pid)
+ {
+ if (restart_num++ == 1)
+ {
+ /* This code should be run once in the parent and not run
+ * across a restart
+ */
+ PSECURITY_ATTRIBUTES sa = GetNullACL(); /* returns NULL if invalid (Win95?) */
+ setup_signal_names(apr_psprintf(pconf,"ap%d", parent_pid));
+
+ ap_log_pid(pconf, ap_pid_fname);
+
+ /* Create shutdown event, apPID_shutdown, where PID is the parent
+ * Apache process ID. Shutdown is signaled by 'apache -k shutdown'.
+ */
+ shutdown_event = CreateEvent(sa, FALSE, FALSE, signal_shutdown_name);
+ if (!shutdown_event) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Cannot create shutdown event %s", signal_shutdown_name);
+ CleanNullACL((void *)sa);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Create restart event, apPID_restart, where PID is the parent
+ * Apache process ID. Restart is signaled by 'apache -k restart'.
+ */
+ restart_event = CreateEvent(sa, FALSE, FALSE, signal_restart_name);
+ if (!restart_event) {
+ CloseHandle(shutdown_event);
+ ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf,
+ "Parent: Cannot create restart event %s", signal_restart_name);
+ CleanNullACL((void *)sa);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ CleanNullACL((void *)sa);
+
+ /* Now that we are flying at 15000 feet...
+ * wipe out the Win95 service console,
+ * signal the SCM the WinNT service started, or
+ * if not a service, setup console handlers instead.
+ */
+ if (!strcasecmp(signal_arg, "runservice"))
+ {
+ if (osver.dwPlatformId != VER_PLATFORM_WIN32_NT)
+ {
+ rv = mpm_service_to_start(&service_name,
+ s->process->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf,
+ "%s: Unable to start the service manager.",
+ service_name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ else /* ! -k runservice */
+ {
+ mpm_start_console_handler();
+ }
+
+ /* Create the start mutex, as an unnamed object for security.
+ * Ths start mutex is used during a restart to prevent more than
+ * one child process from entering the accept loop at once.
+ */
+ rv = apr_proc_mutex_create(&start_mutex, NULL,
+ APR_LOCK_DEFAULT,
+ ap_server_conf->process->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf,
+ "%s: Unable to create the start_mutex.",
+ service_name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ }
+ else /* parent_pid != my_pid */
+ {
+ mpm_start_child_console_handler();
+ }
+ return OK;
+}
+
+/* This really should be a post_config hook, but the error log is already
+ * redirected by that point, so we need to do this in the open_logs phase.
+ */
+static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ /* Initialize shared static objects.
+ */
+ ap_server_conf = s;
+
+ if (parent_pid != my_pid) {
+ return OK;
+ }
+
+ /* We cannot initialize our listeners if we are restarting
+ * (the parent process already has glomed on to them)
+ * nor should we do so for service reconfiguration
+ * (since the service may already be running.)
+ */
+ if (!strcasecmp(signal_arg, "restart")
+ || !strcasecmp(signal_arg, "config")) {
+ return OK;
+ }
+
+ if (ap_setup_listeners(s) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0,
+ NULL, "no listening sockets available, shutting down");
+ return DONE;
+ }
+
+ return OK;
+}
+
+static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s)
+{
+ apr_status_t rv;
+
+ setup_signal_names(apr_psprintf(pchild,"ap%d", parent_pid));
+
+ /* This is a child process, not in single process mode */
+ if (!one_process) {
+ /* Set up events and the scoreboard */
+ get_handles_from_parent(s, &exit_event, &start_mutex,
+ &ap_scoreboard_shm);
+
+ /* Set up the listeners */
+ get_listeners_from_parent(s);
+
+ ap_my_generation = ap_scoreboard_image->global->running_generation;
+ }
+ else {
+ /* Single process mode - this lock doesn't even need to exist */
+ rv = apr_proc_mutex_create(&start_mutex, signal_name_prefix,
+ APR_LOCK_DEFAULT, s->process->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf,
+ "%s child %d: Unable to init the start_mutex.",
+ service_name, my_pid);
+ exit(APEXIT_CHILDINIT);
+ }
+
+ /* Borrow the shutdown_even as our _child_ loop exit event */
+ exit_event = shutdown_event;
+ }
+}
+
+
+AP_DECLARE(int) ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s )
+{
+ static int restart = 0; /* Default is "not a restart" */
+
+ if (!restart) {
+ first_thread_limit = thread_limit;
+ }
+
+ if (changed_limit_at_restart) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, ap_server_conf,
+ "WARNING: Attempt to change ThreadLimit ignored "
+ "during restart");
+ changed_limit_at_restart = 0;
+ }
+
+ /* ### If non-graceful restarts are ever introduced - we need to rerun
+ * the pre_mpm hook on subsequent non-graceful restarts. But Win32
+ * has only graceful style restarts - and we need this hook to act
+ * the same on Win32 as on Unix.
+ */
+ if (!restart && ((parent_pid == my_pid) || one_process)) {
+ /* Set up the scoreboard. */
+ if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
+ return 1;
+ }
+ }
+
+ if ((parent_pid != my_pid) || one_process)
+ {
+ /* The child process or in one_process (debug) mode
+ */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Child process is running", my_pid);
+
+ child_main(pconf);
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf,
+ "Child %d: Child process is exiting", my_pid);
+ return 1;
+ }
+ else
+ {
+ /* A real-honest to goodness parent */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+
+ restart = master_main(ap_server_conf, shutdown_event, restart_event);
+
+ if (!restart)
+ {
+ /* Shutting down. Clean up... */
+ const char *pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+
+ if (pidfile != NULL && unlink(pidfile) == 0) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS,
+ ap_server_conf, "removed PID file %s (pid=%ld)",
+ pidfile, GetCurrentProcessId());
+ }
+ apr_proc_mutex_destroy(start_mutex);
+
+ CloseHandle(restart_event);
+ CloseHandle(shutdown_event);
+
+ return 1;
+ }
+ }
+
+ return 0; /* Restart */
+}
+
+static void winnt_hooks(apr_pool_t *p)
+{
+ /* The prefork open_logs phase must run before the core's, or stderr
+ * will be redirected to a file, and the messages won't print to the
+ * console.
+ */
+ static const char *const aszSucc[] = {"core.c", NULL};
+
+ ap_hook_pre_config(winnt_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(winnt_post_config, NULL, NULL, 0);
+ ap_hook_child_init(winnt_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_open_logs(winnt_open_logs, NULL, aszSucc, APR_HOOK_MIDDLE);
+}
+
+AP_MODULE_DECLARE_DATA module mpm_winnt_module = {
+ MPM20_MODULE_STUFF,
+ winnt_rewrite_args, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ winnt_cmds, /* command apr_table_t */
+ winnt_hooks /* register_hooks */
+};
+
+#endif /* def WIN32 */
diff --git a/server/mpm/winnt/mpm_winnt.h b/server/mpm/winnt/mpm_winnt.h
new file mode 100644
index 00000000..2d9a7526
--- /dev/null
+++ b/server/mpm/winnt/mpm_winnt.h
@@ -0,0 +1,130 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file mpm_winnt.h
+ * @brief WinNT MPM specific
+ *
+ * @addtogroup APACHE_MPM_WINNT
+ * @{
+ */
+
+#ifndef APACHE_MPM_WINNT_H
+#define APACHE_MPM_WINNT_H
+
+#include "ap_listen.h"
+
+/* From service.c: */
+
+#define SERVICE_APACHE_RESTART 128
+
+#ifndef AP_DEFAULT_SERVICE_NAME
+#define AP_DEFAULT_SERVICE_NAME "Apache2"
+#endif
+
+#define SERVICECONFIG9X "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"
+#define SERVICECONFIG "System\\CurrentControlSet\\Services\\%s"
+#define SERVICEPARAMS "System\\CurrentControlSet\\Services\\%s\\Parameters"
+
+apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
+ const char *set_name);
+apr_status_t mpm_merge_service_args(apr_pool_t *p, apr_array_header_t *args,
+ int fixed_args);
+
+apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p);
+apr_status_t mpm_service_started(void);
+apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
+ char const* const* argv, int reconfig);
+apr_status_t mpm_service_uninstall(void);
+
+apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
+ char const* const* argv);
+
+void mpm_signal_service(apr_pool_t *ptemp, int signal);
+
+void mpm_service_stopping(void);
+
+void mpm_start_console_handler(void);
+void mpm_start_child_console_handler(void);
+
+/* From nt_eventlog.c: */
+
+void mpm_nt_eventlog_stderr_open(char *display_name, apr_pool_t *p);
+void mpm_nt_eventlog_stderr_flush(void);
+
+/* From winnt.c: */
+
+extern int use_acceptex;
+extern int winnt_mpm_state;
+extern OSVERSIONINFO osver;
+extern void clean_child_exit(int);
+
+void setup_signal_names(char *prefix);
+
+typedef enum {
+ SIGNAL_PARENT_SHUTDOWN,
+ SIGNAL_PARENT_RESTART,
+ SIGNAL_PARENT_RESTART_GRACEFUL
+} ap_signal_parent_e;
+AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type);
+
+/*
+ * The Windoes MPM uses a queue of completion contexts that it passes
+ * between the accept threads and the worker threads. Declare the
+ * functions to access the queue and the structures passed on the
+ * queue in the header file to enable modules to access them
+ * if necessary. The queue resides in the MPM.
+ */
+#ifdef CONTAINING_RECORD
+#undef CONTAINING_RECORD
+#endif
+#define CONTAINING_RECORD(address, type, field) ((type *)( \
+ (PCHAR)(address) - \
+ (PCHAR)(&((type *)0)->field)))
+#if APR_HAVE_IPV6
+#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN6)+16)
+#else
+#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN)+16)
+#endif
+
+typedef struct CompContext {
+ struct CompContext *next;
+ OVERLAPPED Overlapped;
+ apr_socket_t *sock;
+ SOCKET accept_socket;
+ char buff[2*PADDED_ADDR_SIZE];
+ struct sockaddr *sa_server;
+ int sa_server_len;
+ struct sockaddr *sa_client;
+ int sa_client_len;
+ apr_pool_t *ptrans;
+ apr_bucket_alloc_t *ba;
+ short socket_family;
+} COMP_CONTEXT, *PCOMP_CONTEXT;
+
+typedef enum {
+ IOCP_CONNECTION_ACCEPTED = 1,
+ IOCP_WAIT_FOR_RECEIVE = 2,
+ IOCP_WAIT_FOR_TRANSMITFILE = 3,
+ IOCP_SHUTDOWN = 4
+} io_state_e;
+
+PCOMP_CONTEXT mpm_get_completion_context(void);
+void mpm_recycle_completion_context(PCOMP_CONTEXT pCompContext);
+apr_status_t mpm_post_completion_context(PCOMP_CONTEXT pCompContext, io_state_e state);
+void hold_console_open_on_error(void);
+#endif /* APACHE_MPM_WINNT_H */
+/** @} */
diff --git a/server/mpm/winnt/nt_eventlog.c b/server/mpm/winnt/nt_eventlog.c
new file mode 100644
index 00000000..a0ae68c9
--- /dev/null
+++ b/server/mpm/winnt/nt_eventlog.c
@@ -0,0 +1,189 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CORE_PRIVATE
+
+#include "httpd.h"
+#include "http_log.h"
+#include "mpm_winnt.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "ap_regkey.h"
+
+static char *display_name = NULL;
+static HANDLE stderr_thread = NULL;
+static HANDLE stderr_ready;
+
+static DWORD WINAPI service_stderr_thread(LPVOID hPipe)
+{
+ HANDLE hPipeRead = (HANDLE) hPipe;
+ HANDLE hEventSource;
+ char errbuf[256];
+ char *errmsg = errbuf;
+ const char *errarg[9];
+ DWORD errres;
+ ap_regkey_t *regkey;
+ apr_status_t rv;
+ apr_pool_t *p;
+
+ apr_pool_create_ex(&p, NULL, NULL, NULL);
+
+ errarg[0] = "The Apache service named";
+ errarg[1] = display_name;
+ errarg[2] = "reported the following error:\r\n>>>";
+ errarg[3] = errbuf;
+ errarg[4] = NULL;
+ errarg[5] = NULL;
+ errarg[6] = NULL;
+ errarg[7] = NULL;
+ errarg[8] = NULL;
+
+ /* What are we going to do in here, bail on the user? not. */
+ if ((rv = ap_regkey_open(&regkey, AP_REGKEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Services\\"
+ "EventLog\\Application\\Apache Service",
+ APR_READ | APR_WRITE | APR_CREATE, p))
+ == APR_SUCCESS)
+ {
+ DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
+ EVENTLOG_INFORMATION_TYPE;
+
+ /* The stock message file */
+ ap_regkey_value_set(regkey, "EventMessageFile",
+ "%SystemRoot%\\System32\\netmsg.dll",
+ AP_REGKEY_EXPAND, p);
+
+ ap_regkey_value_raw_set(regkey, "TypesSupported", &dwData,
+ sizeof(dwData), REG_DWORD, p);
+ ap_regkey_close(regkey);
+ }
+
+ hEventSource = RegisterEventSourceW(NULL, L"Apache Service");
+
+ SetEvent(stderr_ready);
+
+ while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1))
+ {
+ if ((errmsg > errbuf) || !apr_isspace(*errmsg))
+ {
+ ++errmsg;
+ if ((*(errmsg - 1) == '\n')
+ || (errmsg >= errbuf + sizeof(errbuf) - 1))
+ {
+ while ((errmsg > errbuf) && apr_isspace(*(errmsg - 1))) {
+ --errmsg;
+ }
+ *errmsg = '\0';
+
+ /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9'
+ * The event code in netmsg.dll is 3299
+ */
+ ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0,
+ 3299, NULL, 9, 0, errarg, NULL);
+ errmsg = errbuf;
+ }
+ }
+ }
+
+ if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) {
+ apr_snprintf(errbuf, sizeof(errbuf),
+ "Win32 error %d reading stderr pipe stream\r\n",
+ GetLastError());
+
+ ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0,
+ 3299, NULL, 9, 0, errarg, NULL);
+ }
+
+ CloseHandle(hPipeRead);
+ DeregisterEventSource(hEventSource);
+ CloseHandle(stderr_thread);
+ stderr_thread = NULL;
+ apr_pool_destroy(p);
+ return 0;
+}
+
+
+void mpm_nt_eventlog_stderr_flush(void)
+{
+ HANDLE cleanup_thread = stderr_thread;
+
+ if (cleanup_thread) {
+ HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE);
+ fclose(stderr);
+ CloseHandle(hErr);
+ WaitForSingleObject(cleanup_thread, 30000);
+ CloseHandle(cleanup_thread);
+ }
+}
+
+
+void mpm_nt_eventlog_stderr_open(char *argv0, apr_pool_t *p)
+{
+ SECURITY_ATTRIBUTES sa;
+ HANDLE hProc = GetCurrentProcess();
+ HANDLE hPipeRead = NULL;
+ HANDLE hPipeWrite = NULL;
+ HANDLE hDup = NULL;
+ DWORD threadid;
+ int fd;
+
+ display_name = argv0;
+
+ /* Create a pipe to send stderr messages to the system error log.
+ *
+ * _dup2() duplicates the write handle inheritable for us.
+ */
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = FALSE;
+ CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0);
+ ap_assert(hPipeRead && hPipeWrite);
+
+ stderr_ready = CreateEvent(NULL, FALSE, FALSE, NULL);
+ stderr_thread = CreateThread(NULL, 0, service_stderr_thread,
+ (LPVOID) hPipeRead, 0, &threadid);
+ ap_assert(stderr_ready && stderr_thread);
+
+ WaitForSingleObject(stderr_ready, INFINITE);
+
+ /* Flush stderr and unset its buffer, then commit and replace stderr.
+ * This is typically a noop for Win2K/XP since services with NULL std
+ * handles [but valid FILE *'s, oddly enough], but is required
+ * for NT 4.0 and to use this code outside of services.
+ */
+ fflush(stderr);
+ setvbuf(stderr, NULL, _IONBF, 0);
+ _commit(2 /* stderr */);
+ fd = _open_osfhandle((long) hPipeWrite,
+ _O_WRONLY | _O_BINARY);
+ _dup2(fd, 2);
+ _close(fd);
+ _setmode(2, _O_BINARY);
+
+ /* hPipeWrite was _close()'ed above, and _dup2()'ed
+ * to fd 2 creating a new, inherited Win32 handle.
+ * Recover that real handle from fd 2.
+ */
+ hPipeWrite = (HANDLE)_get_osfhandle(2);
+
+ SetStdHandle(STD_ERROR_HANDLE, hPipeWrite);
+
+ /* The code above _will_ corrupt the StdHandle...
+ * and we must do so anyways. We set this up only
+ * after we initialized the posix stderr API.
+ */
+ ap_open_stderr_log(p);
+}
diff --git a/server/mpm/winnt/service.c b/server/mpm/winnt/service.c
new file mode 100644
index 00000000..3f51aa75
--- /dev/null
+++ b/server/mpm/winnt/service.c
@@ -0,0 +1,1346 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This module ALONE requires the window message API from user.h
+ * and the default APR include of windows.h will omit it, so
+ * preload the API symbols now...
+ */
+
+#define CORE_PRIVATE
+#define _WINUSER_
+
+#include "httpd.h"
+#include "http_log.h"
+#include "mpm_winnt.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "ap_regkey.h"
+
+#ifdef NOUSER
+#undef NOUSER
+#endif
+#undef _WINUSER_
+#include <winuser.h>
+
+static char *mpm_service_name = NULL;
+static char *mpm_display_name = NULL;
+
+static struct
+{
+ HANDLE mpm_thread; /* primary thread handle of the apache server */
+ HANDLE service_thread; /* thread service/monitor handle */
+ DWORD service_thread_id;/* thread service/monitor ID */
+ HANDLE service_init; /* controller thread init mutex */
+ HANDLE service_term; /* NT service thread kill signal */
+ SERVICE_STATUS ssStatus;
+ SERVICE_STATUS_HANDLE hServiceStatus;
+} globdat;
+
+static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
+
+
+#define PRODREGKEY "SOFTWARE\\" AP_SERVER_BASEVENDOR "\\" \
+ AP_SERVER_BASEPRODUCT "\\" AP_SERVER_BASEREVISION
+
+/*
+ * Get the server root from the registry into 'dir' which is
+ * size bytes long. Returns 0 if the server root was found
+ * or if the serverroot key does not exist (in which case
+ * dir will contain an empty string), or -1 if there was
+ * an error getting the key.
+ */
+apr_status_t ap_registry_get_server_root(apr_pool_t *p, char **buf)
+{
+ apr_status_t rv;
+ ap_regkey_t *key;
+
+ if ((rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, PRODREGKEY,
+ APR_READ, p)) == APR_SUCCESS) {
+ rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
+ ap_regkey_close(key);
+ if (rv == APR_SUCCESS)
+ return rv;
+ }
+
+ if ((rv = ap_regkey_open(&key, AP_REGKEY_CURRENT_USER, PRODREGKEY,
+ APR_READ, p)) == APR_SUCCESS) {
+ rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
+ ap_regkey_close(key);
+ if (rv == APR_SUCCESS)
+ return rv;
+ }
+
+ *buf = NULL;
+ return rv;
+}
+
+
+/* The service configuration's is stored under the following trees:
+ *
+ * HKLM\System\CurrentControlSet\Services\[service name]
+ *
+ * \DisplayName
+ * \ImagePath
+ * \Parameters\ConfigArgs
+ *
+ * For Win9x, the launch service command is stored under:
+ *
+ * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name]
+ */
+
+
+/* exit() for Win32 is macro mapped (horrible, we agree) that allows us
+ * to catch the non-zero conditions and inform the console process that
+ * the application died, and hang on to the console a bit longer.
+ *
+ * The macro only maps for http_main.c and other sources that include
+ * the service.h header, so we best assume it's an error to exit from
+ * _any_ other module.
+ *
+ * If ap_real_exit_code is reset to 0, it will not be set or trigger this
+ * behavior on exit. All service and child processes are expected to
+ * reset this flag to zero to avoid undesireable side effects.
+ */
+AP_DECLARE_DATA int ap_real_exit_code = 1;
+
+void hold_console_open_on_error(void)
+{
+ HANDLE hConIn;
+ HANDLE hConErr;
+ DWORD result;
+ time_t start;
+ time_t remains;
+ char *msg = "Note the errors or messages above, "
+ "and press the <ESC> key to exit. ";
+ CONSOLE_SCREEN_BUFFER_INFO coninfo;
+ INPUT_RECORD in;
+ char count[16];
+
+ if (!ap_real_exit_code)
+ return;
+ hConIn = GetStdHandle(STD_INPUT_HANDLE);
+ hConErr = GetStdHandle(STD_ERROR_HANDLE);
+ if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
+ return;
+ if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) || !result)
+ return;
+ if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
+ return;
+ if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
+ return;
+
+ start = time(NULL);
+ do
+ {
+ while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
+ {
+ if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
+ return;
+ if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
+ && (in.Event.KeyEvent.uChar.AsciiChar == 27))
+ return;
+ if (in.EventType == MOUSE_EVENT
+ && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
+ return;
+ }
+ remains = ((start + 30) - time(NULL));
+ sprintf (count, "%d...", remains);
+ if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
+ return;
+ if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL)
+ || !result)
+ return;
+ }
+ while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
+}
+
+static BOOL die_on_logoff = FALSE;
+
+static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+/* This is the WndProc procedure for our invisible window.
+ * When the user shuts down the system, this window is sent
+ * a signal WM_ENDSESSION. We clean up by signaling Apache
+ * to shut down, and idle until Apache's primary thread quits.
+ */
+ if ((msg == WM_ENDSESSION)
+ && (die_on_logoff || (lParam != ENDSESSION_LOGOFF)))
+ {
+ ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
+ if (wParam)
+ /* Don't leave this message until we are dead! */
+ WaitForSingleObject(globdat.mpm_thread, 30000);
+ return 0;
+ }
+ return (DefWindowProc(hWnd, msg, wParam, lParam));
+}
+
+static DWORD WINAPI monitor_service_9x_thread(void *service_name)
+{
+ /* When running as a service under Windows 9x, there is no console
+ * window present, and no ConsoleCtrlHandler to call when the system
+ * is shutdown. If the WatchWindow thread is created with a NULL
+ * service_name argument, then the ...SystemMonitor window class is
+ * used to create the "Apache" window to watch for logoff and shutdown.
+ * If the service_name is provided, the ...ServiceMonitor window class
+ * is used to create the window named by the service_name argument,
+ * and the logoff message is ignored.
+ */
+ WNDCLASS wc;
+ HWND hwndMain;
+ MSG msg;
+
+ wc.style = CS_GLOBALCLASS;
+ wc.lpfnWndProc = monitor_service_9x_proc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = NULL;
+ wc.hIcon = NULL;
+ wc.hCursor = NULL;
+ wc.hbrBackground = NULL;
+ wc.lpszMenuName = NULL;
+ if (service_name)
+ wc.lpszClassName = "ApacheWin95ServiceMonitor";
+ else
+ wc.lpszClassName = "ApacheWin95SystemMonitor";
+
+ die_on_logoff = service_name ? FALSE : TRUE;
+
+ if (!RegisterClass(&wc))
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
+ NULL, "Could not register window class for WatchWindow");
+ globdat.service_thread_id = 0;
+ return 0;
+ }
+
+ /* Create an invisible window */
+ hwndMain = CreateWindow(wc.lpszClassName,
+ service_name ? (char *) service_name : "Apache",
+ WS_OVERLAPPEDWINDOW & ~WS_VISIBLE,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ CW_USEDEFAULT, NULL, NULL, NULL, NULL);
+
+ if (!hwndMain)
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
+ NULL, "Could not create WatchWindow");
+ globdat.service_thread_id = 0;
+ return 0;
+ }
+
+ /* If we succeed, eliminate the console window.
+ * Signal the parent we are all set up, and
+ * watch the message queue while the window lives.
+ */
+ FreeConsole();
+ SetEvent(globdat.service_init);
+
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ if (msg.message == WM_CLOSE)
+ DestroyWindow(hwndMain);
+ else {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+ globdat.service_thread_id = 0;
+ return 0;
+}
+
+
+static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
+{
+ switch (ctrl_type)
+ {
+ case CTRL_BREAK_EVENT:
+ fprintf(stderr, "Apache server restarting...\n");
+ ap_signal_parent(SIGNAL_PARENT_RESTART);
+ return TRUE;
+ case CTRL_C_EVENT:
+ fprintf(stderr, "Apache server interrupted...\n");
+ /* for Interrupt signals, shut down the server.
+ * Tell the system we have dealt with the signal
+ * without waiting for Apache to terminate.
+ */
+ ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
+ return TRUE;
+
+ case CTRL_CLOSE_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ /* for Terminate signals, shut down the server.
+ * Wait for Apache to terminate, but respond
+ * after a reasonable time to tell the system
+ * that we did attempt to shut ourself down.
+ * THESE EVENTS WILL NOT OCCUR UNDER WIN9x!
+ */
+ fprintf(stderr, "Apache server shutdown initiated...\n");
+ ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
+ Sleep(30000);
+ return TRUE;
+ }
+
+ /* We should never get here, but this is (mostly) harmless */
+ return FALSE;
+}
+
+
+static void stop_console_handler(void)
+{
+ SetConsoleCtrlHandler(console_control_handler, FALSE);
+}
+
+
+void mpm_start_console_handler(void)
+{
+ SetConsoleCtrlHandler(console_control_handler, TRUE);
+ atexit(stop_console_handler);
+}
+
+
+/* Special situation - children of services need to mind their
+ * P's & Q's and wait quietly, ignoring the mean OS signaling
+ * shutdown and other horrors, to kill them gracefully...
+ */
+
+static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
+{
+ switch (ctrl_type)
+ {
+ case CTRL_C_EVENT:
+ case CTRL_BREAK_EVENT:
+ /* for Interrupt signals, ignore them.
+ * The system will also signal the parent process,
+ * which will terminate Apache.
+ */
+ return TRUE;
+
+ case CTRL_CLOSE_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ /* for Shutdown signals, ignore them, but... .
+ * The system will also signal the parent process,
+ * which will terminate Apache, so we need to wait.
+ */
+ Sleep(30000);
+ return TRUE;
+ }
+
+ /* We should never get here, but this is (mostly) harmless */
+ return FALSE;
+}
+
+
+static void stop_child_console_handler(void)
+{
+ SetConsoleCtrlHandler(child_control_handler, FALSE);
+}
+
+
+void mpm_start_child_console_handler(void)
+{
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
+ FreeConsole();
+ }
+ else
+ {
+ SetConsoleCtrlHandler(child_control_handler, TRUE);
+ atexit(stop_child_console_handler);
+ }
+}
+
+
+/**********************************
+ WinNT service control management
+ **********************************/
+
+static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
+{
+ static int checkPoint = 1;
+ int rv = APR_SUCCESS;
+
+ if (globdat.hServiceStatus)
+ {
+ if (currentState == SERVICE_RUNNING) {
+ globdat.ssStatus.dwWaitHint = 0;
+ globdat.ssStatus.dwCheckPoint = 0;
+ globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ }
+ else if (currentState == SERVICE_STOPPED) {
+ globdat.ssStatus.dwWaitHint = 0;
+ globdat.ssStatus.dwCheckPoint = 0;
+ if (!exitCode && globdat.ssStatus.dwCurrentState
+ != SERVICE_STOP_PENDING) {
+ /* An unexpected exit? Better to error! */
+ exitCode = 1;
+ }
+ if (exitCode) {
+ globdat.ssStatus.dwWin32ExitCode =ERROR_SERVICE_SPECIFIC_ERROR;
+ globdat.ssStatus.dwServiceSpecificExitCode = exitCode;
+ }
+ }
+ else {
+ globdat.ssStatus.dwCheckPoint = ++checkPoint;
+ globdat.ssStatus.dwControlsAccepted = 0;
+ if(waitHint)
+ globdat.ssStatus.dwWaitHint = waitHint;
+ }
+
+ globdat.ssStatus.dwCurrentState = currentState;
+
+ rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
+ }
+ return(rv);
+}
+
+/* Set the service description regardless of platform.
+ * We revert to set_service_description on NT/9x, the
+ * very long way so any Apache management program can grab the
+ * description. This would be bad on Win2000, since it wouldn't
+ * notify the service control manager of the name change.
+ */
+
+/* borrowed from mpm_winnt.c */
+extern apr_pool_t *pconf;
+
+/* Windows 2000 alone supports ChangeServiceConfig2 in order to
+ * register our server_version string... so we need some fixups
+ * to avoid binding to that function if we are on WinNT/9x.
+ */
+static void set_service_description(void)
+{
+ const char *full_description;
+ SC_HANDLE schSCManager;
+ BOOL ret = 0;
+
+ /* Nothing to do if we are a console
+ */
+ if (!mpm_service_name)
+ return;
+
+ /* Time to fix up the description, upon each successful restart
+ */
+ full_description = ap_get_server_version();
+
+ if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ && (osver.dwMajorVersion > 4)
+ && (ChangeServiceConfig2)
+ && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)))
+ {
+ SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
+ SERVICE_CHANGE_CONFIG);
+ if (schService) {
+ /* Cast is necessary, ChangeServiceConfig2 handles multiple
+ * object types, some volatile, some not.
+ */
+ /* ###: utf-ize */
+ if (ChangeServiceConfig2(schService,
+ 1 /* SERVICE_CONFIG_DESCRIPTION */,
+ (LPVOID) &full_description)) {
+ full_description = NULL;
+ }
+ CloseServiceHandle(schService);
+ }
+ CloseServiceHandle(schSCManager);
+ }
+
+ if (full_description)
+ {
+ char szPath[MAX_PATH];
+ ap_regkey_t *svckey;
+ apr_status_t rv;
+
+ /* Find the Service key that Monitor Applications iterate */
+ apr_snprintf(szPath, sizeof(szPath),
+ "SYSTEM\\CurrentControlSet\\Services\\%s",
+ mpm_service_name);
+ rv = ap_regkey_open(&svckey, AP_REGKEY_LOCAL_MACHINE, szPath,
+ APR_READ | APR_WRITE, pconf);
+ if (rv != APR_SUCCESS) {
+ return;
+ }
+ /* Attempt to set the Description value for our service */
+ ap_regkey_value_set(svckey, "Description", full_description, 0, pconf);
+ ap_regkey_close(svckey);
+ }
+}
+
+/* handle the SCM's ControlService() callbacks to our service */
+
+static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
+{
+ if (dwCtrlCode == SERVICE_CONTROL_STOP)
+ {
+ ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
+ ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
+ return;
+ }
+ if (dwCtrlCode == SERVICE_APACHE_RESTART)
+ {
+ ap_signal_parent(SIGNAL_PARENT_RESTART);
+ ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
+ return;
+ }
+
+ ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
+}
+
+
+/* service_nt_main_fn is outside of the call stack and outside of the
+ * primary server thread... so now we _really_ need a placeholder!
+ * The winnt_rewrite_args has created and shared mpm_new_argv with us.
+ */
+extern apr_array_header_t *mpm_new_argv;
+
+/* ###: utf-ize */
+static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
+{
+ const char *ignored;
+
+ /* args and service names live in the same pool */
+ mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
+
+ memset(&globdat.ssStatus, 0, sizeof(globdat.ssStatus));
+ globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
+ globdat.ssStatus.dwCheckPoint = 1;
+
+ /* ###: utf-ize */
+ if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
+ NULL, "Failure registering service handler");
+ return;
+ }
+
+ /* Report status, no errors, and buy 3 more seconds */
+ ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
+
+ /* We need to append all the command arguments passed via StartService()
+ * to our running service... which just got here via the SCM...
+ * but we hvae no interest in argv[0] for the mpm_new_argv list.
+ */
+ if (argc > 1)
+ {
+ char **cmb_data;
+
+ mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
+ cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
+
+ /* mpm_new_argv remains first (of lower significance) */
+ memcpy (cmb_data, mpm_new_argv->elts,
+ mpm_new_argv->elt_size * mpm_new_argv->nelts);
+
+ /* Service args follow from StartService() invocation */
+ memcpy (cmb_data + mpm_new_argv->nelts, argv + 1,
+ mpm_new_argv->elt_size * (argc - 1));
+
+ /* The replacement arg list is complete */
+ mpm_new_argv->elts = (char *)cmb_data;
+ mpm_new_argv->nelts = mpm_new_argv->nalloc;
+ }
+
+ /* Let the main thread continue now... but hang on to the
+ * signal_monitor event so we can take further action
+ */
+ SetEvent(globdat.service_init);
+
+ WaitForSingleObject(globdat.service_term, INFINITE);
+}
+
+
+DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+ SERVICE_TABLE_ENTRY dispatchTable[] =
+ {
+ { "", service_nt_main_fn },
+ { NULL, NULL }
+ };
+
+ /* ###: utf-ize */
+ if (!StartServiceCtrlDispatcher(dispatchTable))
+ {
+ /* This is a genuine failure of the SCM. */
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "Error starting service control dispatcher");
+ }
+
+ return (rv);
+}
+
+
+apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
+ const char *set_name)
+{
+ char key_name[MAX_PATH];
+ ap_regkey_t *key;
+ apr_status_t rv;
+
+ /* ### Needs improvement, on Win2K the user can _easily_
+ * change the display name to a string that doesn't reflect
+ * the internal service name + whitespace!
+ */
+ mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
+ apr_collapse_spaces((char*) mpm_service_name, set_name);
+ apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
+ rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, APR_READ, pconf);
+ if (rv == APR_SUCCESS) {
+ rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf);
+ ap_regkey_close(key);
+ }
+ if (rv != APR_SUCCESS) {
+ /* Take the given literal name if there is no service entry */
+ mpm_display_name = apr_pstrdup(p, set_name);
+ }
+ *display_name = mpm_display_name;
+ return rv;
+}
+
+
+apr_status_t mpm_merge_service_args(apr_pool_t *p,
+ apr_array_header_t *args,
+ int fixed_args)
+{
+ apr_array_header_t *svc_args = NULL;
+ char conf_key[MAX_PATH];
+ char **cmb_data;
+ apr_status_t rv;
+ ap_regkey_t *key;
+
+ apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
+ rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p);
+ if (rv == APR_SUCCESS) {
+ rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p);
+ ap_regkey_close(key);
+ }
+ if (rv != APR_SUCCESS) {
+ if (rv == ERROR_FILE_NOT_FOUND) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
+ "No ConfigArgs registered for %s, perhaps "
+ "this service is not installed?",
+ mpm_service_name);
+ return APR_SUCCESS;
+ }
+ else
+ return (rv);
+ }
+
+ if (!svc_args || svc_args->nelts == 0) {
+ return (APR_SUCCESS);
+ }
+
+ /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
+ * call appended the arguments passed by StartService(), so it's
+ * time to _prepend_ the default arguments for the server from
+ * the service's default arguments (all others override them)...
+ */
+ args->nalloc = args->nelts + svc_args->nelts;
+ cmb_data = malloc(args->nalloc * sizeof(const char *));
+
+ /* First three args (argv[0], -f, path) remain first */
+ memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
+
+ /* Service args follow from service registry array */
+ memcpy(cmb_data + fixed_args, svc_args->elts,
+ svc_args->elt_size * svc_args->nelts);
+
+ /* Remaining new args follow */
+ memcpy(cmb_data + fixed_args + svc_args->nelts,
+ (const char **)args->elts + fixed_args,
+ args->elt_size * (args->nelts - fixed_args));
+
+ args->elts = (char *)cmb_data;
+ args->nelts = args->nalloc;
+
+ return APR_SUCCESS;
+}
+
+
+void service_stopped(void)
+{
+ /* Still have a thread & window to clean up, so signal now */
+ if (globdat.service_thread)
+ {
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ /* Stop logging to the event log */
+ mpm_nt_eventlog_stderr_flush();
+
+ /* Cause the service_nt_main_fn to complete */
+ ReleaseMutex(globdat.service_term);
+
+ ReportStatusToSCMgr(SERVICE_STOPPED, // service state
+ NO_ERROR, // exit code
+ 0); // wait hint
+ }
+ else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
+ {
+ RegisterServiceProcess(0, 0);
+ PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0);
+ }
+
+ WaitForSingleObject(globdat.service_thread, 5000);
+ CloseHandle(globdat.service_thread);
+ }
+}
+
+
+apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p)
+{
+ HANDLE hProc = GetCurrentProcess();
+ HANDLE hThread = GetCurrentThread();
+ HANDLE waitfor[2];
+
+ /* Prevent holding open the (hidden) console */
+ ap_real_exit_code = 0;
+
+ /* GetCurrentThread returns a psuedo-handle, we need
+ * a real handle for another thread to wait upon.
+ */
+ if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
+ 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+ return APR_ENOTHREAD;
+ }
+
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
+ globdat.service_term = CreateMutex(NULL, TRUE, NULL);
+ if (!globdat.service_init || !globdat.service_term) {
+ return APR_EGENERAL;
+ }
+
+ globdat.service_thread = CreateThread(NULL, 0, service_nt_dispatch_thread,
+ NULL, 0, &globdat.service_thread_id);
+ }
+ else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
+ {
+ if (!RegisterServiceProcess(0, 1))
+ return GetLastError();
+
+ globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (!globdat.service_init) {
+ return APR_EGENERAL;
+ }
+
+ globdat.service_thread = CreateThread(NULL, 0, monitor_service_9x_thread,
+ (LPVOID) mpm_service_name, 0,
+ &globdat.service_thread_id);
+ }
+
+ if (!globdat.service_thread) {
+ return APR_ENOTHREAD;
+ }
+
+ waitfor[0] = globdat.service_init;
+ waitfor[1] = globdat.service_thread;
+
+ /* Wait for controlling thread init or termination */
+ if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
+ return APR_ENOTHREAD;
+ }
+
+ atexit(service_stopped);
+ *display_name = mpm_display_name;
+ return APR_SUCCESS;
+}
+
+
+apr_status_t mpm_service_started(void)
+{
+ set_service_description();
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ ReportStatusToSCMgr(SERVICE_RUNNING, // service state
+ NO_ERROR, // exit code
+ 0); // wait hint
+ }
+ return APR_SUCCESS;
+}
+
+
+void mpm_service_stopping(void)
+{
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state
+ NO_ERROR, // exit code
+ 30000); // wait hint
+}
+
+
+apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
+ const char * const * argv, int reconfig)
+{
+ char key_name[MAX_PATH];
+ char exe_path[MAX_PATH];
+ char *launch_cmd;
+ ap_regkey_t *key;
+ apr_status_t rv;
+
+ fprintf(stderr,reconfig ? "Reconfiguring the %s service\n"
+ : "Installing the %s service\n", mpm_display_name);
+
+ /* ###: utf-ize */
+ if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
+ {
+ apr_status_t rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "GetModuleFileName failed");
+ return rv;
+ }
+
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ SC_HANDLE schService;
+ SC_HANDLE schSCManager;
+
+ schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
+ SC_MANAGER_CREATE_SERVICE);
+ if (!schSCManager) {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "Failed to open the WinNT service manager");
+ return (rv);
+ }
+
+ launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
+
+ if (reconfig) {
+ /* ###: utf-ize */
+ schService = OpenService(schSCManager, mpm_service_name,
+ SERVICE_CHANGE_CONFIG);
+ if (!schService) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
+ apr_get_os_error(), NULL,
+ "OpenService failed");
+ }
+ /* ###: utf-ize */
+ else if (!ChangeServiceConfig(schService,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_AUTO_START,
+ SERVICE_ERROR_NORMAL,
+ launch_cmd, NULL, NULL,
+ "Tcpip\0Afd\0", NULL, NULL,
+ mpm_display_name)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
+ apr_get_os_error(), NULL,
+ "ChangeServiceConfig failed");
+ /* !schService aborts configuration below */
+ CloseServiceHandle(schService);
+ schService = NULL;
+ }
+ }
+ else {
+ /* RPCSS is the Remote Procedure Call (RPC) Locator required
+ * for DCOM communication pipes. I am far from convinced we
+ * should add this to the default service dependencies, but
+ * be warned that future apache modules or ISAPI dll's may
+ * depend on it.
+ */
+ /* ###: utf-ize */
+ schService = CreateService(schSCManager, // SCManager database
+ mpm_service_name, // name of service
+ mpm_display_name, // name to display
+ SERVICE_ALL_ACCESS, // access required
+ SERVICE_WIN32_OWN_PROCESS, // service type
+ SERVICE_AUTO_START, // start type
+ SERVICE_ERROR_NORMAL, // error control type
+ launch_cmd, // service's binary
+ NULL, // no load svc group
+ NULL, // no tag identifier
+ "Tcpip\0Afd\0", // dependencies
+ NULL, // use SYSTEM account
+ NULL); // no password
+
+ if (!schService)
+ {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "Failed to create WinNT Service Profile");
+ CloseServiceHandle(schSCManager);
+ return (rv);
+ }
+ }
+
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ }
+ else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
+ {
+ /* Store the launch command in the registry */
+ launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice",
+ exe_path, mpm_service_name);
+ rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X,
+ APR_READ | APR_WRITE | APR_CREATE, pconf);
+ if (rv == APR_SUCCESS) {
+ rv = ap_regkey_value_set(key, mpm_service_name,
+ launch_cmd, 0, pconf);
+ ap_regkey_close(key);
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to add the RunServices registry entry.",
+ mpm_display_name);
+ return (rv);
+ }
+
+ apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
+ rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
+ APR_READ | APR_WRITE | APR_CREATE, pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to create the registry service key.",
+ mpm_display_name);
+ return (rv);
+ }
+ rv = ap_regkey_value_set(key, "ImagePath", launch_cmd, 0, pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to store ImagePath in the registry.",
+ mpm_display_name);
+ ap_regkey_close(key);
+ return (rv);
+ }
+ rv = ap_regkey_value_set(key, "DisplayName",
+ mpm_display_name, 0, pconf);
+ ap_regkey_close(key);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to store DisplayName in the registry.",
+ mpm_display_name);
+ return (rv);
+ }
+ }
+
+ set_service_description();
+
+ /* For both WinNT & Win9x store the service ConfigArgs in the registry...
+ */
+ apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
+ rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
+ APR_READ | APR_WRITE | APR_CREATE, pconf);
+ if (rv == APR_SUCCESS) {
+ rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf);
+ ap_regkey_close(key);
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to store the ConfigArgs in the registry.",
+ mpm_display_name);
+ return (rv);
+ }
+ fprintf(stderr,"The %s service is successfully installed.\n", mpm_display_name);
+ return APR_SUCCESS;
+}
+
+
+apr_status_t mpm_service_uninstall(void)
+{
+ char key_name[MAX_PATH];
+ apr_status_t rv;
+
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ SC_HANDLE schService;
+ SC_HANDLE schSCManager;
+
+ fprintf(stderr,"Removing the %s service\n", mpm_display_name);
+
+ schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
+ SC_MANAGER_CONNECT);
+ if (!schSCManager) {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "Failed to open the WinNT service manager.");
+ return (rv);
+ }
+
+ /* ###: utf-ize */
+ schService = OpenService(schSCManager, mpm_service_name, DELETE);
+
+ if (!schService) {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: OpenService failed", mpm_display_name);
+ return (rv);
+ }
+
+ /* assure the service is stopped before continuing
+ *
+ * This may be out of order... we might not be able to be
+ * granted all access if the service is running anyway.
+ *
+ * And do we want to make it *this easy* for them
+ * to uninstall their service unintentionally?
+ */
+ // ap_stop_service(schService);
+
+ if (DeleteService(schService) == 0) {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to delete the service.", mpm_display_name);
+ return (rv);
+ }
+
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ }
+ else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
+ {
+ apr_status_t rv2, rv3;
+ ap_regkey_t *key;
+ fprintf(stderr,"Removing the %s service\n", mpm_display_name);
+
+ /* TODO: assure the service is stopped before continuing */
+
+ rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X,
+ APR_READ | APR_WRITE | APR_CREATE, pconf);
+ if (rv == APR_SUCCESS) {
+ rv = ap_regkey_value_remove(key, mpm_service_name, pconf);
+ ap_regkey_close(key);
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to remove the RunServices registry "
+ "entry.", mpm_display_name);
+ }
+
+ /* we blast Services/us, not just the Services/us/Parameters branch */
+ apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
+ rv2 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf);
+ apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
+ rv3 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf);
+ rv2 = (rv2 != APR_SUCCESS) ? rv2 : rv3;
+ if (rv2 != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv2, NULL,
+ "%s: Failed to remove the service config from the "
+ "registry.", mpm_display_name);
+ }
+ rv = (rv != APR_SUCCESS) ? rv : rv2;
+ if (rv != APR_SUCCESS)
+ return rv;
+ }
+ fprintf(stderr,"The %s service has been removed successfully.\n", mpm_display_name);
+ return APR_SUCCESS;
+}
+
+
+/* signal_service_transition is a simple thunk to signal the service
+ * and monitor its successful transition. If the signal passed is 0,
+ * then the caller is assumed to already have performed some service
+ * operation to be monitored (such as StartService), and no actual
+ * ControlService signal is sent.
+ */
+
+static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
+{
+ if (signal && !ControlService(schService, signal, &globdat.ssStatus))
+ return FALSE;
+
+ do {
+ Sleep(1000);
+ if (!QueryServiceStatus(schService, &globdat.ssStatus))
+ return FALSE;
+ } while (globdat.ssStatus.dwCurrentState == pending);
+
+ return (globdat.ssStatus.dwCurrentState == complete);
+}
+
+
+apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
+ const char * const * argv)
+{
+ apr_status_t rv;
+
+ fprintf(stderr,"Starting the %s service\n", mpm_display_name);
+
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ char **start_argv;
+ SC_HANDLE schService;
+ SC_HANDLE schSCManager;
+
+ schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
+ SC_MANAGER_CONNECT);
+ if (!schSCManager) {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "Failed to open the WinNT service manager");
+ return (rv);
+ }
+
+ /* ###: utf-ize */
+ schService = OpenService(schSCManager, mpm_service_name,
+ SERVICE_START | SERVICE_QUERY_STATUS);
+ if (!schService) {
+ rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "%s: Failed to open the service.", mpm_display_name);
+ CloseServiceHandle(schSCManager);
+ return (rv);
+ }
+
+ if (QueryServiceStatus(schService, &globdat.ssStatus)
+ && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
+ "Service %s is already started!", mpm_display_name);
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ return 0;
+ }
+
+ start_argv = malloc((argc + 1) * sizeof(const char **));
+ memcpy(start_argv, argv, argc * sizeof(const char **));
+ start_argv[argc] = NULL;
+
+ rv = APR_EINIT;
+ /* ###: utf-ize */
+ if (StartService(schService, argc, start_argv)
+ && signal_service_transition(schService, 0, /* test only */
+ SERVICE_START_PENDING,
+ SERVICE_RUNNING))
+ rv = APR_SUCCESS;
+
+ if (rv != APR_SUCCESS)
+ rv = apr_get_os_error();
+
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ }
+ else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
+ {
+ STARTUPINFO si; /* Filled in prior to call to CreateProcess */
+ PROCESS_INFORMATION pi; /* filled in on call to CreateProcess */
+ char exe_path[MAX_PATH];
+ char exe_cmd[MAX_PATH * 4];
+ char *next_arg;
+ int i;
+
+ /* Locate the active top level window named service_name
+ * provided the class is ApacheWin95ServiceMonitor
+ */
+ if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
+ "Service %s is already started!", mpm_display_name);
+ return 0;
+ }
+
+ /* This may not appear intuitive, but Win9x will not allow a process
+ * to detach from the console without releasing the entire console.
+ * Ergo, we must spawn a new process for the service to get back our
+ * console window.
+ * The config is pre-flighted, so there should be no danger of failure.
+ */
+
+ if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
+ {
+ apr_status_t rv = apr_get_os_error();
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
+ "GetModuleFileName failed");
+ return rv;
+ }
+
+ apr_snprintf(exe_cmd, sizeof(exe_cmd),
+ "\"%s\" -n %s -k runservice",
+ exe_path, mpm_service_name);
+ next_arg = strchr(exe_cmd, '\0');
+ for (i = 0; i < argc; ++i) {
+ apr_snprintf(next_arg, sizeof(exe_cmd) - (next_arg - exe_cmd),
+ " \"%s\"", argv[i]);
+ next_arg = strchr(exe_cmd, '\0');
+ }
+
+ memset(&si, 0, sizeof(si));
+ memset(&pi, 0, sizeof(pi));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE; /* This might be redundant */
+
+ rv = APR_EINIT;
+ if (CreateProcess(NULL, exe_cmd, NULL, NULL, FALSE,
+ DETACHED_PROCESS, /* Creation flags */
+ NULL, NULL, &si, &pi))
+ {
+ DWORD code;
+ while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) {
+ if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
+ rv = APR_SUCCESS;
+ break;
+ }
+ Sleep (1000);
+ }
+ }
+
+ if (rv != APR_SUCCESS)
+ rv = apr_get_os_error();
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+
+ if (rv == APR_SUCCESS)
+ fprintf(stderr,"The %s service is running.\n", mpm_display_name);
+ else
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "%s: Failed to start the service process.",
+ mpm_display_name);
+
+ return rv;
+}
+
+
+/* signal is zero to stop, non-zero for restart */
+
+void mpm_signal_service(apr_pool_t *ptemp, int signal)
+{
+ int success = FALSE;
+
+ if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ SC_HANDLE schService;
+ SC_HANDLE schSCManager;
+
+ schSCManager = OpenSCManager(NULL, NULL, // default machine & database
+ SC_MANAGER_CONNECT);
+
+ if (!schSCManager) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
+ "Failed to open the NT Service Manager");
+ return;
+ }
+
+ /* ###: utf-ize */
+ schService = OpenService(schSCManager, mpm_service_name,
+ SERVICE_INTERROGATE | SERVICE_QUERY_STATUS |
+ SERVICE_USER_DEFINED_CONTROL |
+ SERVICE_START | SERVICE_STOP);
+
+ if (schService == NULL) {
+ /* Could not open the service */
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
+ "Failed to open the %s Service", mpm_display_name);
+ CloseServiceHandle(schSCManager);
+ return;
+ }
+
+ if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
+ "Query of Service %s failed", mpm_display_name);
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ return;
+ }
+
+ if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
+ fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ return;
+ }
+
+ fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
+ signal ? "restarting" : "stopping");
+
+ if (!signal)
+ success = signal_service_transition(schService,
+ SERVICE_CONTROL_STOP,
+ SERVICE_STOP_PENDING,
+ SERVICE_STOPPED);
+ else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
+ mpm_service_start(ptemp, 0, NULL);
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ return;
+ }
+ else
+ success = signal_service_transition(schService,
+ SERVICE_APACHE_RESTART,
+ SERVICE_START_PENDING,
+ SERVICE_RUNNING);
+
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ }
+ else /* !isWindowsNT() */
+ {
+ DWORD service_pid;
+ HANDLE hwnd;
+ char prefix[20];
+ /* Locate the active top level window named service_name
+ * provided the class is ApacheWin95ServiceMonitor
+ */
+ hwnd = FindWindow("ApacheWin95ServiceMonitor", mpm_service_name);
+ if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
+ globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
+ else
+ {
+ globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
+ if (!signal) {
+ fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
+ return;
+ }
+ }
+
+ fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
+ signal ? "restarting" : "stopping");
+
+ apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
+ setup_signal_names(prefix);
+
+ if (!signal)
+ {
+ int ticks = 60;
+ ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
+ while (--ticks)
+ {
+ if (!IsWindow(hwnd)) {
+ success = TRUE;
+ break;
+ }
+ Sleep(1000);
+ }
+ }
+ else /* !stop */
+ {
+ /* TODO: Aught to add a little test to the restart logic, and
+ * store the restart counter in the window's user dword.
+ * Then we can hang on and report a successful restart. But
+ * that's a project for another day.
+ */
+ if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
+ mpm_service_start(ptemp, 0, NULL);
+ return;
+ }
+ else {
+ success = TRUE;
+ ap_signal_parent(SIGNAL_PARENT_RESTART);
+ }
+ }
+ }
+
+ if (success)
+ fprintf(stderr,"The %s service has %s.\n", mpm_display_name,
+ signal ? "restarted" : "stopped");
+ else
+ fprintf(stderr,"Failed to %s the %s service.\n",
+ signal ? "restart" : "stop", mpm_display_name);
+}
diff --git a/server/mpm/worker/Makefile.in b/server/mpm/worker/Makefile.in
new file mode 100644
index 00000000..b45b8483
--- /dev/null
+++ b/server/mpm/worker/Makefile.in
@@ -0,0 +1,5 @@
+
+LTLIBRARY_NAME = libworker.la
+LTLIBRARY_SOURCES = worker.c fdqueue.c pod.c
+
+include $(top_srcdir)/build/ltlib.mk
diff --git a/server/mpm/worker/config5.m4 b/server/mpm/worker/config5.m4
new file mode 100644
index 00000000..cc131348
--- /dev/null
+++ b/server/mpm/worker/config5.m4
@@ -0,0 +1,6 @@
+dnl ## XXX - Need a more thorough check of the proper flags to use
+
+if test "$MPM_NAME" = "worker" ; then
+ AC_CHECK_FUNCS(pthread_kill)
+ APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile)
+fi
diff --git a/server/mpm/worker/fdqueue.c b/server/mpm/worker/fdqueue.c
new file mode 100644
index 00000000..6b5485b3
--- /dev/null
+++ b/server/mpm/worker/fdqueue.c
@@ -0,0 +1,384 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fdqueue.h"
+#include "apr_atomic.h"
+
+typedef struct recycled_pool {
+ apr_pool_t *pool;
+ struct recycled_pool *next;
+} recycled_pool;
+
+struct fd_queue_info_t {
+ apr_uint32_t idlers;
+ apr_thread_mutex_t *idlers_mutex;
+ apr_thread_cond_t *wait_for_idler;
+ int terminated;
+ int max_idlers;
+ recycled_pool *recycled_pools;
+};
+
+static apr_status_t queue_info_cleanup(void *data_)
+{
+ fd_queue_info_t *qi = data_;
+ apr_thread_cond_destroy(qi->wait_for_idler);
+ apr_thread_mutex_destroy(qi->idlers_mutex);
+
+ /* Clean up any pools in the recycled list */
+ for (;;) {
+ struct recycled_pool *first_pool = qi->recycled_pools;
+ if (first_pool == NULL) {
+ break;
+ }
+ if (apr_atomic_casptr((volatile void**)&(qi->recycled_pools), first_pool->next,
+ first_pool) == first_pool) {
+ apr_pool_destroy(first_pool->pool);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_queue_info_create(fd_queue_info_t **queue_info,
+ apr_pool_t *pool, int max_idlers)
+{
+ apr_status_t rv;
+ fd_queue_info_t *qi;
+
+ qi = apr_palloc(pool, sizeof(*qi));
+ memset(qi, 0, sizeof(*qi));
+
+ rv = apr_thread_mutex_create(&qi->idlers_mutex, APR_THREAD_MUTEX_DEFAULT,
+ pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ rv = apr_thread_cond_create(&qi->wait_for_idler, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ qi->recycled_pools = NULL;
+ qi->max_idlers = max_idlers;
+ apr_pool_cleanup_register(pool, qi, queue_info_cleanup,
+ apr_pool_cleanup_null);
+
+ *queue_info = qi;
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_queue_info_set_idle(fd_queue_info_t *queue_info,
+ apr_pool_t *pool_to_recycle)
+{
+ apr_status_t rv;
+ int prev_idlers;
+
+ /* If we have been given a pool to recycle, atomically link
+ * it into the queue_info's list of recycled pools
+ */
+ if (pool_to_recycle) {
+ struct recycled_pool *new_recycle;
+ new_recycle = (struct recycled_pool *)apr_palloc(pool_to_recycle,
+ sizeof(*new_recycle));
+ new_recycle->pool = pool_to_recycle;
+ for (;;) {
+ new_recycle->next = queue_info->recycled_pools;
+ if (apr_atomic_casptr((volatile void**)&(queue_info->recycled_pools),
+ new_recycle, new_recycle->next) ==
+ new_recycle->next) {
+ break;
+ }
+ }
+ }
+
+ /* Atomically increment the count of idle workers */
+ for (;;) {
+ prev_idlers = queue_info->idlers;
+ if (apr_atomic_cas32(&(queue_info->idlers), prev_idlers + 1,
+ prev_idlers) == prev_idlers) {
+ break;
+ }
+ }
+
+ /* If this thread just made the idle worker count nonzero,
+ * wake up the listener. */
+ if (prev_idlers == 0) {
+ rv = apr_thread_mutex_lock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ rv = apr_thread_cond_signal(queue_info->wait_for_idler);
+ if (rv != APR_SUCCESS) {
+ apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ return rv;
+ }
+ rv = apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info,
+ apr_pool_t **recycled_pool)
+{
+ apr_status_t rv;
+
+ *recycled_pool = NULL;
+
+ /* Block if the count of idle workers is zero */
+ if (queue_info->idlers == 0) {
+ rv = apr_thread_mutex_lock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ /* Re-check the idle worker count to guard against a
+ * race condition. Now that we're in the mutex-protected
+ * region, one of two things may have happened:
+ * - If the idle worker count is still zero, the
+ * workers are all still busy, so it's safe to
+ * block on a condition variable.
+ * - If the idle worker count is nonzero, then a
+ * worker has become idle since the first check
+ * of queue_info->idlers above. It's possible
+ * that the worker has also signaled the condition
+ * variable--and if so, the listener missed it
+ * because it wasn't yet blocked on the condition
+ * variable. But if the idle worker count is
+ * now nonzero, it's safe for this function to
+ * return immediately.
+ */
+ if (queue_info->idlers == 0) {
+ rv = apr_thread_cond_wait(queue_info->wait_for_idler,
+ queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ apr_status_t rv2;
+ rv2 = apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ if (rv2 != APR_SUCCESS) {
+ return rv2;
+ }
+ return rv;
+ }
+ }
+ rv = apr_thread_mutex_unlock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ /* Atomically decrement the idle worker count */
+ apr_atomic_dec32(&(queue_info->idlers));
+
+ /* Atomically pop a pool from the recycled list */
+ for (;;) {
+ struct recycled_pool *first_pool = queue_info->recycled_pools;
+ if (first_pool == NULL) {
+ break;
+ }
+ if (apr_atomic_casptr((volatile void**)&(queue_info->recycled_pools), first_pool->next,
+ first_pool) == first_pool) {
+ *recycled_pool = first_pool->pool;
+ break;
+ }
+ }
+
+ if (queue_info->terminated) {
+ return APR_EOF;
+ }
+ else {
+ return APR_SUCCESS;
+ }
+}
+
+apr_status_t ap_queue_info_term(fd_queue_info_t *queue_info)
+{
+ apr_status_t rv;
+ rv = apr_thread_mutex_lock(queue_info->idlers_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ queue_info->terminated = 1;
+ apr_thread_cond_broadcast(queue_info->wait_for_idler);
+ return apr_thread_mutex_unlock(queue_info->idlers_mutex);
+}
+
+/**
+ * Detects when the fd_queue_t is full. This utility function is expected
+ * to be called from within critical sections, and is not threadsafe.
+ */
+#define ap_queue_full(queue) ((queue)->nelts == (queue)->bounds)
+
+/**
+ * Detects when the fd_queue_t is empty. This utility function is expected
+ * to be called from within critical sections, and is not threadsafe.
+ */
+#define ap_queue_empty(queue) ((queue)->nelts == 0)
+
+/**
+ * Callback routine that is called to destroy this
+ * fd_queue_t when its pool is destroyed.
+ */
+static apr_status_t ap_queue_destroy(void *data)
+{
+ fd_queue_t *queue = data;
+
+ /* Ignore errors here, we can't do anything about them anyway.
+ * XXX: We should at least try to signal an error here, it is
+ * indicative of a programmer error. -aaron */
+ apr_thread_cond_destroy(queue->not_empty);
+ apr_thread_mutex_destroy(queue->one_big_mutex);
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Initialize the fd_queue_t.
+ */
+apr_status_t ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a)
+{
+ int i;
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_create(&queue->one_big_mutex,
+ APR_THREAD_MUTEX_DEFAULT, a)) != APR_SUCCESS) {
+ return rv;
+ }
+ if ((rv = apr_thread_cond_create(&queue->not_empty, a)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ queue->data = apr_palloc(a, queue_capacity * sizeof(fd_queue_elem_t));
+ queue->bounds = queue_capacity;
+ queue->nelts = 0;
+
+ /* Set all the sockets in the queue to NULL */
+ for (i = 0; i < queue_capacity; ++i)
+ queue->data[i].sd = NULL;
+
+ apr_pool_cleanup_register(a, queue, ap_queue_destroy, apr_pool_cleanup_null);
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Push a new socket onto the queue.
+ *
+ * precondition: ap_queue_info_wait_for_idler has already been called
+ * to reserve an idle worker thread
+ */
+apr_status_t ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p)
+{
+ fd_queue_elem_t *elem;
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ AP_DEBUG_ASSERT(!queue->terminated);
+ AP_DEBUG_ASSERT(!ap_queue_full(queue));
+
+ elem = &queue->data[queue->nelts];
+ elem->sd = sd;
+ elem->p = p;
+ queue->nelts++;
+
+ apr_thread_cond_signal(queue->not_empty);
+
+ if ((rv = apr_thread_mutex_unlock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+/**
+ * Retrieves the next available socket from the queue. If there are no
+ * sockets available, it will block until one becomes available.
+ * Once retrieved, the socket is placed into the address specified by
+ * 'sd'.
+ */
+apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p)
+{
+ fd_queue_elem_t *elem;
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Keep waiting until we wake up and find that the queue is not empty. */
+ if (ap_queue_empty(queue)) {
+ if (!queue->terminated) {
+ apr_thread_cond_wait(queue->not_empty, queue->one_big_mutex);
+ }
+ /* If we wake up and it's still empty, then we were interrupted */
+ if (ap_queue_empty(queue)) {
+ rv = apr_thread_mutex_unlock(queue->one_big_mutex);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (queue->terminated) {
+ return APR_EOF; /* no more elements ever again */
+ }
+ else {
+ return APR_EINTR;
+ }
+ }
+ }
+
+ elem = &queue->data[--queue->nelts];
+ *sd = elem->sd;
+ *p = elem->p;
+#ifdef AP_DEBUG
+ elem->sd = NULL;
+ elem->p = NULL;
+#endif /* AP_DEBUG */
+
+ rv = apr_thread_mutex_unlock(queue->one_big_mutex);
+ return rv;
+}
+
+apr_status_t ap_queue_interrupt_all(fd_queue_t *queue)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+ apr_thread_cond_broadcast(queue->not_empty);
+ return apr_thread_mutex_unlock(queue->one_big_mutex);
+}
+
+apr_status_t ap_queue_term(fd_queue_t *queue)
+{
+ apr_status_t rv;
+
+ if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+ /* we must hold one_big_mutex when setting this... otherwise,
+ * we could end up setting it and waking everybody up just after a
+ * would-be popper checks it but right before they block
+ */
+ queue->terminated = 1;
+ if ((rv = apr_thread_mutex_unlock(queue->one_big_mutex)) != APR_SUCCESS) {
+ return rv;
+ }
+ return ap_queue_interrupt_all(queue);
+}
diff --git a/server/mpm/worker/fdqueue.h b/server/mpm/worker/fdqueue.h
new file mode 100644
index 00000000..7de082a0
--- /dev/null
+++ b/server/mpm/worker/fdqueue.h
@@ -0,0 +1,73 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file worker/fdqueue.h
+ * @brief fd queue declarations
+ *
+ * @addtogroup APACHE_MPM_WORKER
+ * @{
+ */
+
+#ifndef FDQUEUE_H
+#define FDQUEUE_H
+#include "httpd.h"
+#include <stdlib.h>
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+#include <sys/types.h>
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#include <apr_errno.h>
+
+typedef struct fd_queue_info_t fd_queue_info_t;
+
+apr_status_t ap_queue_info_create(fd_queue_info_t **queue_info,
+ apr_pool_t *pool, int max_idlers);
+apr_status_t ap_queue_info_set_idle(fd_queue_info_t *queue_info,
+ apr_pool_t *pool_to_recycle);
+apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info,
+ apr_pool_t **recycled_pool);
+apr_status_t ap_queue_info_term(fd_queue_info_t *queue_info);
+
+struct fd_queue_elem_t {
+ apr_socket_t *sd;
+ apr_pool_t *p;
+};
+typedef struct fd_queue_elem_t fd_queue_elem_t;
+
+struct fd_queue_t {
+ fd_queue_elem_t *data;
+ int nelts;
+ int bounds;
+ apr_thread_mutex_t *one_big_mutex;
+ apr_thread_cond_t *not_empty;
+ int terminated;
+};
+typedef struct fd_queue_t fd_queue_t;
+
+apr_status_t ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a);
+apr_status_t ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p);
+apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p);
+apr_status_t ap_queue_interrupt_all(fd_queue_t *queue);
+apr_status_t ap_queue_term(fd_queue_t *queue);
+
+#endif /* FDQUEUE_H */
+/** @} */
diff --git a/server/mpm/worker/mpm.h b/server/mpm/worker/mpm.h
new file mode 100644
index 00000000..335f4b6a
--- /dev/null
+++ b/server/mpm/worker/mpm.h
@@ -0,0 +1,62 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file worker/mpm.h
+ * @brief Unix Worker MPM
+ *
+ * @defgroup APACHE_MPM_WORKER Unix Worker MPM
+ * @ingroup APACHE_OS_UNIX APACHE_MPM
+ * @{
+ */
+
+#include "scoreboard.h"
+#include "unixd.h"
+
+#ifndef APACHE_MPM_WORKER_H
+#define APACHE_MPM_WORKER_H
+
+#define WORKER_MPM
+
+#define MPM_NAME "Worker"
+
+#define AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+#define AP_MPM_WANT_WAIT_OR_TIMEOUT
+#define AP_MPM_WANT_PROCESS_CHILD_STATUS
+#define AP_MPM_WANT_SET_PIDFILE
+#define AP_MPM_WANT_SET_SCOREBOARD
+#define AP_MPM_WANT_SET_LOCKFILE
+#define AP_MPM_WANT_SET_MAX_REQUESTS
+#define AP_MPM_WANT_SET_COREDUMPDIR
+#define AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+#define AP_MPM_WANT_SIGNAL_SERVER
+#define AP_MPM_WANT_SET_MAX_MEM_FREE
+#define AP_MPM_WANT_SET_STACKSIZE
+#define AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
+#define AP_MPM_WANT_FATAL_SIGNAL_HANDLER
+#define AP_MPM_DISABLE_NAGLE_ACCEPTED_SOCK
+
+#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid)
+#define MPM_NOTE_CHILD_KILLED(i) (MPM_CHILD_PID(i) = 0)
+#define MPM_ACCEPT_FUNC unixd_accept
+
+extern int ap_threads_per_child;
+extern int ap_max_daemons_limit;
+extern server_rec *ap_server_conf;
+extern char ap_coredump_dir[MAX_STRING_LEN];
+
+#endif /* APACHE_MPM_WORKER_H */
+/** @} */
diff --git a/server/mpm/worker/mpm_default.h b/server/mpm/worker/mpm_default.h
new file mode 100644
index 00000000..a48f6725
--- /dev/null
+++ b/server/mpm/worker/mpm_default.h
@@ -0,0 +1,78 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file worker/mpm_default.h
+ * @brief Worker MPM defaults
+ *
+ * @addtogroup APACHE_MPM_WORKER
+ * @{
+ */
+
+#ifndef APACHE_MPM_DEFAULT_H
+#define APACHE_MPM_DEFAULT_H
+
+/* Number of servers to spawn off by default --- also, if fewer than
+ * this free when the caretaker checks, it will spawn more.
+ */
+#ifndef DEFAULT_START_DAEMON
+#define DEFAULT_START_DAEMON 3
+#endif
+
+/* Maximum number of *free* server processes --- more than this, and
+ * they will die off.
+ */
+
+#ifndef DEFAULT_MAX_FREE_DAEMON
+#define DEFAULT_MAX_FREE_DAEMON 10
+#endif
+
+/* Minimum --- fewer than this, and more will be created */
+
+#ifndef DEFAULT_MIN_FREE_DAEMON
+#define DEFAULT_MIN_FREE_DAEMON 3
+#endif
+
+#ifndef DEFAULT_THREADS_PER_CHILD
+#define DEFAULT_THREADS_PER_CHILD 25
+#endif
+
+/* File used for accept locking, when we use a file */
+#ifndef DEFAULT_LOCKFILE
+#define DEFAULT_LOCKFILE DEFAULT_REL_RUNTIMEDIR "/accept.lock"
+#endif
+
+/* Where the main/parent process's pid is logged */
+#ifndef DEFAULT_PIDLOG
+#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid"
+#endif
+
+/*
+ * Interval, in microseconds, between scoreboard maintenance.
+ */
+#ifndef SCOREBOARD_MAINTENANCE_INTERVAL
+#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000
+#endif
+
+/* Number of requests to try to handle in a single process. If <= 0,
+ * the children don't die off.
+ */
+#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD
+#define DEFAULT_MAX_REQUESTS_PER_CHILD 10000
+#endif
+
+#endif /* AP_MPM_DEFAULT_H */
+/** @} */
diff --git a/server/mpm/worker/pod.c b/server/mpm/worker/pod.c
new file mode 100644
index 00000000..77ad1c90
--- /dev/null
+++ b/server/mpm/worker/pod.c
@@ -0,0 +1,110 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pod.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod)
+{
+ apr_status_t rv;
+
+ *pod = apr_palloc(p, sizeof(**pod));
+ rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+/*
+ apr_file_pipe_timeout_set((*pod)->pod_in, 0);
+*/
+ (*pod)->p = p;
+
+ /* close these before exec. */
+ apr_file_inherit_unset((*pod)->pod_in);
+ apr_file_inherit_unset((*pod)->pod_out);
+
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t *pod)
+{
+ char c;
+ apr_os_file_t fd;
+ int rc;
+
+ /* we need to surface EINTR so we'll have to grab the
+ * native file descriptor and do the OS read() ourselves
+ */
+ apr_os_file_get(&fd, pod->pod_in);
+ rc = read(fd, &c, 1);
+ if (rc == 1) {
+ switch(c) {
+ case RESTART_CHAR:
+ return AP_RESTART;
+ case GRACEFUL_CHAR:
+ return AP_GRACEFUL;
+ }
+ }
+ return AP_NORESTART;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod)
+{
+ apr_status_t rv;
+
+ rv = apr_file_close(pod->pod_out);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_file_close(pod->pod_in);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ return rv;
+}
+
+static apr_status_t pod_signal_internal(ap_pod_t *pod, int graceful)
+{
+ apr_status_t rv;
+ char char_of_death = graceful ? GRACEFUL_CHAR : RESTART_CHAR;
+ apr_size_t one = 1;
+
+ rv = apr_file_write(pod->pod_out, &char_of_death, &one);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
+ "write pipe_of_death");
+ }
+ return rv;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod, int graceful)
+{
+ return pod_signal_internal(pod, graceful);
+}
+
+AP_DECLARE(void) ap_mpm_pod_killpg(ap_pod_t *pod, int num, int graceful)
+{
+ int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ for (i = 0; i < num && rv == APR_SUCCESS; i++) {
+ rv = pod_signal_internal(pod, graceful);
+ }
+}
+
diff --git a/server/mpm/worker/pod.h b/server/mpm/worker/pod.h
new file mode 100644
index 00000000..55cad819
--- /dev/null
+++ b/server/mpm/worker/pod.h
@@ -0,0 +1,59 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file worker/pod.h
+ * @brief Worker MPM Pipe of Death
+ *
+ * @addtogroup APACHE_MPM_WORKER
+ * @{
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "mpm.h"
+#include "mpm_common.h"
+#include "ap_mpm.h"
+#include "ap_listen.h"
+#include "mpm_default.h"
+
+#define RESTART_CHAR '$'
+#define GRACEFUL_CHAR '!'
+
+#define AP_RESTART 0
+#define AP_GRACEFUL 1
+
+typedef struct ap_pod_t ap_pod_t;
+
+struct ap_pod_t {
+ apr_file_t *pod_in;
+ apr_file_t *pod_out;
+ apr_pool_t *p;
+};
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod);
+AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t *pod);
+AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod);
+AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod, int graceful);
+AP_DECLARE(void) ap_mpm_pod_killpg(ap_pod_t *pod, int num, int graceful);
+/** @} */
diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c
new file mode 100644
index 00000000..b71cd237
--- /dev/null
+++ b/server/mpm/worker/worker.c
@@ -0,0 +1,2262 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* The purpose of this MPM is to fix the design flaws in the threaded
+ * model. Because of the way that pthreads and mutex locks interact,
+ * it is basically impossible to cleanly gracefully shutdown a child
+ * process if multiple threads are all blocked in accept. This model
+ * fixes those problems.
+ */
+
+#include "apr.h"
+#include "apr_portable.h"
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_thread_proc.h"
+#include "apr_signal.h"
+#include "apr_thread_mutex.h"
+#include "apr_proc_mutex.h"
+#include "apr_poll.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#if APR_HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#ifdef HAVE_SYS_PROCESSOR_H
+#include <sys/processor.h> /* for bindprocessor() */
+#endif
+
+#if !APR_HAS_THREADS
+#error The Worker MPM requires APR threads, but they are unavailable.
+#endif
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_config.h" /* for read_config */
+#include "http_core.h" /* for get_remote_host */
+#include "http_connection.h"
+#include "ap_mpm.h"
+#include "pod.h"
+#include "mpm_common.h"
+#include "ap_listen.h"
+#include "scoreboard.h"
+#include "fdqueue.h"
+#include "mpm_default.h"
+
+#include <signal.h>
+#include <limits.h> /* for INT_MAX */
+
+/* Limit on the total --- clients will be locked out if more servers than
+ * this are needed. It is intended solely to keep the server from crashing
+ * when things get out of hand.
+ *
+ * We keep a hard maximum number of servers, for two reasons --- first off,
+ * in case something goes seriously wrong, we want to stop the fork bomb
+ * short of actually crashing the machine we're running on by filling some
+ * kernel table. Secondly, it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef DEFAULT_SERVER_LIMIT
+#define DEFAULT_SERVER_LIMIT 16
+#endif
+
+/* Admin can't tune ServerLimit beyond MAX_SERVER_LIMIT. We want
+ * some sort of compile-time limit to help catch typos.
+ */
+#ifndef MAX_SERVER_LIMIT
+#define MAX_SERVER_LIMIT 20000
+#endif
+
+/* Limit on the threads per process. Clients will be locked out if more than
+ * this * server_limit are needed.
+ *
+ * We keep this for one reason it keeps the size of the scoreboard file small
+ * enough that we can read the whole thing without worrying too much about
+ * the overhead.
+ */
+#ifndef DEFAULT_THREAD_LIMIT
+#define DEFAULT_THREAD_LIMIT 64
+#endif
+
+/* Admin can't tune ThreadLimit beyond MAX_THREAD_LIMIT. We want
+ * some sort of compile-time limit to help catch typos.
+ */
+#ifndef MAX_THREAD_LIMIT
+#define MAX_THREAD_LIMIT 20000
+#endif
+
+/*
+ * Actual definitions of config globals
+ */
+
+int ap_threads_per_child = 0; /* Worker threads per child */
+static int ap_daemons_to_start = 0;
+static int min_spare_threads = 0;
+static int max_spare_threads = 0;
+static int ap_daemons_limit = 0;
+static int server_limit = DEFAULT_SERVER_LIMIT;
+static int first_server_limit = 0;
+static int thread_limit = DEFAULT_THREAD_LIMIT;
+static int first_thread_limit = 0;
+static int changed_limit_at_restart;
+static int dying = 0;
+static int workers_may_exit = 0;
+static int start_thread_may_exit = 0;
+static int listener_may_exit = 0;
+static int requests_this_child;
+static int num_listensocks = 0;
+static int resource_shortage = 0;
+static fd_queue_t *worker_queue;
+static fd_queue_info_t *worker_queue_info;
+static int mpm_state = AP_MPMQ_STARTING;
+static int sick_child_detected;
+
+/* The structure used to pass unique initialization info to each thread */
+typedef struct {
+ int pid;
+ int tid;
+ int sd;
+} proc_info;
+
+/* Structure used to pass information to the thread responsible for
+ * creating the rest of the threads.
+ */
+typedef struct {
+ apr_thread_t **threads;
+ apr_thread_t *listener;
+ int child_num_arg;
+ apr_threadattr_t *threadattr;
+} thread_starter;
+
+#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t)
+
+/*
+ * The max child slot ever assigned, preserved across restarts. Necessary
+ * to deal with MaxClients changes across AP_SIG_GRACEFUL restarts. We
+ * use this value to optimize routines that have to scan the entire
+ * scoreboard.
+ */
+int ap_max_daemons_limit = -1;
+
+static ap_pod_t *pod;
+
+/* *Non*-shared http_main globals... */
+
+server_rec *ap_server_conf;
+
+/* The worker MPM respects a couple of runtime flags that can aid
+ * in debugging. Setting the -DNO_DETACH flag will prevent the root process
+ * from detaching from its controlling terminal. Additionally, setting
+ * the -DONE_PROCESS flag (which implies -DNO_DETACH) will get you the
+ * child_main loop running in the process which originally started up.
+ * This gives you a pretty nice debugging environment. (You'll get a SIGHUP
+ * early in standalone_main; just continue through. This is the server
+ * trying to kill off any child processes which it might have lying
+ * around --- Apache doesn't keep track of their pids, it just sends
+ * SIGHUP to the process group, ignoring it in the root process.
+ * Continue through and you'll be fine.).
+ */
+
+static int one_process = 0;
+
+#ifdef DEBUG_SIGSTOP
+int raise_sigstop_flags;
+#endif
+
+static apr_pool_t *pconf; /* Pool for config stuff */
+static apr_pool_t *pchild; /* Pool for httpd child stuff */
+
+static pid_t ap_my_pid; /* Linux getpid() doesn't work except in main
+ thread. Use this instead */
+static pid_t parent_pid;
+static apr_os_thread_t *listener_os_thread;
+
+/* Locks for accept serialization */
+static apr_proc_mutex_t *accept_mutex;
+
+#ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
+#define SAFE_ACCEPT(stmt) (ap_listeners->next ? (stmt) : APR_SUCCESS)
+#else
+#define SAFE_ACCEPT(stmt) (stmt)
+#endif
+
+/* The LISTENER_SIGNAL signal will be sent from the main thread to the
+ * listener thread to wake it up for graceful termination (what a child
+ * process from an old generation does when the admin does "apachectl
+ * graceful"). This signal will be blocked in all threads of a child
+ * process except for the listener thread.
+ */
+#define LISTENER_SIGNAL SIGHUP
+
+/* The WORKER_SIGNAL signal will be sent from the main thread to the
+ * worker threads during an ungraceful restart or shutdown.
+ * This ensures that on systems (i.e., Linux) where closing the worker
+ * socket doesn't awake the worker thread when it is polling on the socket
+ * (especially in apr_wait_for_io_or_timeout() when handling
+ * Keep-Alive connections), close_worker_sockets() and join_workers()
+ * still function in timely manner and allow ungraceful shutdowns to
+ * proceed to completion. Otherwise join_workers() doesn't return
+ * before the main process decides the child process is non-responsive
+ * and sends a SIGKILL.
+ */
+#define WORKER_SIGNAL AP_SIG_GRACEFUL
+
+/* An array of socket descriptors in use by each thread used to
+ * perform a non-graceful (forced) shutdown of the server. */
+static apr_socket_t **worker_sockets;
+
+static void close_worker_sockets(void)
+{
+ int i;
+ for (i = 0; i < ap_threads_per_child; i++) {
+ if (worker_sockets[i]) {
+ apr_socket_close(worker_sockets[i]);
+ worker_sockets[i] = NULL;
+ }
+ }
+}
+
+static void wakeup_listener(void)
+{
+ listener_may_exit = 1;
+ if (!listener_os_thread) {
+ /* XXX there is an obscure path that this doesn't handle perfectly:
+ * right after listener thread is created but before
+ * listener_os_thread is set, the first worker thread hits an
+ * error and starts graceful termination
+ */
+ return;
+ }
+ /*
+ * we should just be able to "kill(ap_my_pid, LISTENER_SIGNAL)" on all
+ * platforms and wake up the listener thread since it is the only thread
+ * with SIGHUP unblocked, but that doesn't work on Linux
+ */
+#ifdef HAVE_PTHREAD_KILL
+ pthread_kill(*listener_os_thread, LISTENER_SIGNAL);
+#else
+ kill(ap_my_pid, LISTENER_SIGNAL);
+#endif
+}
+
+#define ST_INIT 0
+#define ST_GRACEFUL 1
+#define ST_UNGRACEFUL 2
+
+static int terminate_mode = ST_INIT;
+
+static void signal_threads(int mode)
+{
+ if (terminate_mode == mode) {
+ return;
+ }
+ terminate_mode = mode;
+ mpm_state = AP_MPMQ_STOPPING;
+
+ /* in case we weren't called from the listener thread, wake up the
+ * listener thread
+ */
+ wakeup_listener();
+
+ /* for ungraceful termination, let the workers exit now;
+ * for graceful termination, the listener thread will notify the
+ * workers to exit once it has stopped accepting new connections
+ */
+ if (mode == ST_UNGRACEFUL) {
+ workers_may_exit = 1;
+ ap_queue_interrupt_all(worker_queue);
+ ap_queue_info_term(worker_queue_info);
+ close_worker_sockets(); /* forcefully kill all current connections */
+ }
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result)
+{
+ switch(query_code){
+ case AP_MPMQ_MAX_DAEMON_USED:
+ *result = ap_max_daemons_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_THREADED:
+ *result = AP_MPMQ_STATIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_IS_FORKED:
+ *result = AP_MPMQ_DYNAMIC;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_DAEMONS:
+ *result = server_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_HARD_LIMIT_THREADS:
+ *result = thread_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_THREADS:
+ *result = ap_threads_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MIN_SPARE_THREADS:
+ *result = min_spare_threads;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_DAEMONS:
+ *result = 0;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_SPARE_THREADS:
+ *result = max_spare_threads;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_REQUESTS_DAEMON:
+ *result = ap_max_requests_per_child;
+ return APR_SUCCESS;
+ case AP_MPMQ_MAX_DAEMONS:
+ *result = ap_daemons_limit;
+ return APR_SUCCESS;
+ case AP_MPMQ_MPM_STATE:
+ *result = mpm_state;
+ return APR_SUCCESS;
+ }
+ return APR_ENOTIMPL;
+}
+
+/* a clean exit from a child with proper cleanup */
+static void clean_child_exit(int code) __attribute__ ((noreturn));
+static void clean_child_exit(int code)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+ if (pchild) {
+ apr_pool_destroy(pchild);
+ }
+ exit(code);
+}
+
+static void just_die(int sig)
+{
+ clean_child_exit(0);
+}
+
+/*****************************************************************
+ * Connection structures and accounting...
+ */
+
+/* volatile just in case */
+static int volatile shutdown_pending;
+static int volatile restart_pending;
+static int volatile is_graceful;
+static volatile int child_fatal;
+ap_generation_t volatile ap_my_generation;
+
+/*
+ * ap_start_shutdown() and ap_start_restart(), below, are a first stab at
+ * functions to initiate shutdown or restart without relying on signals.
+ * Previously this was initiated in sig_term() and restart() signal handlers,
+ * but we want to be able to start a shutdown/restart from other sources --
+ * e.g. on Win32, from the service manager. Now the service manager can
+ * call ap_start_shutdown() or ap_start_restart() as appropiate. Note that
+ * these functions can also be called by the child processes, since global
+ * variables are no longer used to pass on the required action to the parent.
+ *
+ * These should only be called from the parent process itself, since the
+ * parent process will use the shutdown_pending and restart_pending variables
+ * to determine whether to shutdown or restart. The child process should
+ * call signal_parent() directly to tell the parent to die -- this will
+ * cause neither of those variable to be set, which the parent will
+ * assume means something serious is wrong (which it will be, for the
+ * child to force an exit) and so do an exit anyway.
+ */
+
+static void ap_start_shutdown(int graceful)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+ if (shutdown_pending == 1) {
+ /* Um, is this _probably_ not an error, if the user has
+ * tried to do a shutdown twice quickly, so we won't
+ * worry about reporting it.
+ */
+ return;
+ }
+ shutdown_pending = 1;
+ is_graceful = graceful;
+}
+
+/* do a graceful restart if graceful == 1 */
+static void ap_start_restart(int graceful)
+{
+ mpm_state = AP_MPMQ_STOPPING;
+ if (restart_pending == 1) {
+ /* Probably not an error - don't bother reporting it */
+ return;
+ }
+ restart_pending = 1;
+ is_graceful = graceful;
+}
+
+static void sig_term(int sig)
+{
+ ap_start_shutdown(sig == AP_SIG_GRACEFUL_STOP);
+}
+
+static void restart(int sig)
+{
+ ap_start_restart(sig == AP_SIG_GRACEFUL);
+}
+
+static void set_signals(void)
+{
+#ifndef NO_USE_SIGACTION
+ struct sigaction sa;
+#endif
+
+ if (!one_process) {
+ ap_fatal_signal_setup(ap_server_conf, pconf);
+ }
+
+#ifndef NO_USE_SIGACTION
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ sa.sa_handler = sig_term;
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGTERM)");
+#ifdef AP_SIG_GRACEFUL_STOP
+ if (sigaction(AP_SIG_GRACEFUL_STOP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(" AP_SIG_GRACEFUL_STOP_STRING ")");
+#endif
+#ifdef SIGINT
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGINT)");
+#endif
+#ifdef SIGXCPU
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGXCPU, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGXCPU)");
+#endif
+#ifdef SIGXFSZ
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGXFSZ, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGXFSZ)");
+#endif
+#ifdef SIGPIPE
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGPIPE)");
+#endif
+
+ /* we want to ignore HUPs and AP_SIG_GRACEFUL while we're busy
+ * processing one */
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, AP_SIG_GRACEFUL);
+ sa.sa_handler = restart;
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(SIGHUP)");
+ if (sigaction(AP_SIG_GRACEFUL, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf,
+ "sigaction(" AP_SIG_GRACEFUL_STRING ")");
+#else
+ if (!one_process) {
+#ifdef SIGXCPU
+ apr_signal(SIGXCPU, SIG_DFL);
+#endif /* SIGXCPU */
+#ifdef SIGXFSZ
+ apr_signal(SIGXFSZ, SIG_DFL);
+#endif /* SIGXFSZ */
+ }
+
+ apr_signal(SIGTERM, sig_term);
+#ifdef SIGHUP
+ apr_signal(SIGHUP, restart);
+#endif /* SIGHUP */
+#ifdef AP_SIG_GRACEFUL
+ apr_signal(AP_SIG_GRACEFUL, restart);
+#endif /* AP_SIG_GRACEFUL */
+#ifdef AP_SIG_GRACEFUL_STOP
+ apr_signal(AP_SIG_GRACEFUL_STOP, sig_term);
+#endif /* AP_SIG_GRACEFUL_STOP */
+#ifdef SIGPIPE
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif /* SIGPIPE */
+
+#endif
+}
+
+/*****************************************************************
+ * Here follows a long bunch of generic server bookkeeping stuff...
+ */
+
+int ap_graceful_stop_signalled(void)
+ /* XXX this is really a bad confusing obsolete name
+ * maybe it should be ap_mpm_process_exiting?
+ */
+{
+ /* note: for a graceful termination, listener_may_exit will be set before
+ * workers_may_exit, so check listener_may_exit
+ */
+ return listener_may_exit;
+}
+
+/*****************************************************************
+ * Child process main loop.
+ */
+
+static void process_socket(apr_pool_t *p, apr_socket_t *sock, int my_child_num,
+ int my_thread_num, apr_bucket_alloc_t *bucket_alloc)
+{
+ conn_rec *current_conn;
+ long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num);
+ int csd;
+ ap_sb_handle_t *sbh;
+
+ ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num);
+ apr_os_sock_get(&csd, sock);
+
+ current_conn = ap_run_create_connection(p, ap_server_conf, sock,
+ conn_id, sbh, bucket_alloc);
+ if (current_conn) {
+ ap_process_connection(current_conn, sock);
+ ap_lingering_close(current_conn);
+ }
+}
+
+/* requests_this_child has gone to zero or below. See if the admin coded
+ "MaxRequestsPerChild 0", and keep going in that case. Doing it this way
+ simplifies the hot path in worker_thread */
+static void check_infinite_requests(void)
+{
+ if (ap_max_requests_per_child) {
+ signal_threads(ST_GRACEFUL);
+ }
+ else {
+ /* wow! if you're executing this code, you may have set a record.
+ * either this child process has served over 2 billion requests, or
+ * you're running a threaded 2.0 on a 16 bit machine.
+ *
+ * I'll buy pizza and beers at Apachecon for the first person to do
+ * the former without cheating (dorking with INT_MAX, or running with
+ * uncommitted performance patches, for example).
+ *
+ * for the latter case, you probably deserve a beer too. Greg Ames
+ */
+
+ requests_this_child = INT_MAX; /* keep going */
+ }
+}
+
+static void unblock_signal(int sig)
+{
+ sigset_t sig_mask;
+
+ sigemptyset(&sig_mask);
+ sigaddset(&sig_mask, sig);
+#if defined(SIGPROCMASK_SETS_THREAD_MASK)
+ sigprocmask(SIG_UNBLOCK, &sig_mask, NULL);
+#else
+ pthread_sigmask(SIG_UNBLOCK, &sig_mask, NULL);
+#endif
+}
+
+static void dummy_signal_handler(int sig)
+{
+ /* XXX If specifying SIG_IGN is guaranteed to unblock a syscall,
+ * then we don't need this goofy function.
+ */
+}
+
+static void *listener_thread(apr_thread_t *thd, void * dummy)
+{
+ proc_info * ti = dummy;
+ int process_slot = ti->pid;
+ apr_pool_t *tpool = apr_thread_pool_get(thd);
+ void *csd = NULL;
+ apr_pool_t *ptrans = NULL; /* Pool for per-transaction stuff */
+ apr_pollset_t *pollset;
+ apr_status_t rv;
+ ap_listen_rec *lr;
+ int have_idle_worker = 0;
+ int last_poll_idx = 0;
+
+ free(ti);
+
+ /* ### check the status */
+ (void) apr_pollset_create(&pollset, num_listensocks, tpool, 0);
+
+ for (lr = ap_listeners; lr != NULL; lr = lr->next) {
+ apr_pollfd_t pfd = { 0 };
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = lr->sd;
+ pfd.reqevents = APR_POLLIN;
+ pfd.client_data = lr;
+
+ /* ### check the status */
+ (void) apr_pollset_add(pollset, &pfd);
+ }
+
+ /* Unblock the signal used to wake this thread up, and set a handler for
+ * it.
+ */
+ unblock_signal(LISTENER_SIGNAL);
+ apr_signal(LISTENER_SIGNAL, dummy_signal_handler);
+
+ /* TODO: Switch to a system where threads reuse the results from earlier
+ poll calls - manoj */
+ while (1) {
+ /* TODO: requests_this_child should be synchronized - aaron */
+ if (requests_this_child <= 0) {
+ check_infinite_requests();
+ }
+ if (listener_may_exit) break;
+
+ if (!have_idle_worker) {
+ /* the following pops a recycled ptrans pool off a stack
+ * if there is one, in addition to reserving a worker thread
+ */
+ rv = ap_queue_info_wait_for_idler(worker_queue_info,
+ &ptrans);
+ if (APR_STATUS_IS_EOF(rv)) {
+ break; /* we've been signaled to die now */
+ }
+ else if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf,
+ "apr_queue_info_wait failed. Attempting to "
+ " shutdown process gracefully.");
+ signal_threads(ST_GRACEFUL);
+ break;
+ }
+ have_idle_worker = 1;
+ }
+
+ /* We've already decremented the idle worker count inside
+ * ap_queue_info_wait_for_idler. */
+
+ if ((rv = SAFE_ACCEPT(apr_proc_mutex_lock(accept_mutex)))
+ != APR_SUCCESS) {
+ int level = APLOG_EMERG;
+
+ if (listener_may_exit) {
+ break;
+ }
+ if (ap_scoreboard_image->parent[process_slot].generation !=
+ ap_scoreboard_image->global->running_generation) {
+ level = APLOG_DEBUG; /* common to get these at restart time */
+ }
+ ap_log_error(APLOG_MARK, level, rv, ap_server_conf,
+ "apr_proc_mutex_lock failed. Attempting to shutdown "
+ "process gracefully.");
+ signal_threads(ST_GRACEFUL);
+ break; /* skip the lock release */
+ }
+
+ if (!ap_listeners->next) {
+ /* Only one listener, so skip the poll */
+ lr = ap_listeners;
+ }
+ else {
+ while (!listener_may_exit) {
+ apr_int32_t numdesc;
+ const apr_pollfd_t *pdesc;
+
+ rv = apr_pollset_poll(pollset, -1, &numdesc, &pdesc);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
+ }
+
+ /* apr_pollset_poll() will only return errors in catastrophic
+ * circumstances. Let's try exiting gracefully, for now. */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv,
+ (const server_rec *) ap_server_conf,
+ "apr_pollset_poll: (listen)");
+ signal_threads(ST_GRACEFUL);
+ }
+
+ if (listener_may_exit) break;
+
+ /* We can always use pdesc[0], but sockets at position N
+ * could end up completely starved of attention in a very
+ * busy server. Therefore, we round-robin across the
+ * returned set of descriptors. While it is possible that
+ * the returned set of descriptors might flip around and
+ * continue to starve some sockets, we happen to know the
+ * internal pollset implementation retains ordering
+ * stability of the sockets. Thus, the round-robin should
+ * ensure that a socket will eventually be serviced.
+ */
+ if (last_poll_idx >= numdesc)
+ last_poll_idx = 0;
+
+ /* Grab a listener record from the client_data of the poll
+ * descriptor, and advance our saved index to round-robin
+ * the next fetch.
+ *
+ * ### hmm... this descriptor might have POLLERR rather
+ * ### than POLLIN
+ */
+ lr = pdesc[last_poll_idx++].client_data;
+ break;
+
+ } /* while */
+
+ } /* if/else */
+
+ if (!listener_may_exit) {
+ if (ptrans == NULL) {
+ /* we can't use a recycled transaction pool this time.
+ * create a new transaction pool */
+ apr_allocator_t *allocator;
+
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ apr_pool_create_ex(&ptrans, pconf, NULL, allocator);
+ apr_allocator_owner_set(allocator, ptrans);
+ }
+ apr_pool_tag(ptrans, "transaction");
+ rv = lr->accept_func(&csd, lr, ptrans);
+ /* later we trash rv and rely on csd to indicate success/failure */
+ AP_DEBUG_ASSERT(rv == APR_SUCCESS || !csd);
+
+ if (rv == APR_EGENERAL) {
+ /* E[NM]FILE, ENOMEM, etc */
+ resource_shortage = 1;
+ signal_threads(ST_GRACEFUL);
+ }
+ if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(accept_mutex)))
+ != APR_SUCCESS) {
+ int level = APLOG_EMERG;
+
+ if (listener_may_exit) {
+ break;
+ }
+ if (ap_scoreboard_image->parent[process_slot].generation !=
+ ap_scoreboard_image->global->running_generation) {
+ level = APLOG_DEBUG; /* common to get these at restart time */
+ }
+ ap_log_error(APLOG_MARK, level, rv, ap_server_conf,
+ "apr_proc_mutex_unlock failed. Attempting to "
+ "shutdown process gracefully.");
+ signal_threads(ST_GRACEFUL);
+ }
+ if (csd != NULL) {
+ rv = ap_queue_push(worker_queue, csd, ptrans);
+ if (rv) {
+ /* trash the connection; we couldn't queue the connected
+ * socket to a worker
+ */
+ apr_socket_close(csd);
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "ap_queue_push failed");
+ }
+ else {
+ have_idle_worker = 0;
+ }
+ }
+ }
+ else {
+ if ((rv = SAFE_ACCEPT(apr_proc_mutex_unlock(accept_mutex)))
+ != APR_SUCCESS) {
+ int level = APLOG_EMERG;
+
+ if (ap_scoreboard_image->parent[process_slot].generation !=
+ ap_scoreboard_image->global->running_generation) {
+ level = APLOG_DEBUG; /* common to get these at restart time */
+ }
+ ap_log_error(APLOG_MARK, level, rv, ap_server_conf,
+ "apr_proc_mutex_unlock failed. Attempting to "
+ "shutdown process gracefully.");
+ signal_threads(ST_GRACEFUL);
+ }
+ break;
+ }
+ }
+
+ ap_close_listeners();
+ ap_queue_term(worker_queue);
+ dying = 1;
+ ap_scoreboard_image->parent[process_slot].quiescing = 1;
+
+ /* wake up the main thread */
+ kill(ap_my_pid, SIGTERM);
+
+ apr_thread_exit(thd, APR_SUCCESS);
+ return NULL;
+}
+
+/* XXX For ungraceful termination/restart, we definitely don't want to
+ * wait for active connections to finish but we may want to wait
+ * for idle workers to get out of the queue code and release mutexes,
+ * since those mutexes are cleaned up pretty soon and some systems
+ * may not react favorably (i.e., segfault) if operations are attempted
+ * on cleaned-up mutexes.
+ */
+static void * APR_THREAD_FUNC worker_thread(apr_thread_t *thd, void * dummy)
+{
+ proc_info * ti = dummy;
+ int process_slot = ti->pid;
+ int thread_slot = ti->tid;
+ apr_socket_t *csd = NULL;
+ apr_bucket_alloc_t *bucket_alloc;
+ apr_pool_t *last_ptrans = NULL;
+ apr_pool_t *ptrans; /* Pool for per-transaction stuff */
+ apr_status_t rv;
+ int is_idle = 0;
+
+ free(ti);
+
+ ap_scoreboard_image->servers[process_slot][thread_slot].pid = ap_my_pid;
+ ap_scoreboard_image->servers[process_slot][thread_slot].generation = ap_my_generation;
+ ap_update_child_status_from_indexes(process_slot, thread_slot, SERVER_STARTING, NULL);
+
+#ifdef HAVE_PTHREAD_KILL
+ unblock_signal(WORKER_SIGNAL);
+ apr_signal(WORKER_SIGNAL, dummy_signal_handler);
+#endif
+
+ while (!workers_may_exit) {
+ if (!is_idle) {
+ rv = ap_queue_info_set_idle(worker_queue_info, last_ptrans);
+ last_ptrans = NULL;
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf,
+ "ap_queue_info_set_idle failed. Attempting to "
+ "shutdown process gracefully.");
+ signal_threads(ST_GRACEFUL);
+ break;
+ }
+ is_idle = 1;
+ }
+
+ ap_update_child_status_from_indexes(process_slot, thread_slot, SERVER_READY, NULL);
+worker_pop:
+ if (workers_may_exit) {
+ break;
+ }
+ rv = ap_queue_pop(worker_queue, &csd, &ptrans);
+
+ if (rv != APR_SUCCESS) {
+ /* We get APR_EOF during a graceful shutdown once all the connections
+ * accepted by this server process have been handled.
+ */
+ if (APR_STATUS_IS_EOF(rv)) {
+ break;
+ }
+ /* We get APR_EINTR whenever ap_queue_pop() has been interrupted
+ * from an explicit call to ap_queue_interrupt_all(). This allows
+ * us to unblock threads stuck in ap_queue_pop() when a shutdown
+ * is pending.
+ *
+ * If workers_may_exit is set and this is ungraceful termination/
+ * restart, we are bound to get an error on some systems (e.g.,
+ * AIX, which sanity-checks mutex operations) since the queue
+ * may have already been cleaned up. Don't log the "error" if
+ * workers_may_exit is set.
+ */
+ else if (APR_STATUS_IS_EINTR(rv)) {
+ goto worker_pop;
+ }
+ /* We got some other error. */
+ else if (!workers_may_exit) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "ap_queue_pop failed");
+ }
+ continue;
+ }
+ is_idle = 0;
+ worker_sockets[thread_slot] = csd;
+ bucket_alloc = apr_bucket_alloc_create(ptrans);
+ process_socket(ptrans, csd, process_slot, thread_slot, bucket_alloc);
+ worker_sockets[thread_slot] = NULL;
+ requests_this_child--; /* FIXME: should be synchronized - aaron */
+ apr_pool_clear(ptrans);
+ last_ptrans = ptrans;
+ }
+
+ ap_update_child_status_from_indexes(process_slot, thread_slot,
+ (dying) ? SERVER_DEAD : SERVER_GRACEFUL, (request_rec *) NULL);
+
+ apr_thread_exit(thd, APR_SUCCESS);
+ return NULL;
+}
+
+static int check_signal(int signum)
+{
+ switch (signum) {
+ case SIGTERM:
+ case SIGINT:
+ return 1;
+ }
+ return 0;
+}
+
+static void create_listener_thread(thread_starter *ts)
+{
+ int my_child_num = ts->child_num_arg;
+ apr_threadattr_t *thread_attr = ts->threadattr;
+ proc_info *my_info;
+ apr_status_t rv;
+
+ my_info = (proc_info *)malloc(sizeof(proc_info));
+ my_info->pid = my_child_num;
+ my_info->tid = -1; /* listener thread doesn't have a thread slot */
+ my_info->sd = 0;
+ rv = apr_thread_create(&ts->listener, thread_attr, listener_thread,
+ my_info, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "apr_thread_create: unable to create listener thread");
+ /* let the parent decide how bad this really is */
+ clean_child_exit(APEXIT_CHILDSICK);
+ }
+ apr_os_thread_get(&listener_os_thread, ts->listener);
+}
+
+/* XXX under some circumstances not understood, children can get stuck
+ * in start_threads forever trying to take over slots which will
+ * never be cleaned up; for now there is an APLOG_DEBUG message issued
+ * every so often when this condition occurs
+ */
+static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy)
+{
+ thread_starter *ts = dummy;
+ apr_thread_t **threads = ts->threads;
+ apr_threadattr_t *thread_attr = ts->threadattr;
+ int child_num_arg = ts->child_num_arg;
+ int my_child_num = child_num_arg;
+ proc_info *my_info;
+ apr_status_t rv;
+ int i;
+ int threads_created = 0;
+ int listener_started = 0;
+ int loops;
+ int prev_threads_created;
+
+ /* We must create the fd queues before we start up the listener
+ * and worker threads. */
+ worker_queue = apr_pcalloc(pchild, sizeof(*worker_queue));
+ rv = ap_queue_init(worker_queue, ap_threads_per_child, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "ap_queue_init() failed");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ rv = ap_queue_info_create(&worker_queue_info, pchild,
+ ap_threads_per_child);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "ap_queue_info_create() failed");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ worker_sockets = apr_pcalloc(pchild, ap_threads_per_child
+ * sizeof(apr_socket_t *));
+
+ loops = prev_threads_created = 0;
+ while (1) {
+ /* ap_threads_per_child does not include the listener thread */
+ for (i = 0; i < ap_threads_per_child; i++) {
+ int status = ap_scoreboard_image->servers[child_num_arg][i].status;
+
+ if (status != SERVER_GRACEFUL && status != SERVER_DEAD) {
+ continue;
+ }
+
+ my_info = (proc_info *)malloc(sizeof(proc_info));
+ if (my_info == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, ap_server_conf,
+ "malloc: out of memory");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+ my_info->pid = my_child_num;
+ my_info->tid = i;
+ my_info->sd = 0;
+
+ /* We are creating threads right now */
+ ap_update_child_status_from_indexes(my_child_num, i,
+ SERVER_STARTING, NULL);
+ /* We let each thread update its own scoreboard entry. This is
+ * done because it lets us deal with tid better.
+ */
+ rv = apr_thread_create(&threads[i], thread_attr,
+ worker_thread, my_info, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "apr_thread_create: unable to create worker thread");
+ /* let the parent decide how bad this really is */
+ clean_child_exit(APEXIT_CHILDSICK);
+ }
+ threads_created++;
+ }
+ /* Start the listener only when there are workers available */
+ if (!listener_started && threads_created) {
+ create_listener_thread(ts);
+ listener_started = 1;
+ }
+ if (start_thread_may_exit || threads_created == ap_threads_per_child) {
+ break;
+ }
+ /* wait for previous generation to clean up an entry */
+ apr_sleep(apr_time_from_sec(1));
+ ++loops;
+ if (loops % 120 == 0) { /* every couple of minutes */
+ if (prev_threads_created == threads_created) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "child %" APR_PID_T_FMT " isn't taking over "
+ "slots very quickly (%d of %d)",
+ ap_my_pid, threads_created, ap_threads_per_child);
+ }
+ prev_threads_created = threads_created;
+ }
+ }
+
+ /* What state should this child_main process be listed as in the
+ * scoreboard...?
+ * ap_update_child_status_from_indexes(my_child_num, i, SERVER_STARTING,
+ * (request_rec *) NULL);
+ *
+ * This state should be listed separately in the scoreboard, in some kind
+ * of process_status, not mixed in with the worker threads' status.
+ * "life_status" is almost right, but it's in the worker's structure, and
+ * the name could be clearer. gla
+ */
+ apr_thread_exit(thd, APR_SUCCESS);
+ return NULL;
+}
+
+static void join_workers(apr_thread_t *listener, apr_thread_t **threads)
+{
+ int i;
+ apr_status_t rv, thread_rv;
+
+ if (listener) {
+ int iter;
+
+ /* deal with a rare timing window which affects waking up the
+ * listener thread... if the signal sent to the listener thread
+ * is delivered between the time it verifies that the
+ * listener_may_exit flag is clear and the time it enters a
+ * blocking syscall, the signal didn't do any good... work around
+ * that by sleeping briefly and sending it again
+ */
+
+ iter = 0;
+ while (iter < 10 &&
+#ifdef HAVE_PTHREAD_KILL
+ pthread_kill(*listener_os_thread, 0)
+#else
+ kill(ap_my_pid, 0)
+#endif
+ == 0) {
+ /* listener not dead yet */
+ apr_sleep(apr_time_make(0, 500000));
+ wakeup_listener();
+ ++iter;
+ }
+ if (iter >= 10) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "the listener thread didn't exit");
+ }
+ else {
+ rv = apr_thread_join(&thread_rv, listener);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "apr_thread_join: unable to join listener thread");
+ }
+ }
+ }
+
+ for (i = 0; i < ap_threads_per_child; i++) {
+ if (threads[i]) { /* if we ever created this thread */
+#ifdef HAVE_PTHREAD_KILL
+ apr_os_thread_t *worker_os_thread;
+
+ apr_os_thread_get(&worker_os_thread, threads[i]);
+ pthread_kill(*worker_os_thread, WORKER_SIGNAL);
+#endif
+
+ rv = apr_thread_join(&thread_rv, threads[i]);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "apr_thread_join: unable to join worker "
+ "thread %d",
+ i);
+ }
+ }
+ }
+}
+
+static void join_start_thread(apr_thread_t *start_thread_id)
+{
+ apr_status_t rv, thread_rv;
+
+ start_thread_may_exit = 1; /* tell it to give up in case it is still
+ * trying to take over slots from a
+ * previous generation
+ */
+ rv = apr_thread_join(&thread_rv, start_thread_id);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "apr_thread_join: unable to join the start "
+ "thread");
+ }
+}
+
+static void child_main(int child_num_arg)
+{
+ apr_thread_t **threads;
+ apr_status_t rv;
+ thread_starter *ts;
+ apr_threadattr_t *thread_attr;
+ apr_thread_t *start_thread_id;
+
+ mpm_state = AP_MPMQ_STARTING; /* for benefit of any hooks that run as this
+ * child initializes
+ */
+ ap_my_pid = getpid();
+ ap_fatal_signal_child_setup(ap_server_conf);
+ apr_pool_create(&pchild, pconf);
+
+ /*stuff to do before we switch id's, so we have permissions.*/
+ ap_reopen_scoreboard(pchild, NULL, 0);
+
+ rv = SAFE_ACCEPT(apr_proc_mutex_child_init(&accept_mutex, ap_lock_fname,
+ pchild));
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf,
+ "Couldn't initialize cross-process lock in child");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ if (unixd_setup_child()) {
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ap_run_child_init(pchild, ap_server_conf);
+
+ /* done with init critical section */
+
+ /* Just use the standard apr_setup_signal_thread to block all signals
+ * from being received. The child processes no longer use signals for
+ * any communication with the parent process.
+ */
+ rv = apr_setup_signal_thread();
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf,
+ "Couldn't initialize signal thread");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ if (ap_max_requests_per_child) {
+ requests_this_child = ap_max_requests_per_child;
+ }
+ else {
+ /* coding a value of zero means infinity */
+ requests_this_child = INT_MAX;
+ }
+
+ /* Setup worker threads */
+
+ /* clear the storage; we may not create all our threads immediately,
+ * and we want a 0 entry to indicate a thread which was not created
+ */
+ threads = (apr_thread_t **)calloc(1,
+ sizeof(apr_thread_t *) * ap_threads_per_child);
+ if (threads == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, errno, ap_server_conf,
+ "malloc: out of memory");
+ clean_child_exit(APEXIT_CHILDFATAL);
+ }
+
+ ts = (thread_starter *)apr_palloc(pchild, sizeof(*ts));
+
+ apr_threadattr_create(&thread_attr, pchild);
+ /* 0 means PTHREAD_CREATE_JOINABLE */
+ apr_threadattr_detach_set(thread_attr, 0);
+
+ if (ap_thread_stacksize != 0) {
+ apr_threadattr_stacksize_set(thread_attr, ap_thread_stacksize);
+ }
+
+ ts->threads = threads;
+ ts->listener = NULL;
+ ts->child_num_arg = child_num_arg;
+ ts->threadattr = thread_attr;
+
+ rv = apr_thread_create(&start_thread_id, thread_attr, start_threads,
+ ts, pchild);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf,
+ "apr_thread_create: unable to create worker thread");
+ /* let the parent decide how bad this really is */
+ clean_child_exit(APEXIT_CHILDSICK);
+ }
+
+ mpm_state = AP_MPMQ_RUNNING;
+
+ /* If we are only running in one_process mode, we will want to
+ * still handle signals. */
+ if (one_process) {
+ /* Block until we get a terminating signal. */
+ apr_signal_thread(check_signal);
+ /* make sure the start thread has finished; signal_threads()
+ * and join_workers() depend on that
+ */
+ /* XXX join_start_thread() won't be awakened if one of our
+ * threads encounters a critical error and attempts to
+ * shutdown this child
+ */
+ join_start_thread(start_thread_id);
+ signal_threads(ST_UNGRACEFUL); /* helps us terminate a little more
+ * quickly than the dispatch of the signal thread
+ * beats the Pipe of Death and the browsers
+ */
+ /* A terminating signal was received. Now join each of the
+ * workers to clean them up.
+ * If the worker already exited, then the join frees
+ * their resources and returns.
+ * If the worker hasn't exited, then this blocks until
+ * they have (then cleans up).
+ */
+ join_workers(ts->listener, threads);
+ }
+ else { /* !one_process */
+ /* remove SIGTERM from the set of blocked signals... if one of
+ * the other threads in the process needs to take us down
+ * (e.g., for MaxRequestsPerChild) it will send us SIGTERM
+ */
+ unblock_signal(SIGTERM);
+ apr_signal(SIGTERM, dummy_signal_handler);
+ /* Watch for any messages from the parent over the POD */
+ while (1) {
+ rv = ap_mpm_pod_check(pod);
+ if (rv == AP_NORESTART) {
+ /* see if termination was triggered while we slept */
+ switch(terminate_mode) {
+ case ST_GRACEFUL:
+ rv = AP_GRACEFUL;
+ break;
+ case ST_UNGRACEFUL:
+ rv = AP_RESTART;
+ break;
+ }
+ }
+ if (rv == AP_GRACEFUL || rv == AP_RESTART) {
+ /* make sure the start thread has finished;
+ * signal_threads() and join_workers depend on that
+ */
+ join_start_thread(start_thread_id);
+ signal_threads(rv == AP_GRACEFUL ? ST_GRACEFUL : ST_UNGRACEFUL);
+ break;
+ }
+ }
+
+ /* A terminating signal was received. Now join each of the
+ * workers to clean them up.
+ * If the worker already exited, then the join frees
+ * their resources and returns.
+ * If the worker hasn't exited, then this blocks until
+ * they have (then cleans up).
+ */
+ join_workers(ts->listener, threads);
+ }
+
+ free(threads);
+
+ clean_child_exit(resource_shortage ? APEXIT_CHILDSICK : 0);
+}
+
+static int make_child(server_rec *s, int slot)
+{
+ int pid;
+
+ if (slot + 1 > ap_max_daemons_limit) {
+ ap_max_daemons_limit = slot + 1;
+ }
+
+ if (one_process) {
+ set_signals();
+ ap_scoreboard_image->parent[slot].pid = getpid();
+ child_main(slot);
+ }
+
+ if ((pid = fork()) == -1) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, s,
+ "fork: Unable to fork new process");
+
+ /* fork didn't succeed. Fix the scoreboard or else
+ * it will say SERVER_STARTING forever and ever
+ */
+ ap_update_child_status_from_indexes(slot, 0, SERVER_DEAD, NULL);
+
+ /* In case system resources are maxxed out, we don't want
+ Apache running away with the CPU trying to fork over and
+ over and over again. */
+ apr_sleep(apr_time_from_sec(10));
+
+ return -1;
+ }
+
+ if (!pid) {
+#ifdef HAVE_BINDPROCESSOR
+ /* By default, AIX binds to a single processor. This bit unbinds
+ * children which will then bind to another CPU.
+ */
+ int status = bindprocessor(BINDPROCESS, (int)getpid(),
+ PROCESSOR_CLASS_ANY);
+ if (status != OK)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno,
+ ap_server_conf,
+ "processor unbind failed %d", status);
+#endif
+ RAISE_SIGSTOP(MAKE_CHILD);
+
+ apr_signal(SIGTERM, just_die);
+ child_main(slot);
+
+ clean_child_exit(0);
+ }
+ /* else */
+ if (ap_scoreboard_image->parent[slot].pid != 0) {
+ /* This new child process is squatting on the scoreboard
+ * entry owned by an exiting child process, which cannot
+ * exit until all active requests complete.
+ * Don't forget about this exiting child process, or we
+ * won't be able to kill it if it doesn't exit by the
+ * time the server is shut down.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "taking over scoreboard slot from %" APR_PID_T_FMT "%s",
+ ap_scoreboard_image->parent[slot].pid,
+ ap_scoreboard_image->parent[slot].quiescing ?
+ " (quiescing)" : "");
+ ap_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid);
+ }
+ ap_scoreboard_image->parent[slot].quiescing = 0;
+ ap_scoreboard_image->parent[slot].pid = pid;
+ return 0;
+}
+
+/* start up a bunch of children */
+static void startup_children(int number_to_start)
+{
+ int i;
+
+ for (i = 0; number_to_start && i < ap_daemons_limit; ++i) {
+ if (ap_scoreboard_image->parent[i].pid != 0) {
+ continue;
+ }
+ if (make_child(ap_server_conf, i) < 0) {
+ break;
+ }
+ --number_to_start;
+ }
+}
+
+
+/*
+ * idle_spawn_rate is the number of children that will be spawned on the
+ * next maintenance cycle if there aren't enough idle servers. It is
+ * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by
+ * without the need to spawn.
+ */
+static int idle_spawn_rate = 1;
+#ifndef MAX_SPAWN_RATE
+#define MAX_SPAWN_RATE (32)
+#endif
+static int hold_off_on_exponential_spawning;
+
+static void perform_idle_server_maintenance(void)
+{
+ int i, j;
+ int idle_thread_count;
+ worker_score *ws;
+ process_score *ps;
+ int free_length;
+ int totally_free_length = 0;
+ int free_slots[MAX_SPAWN_RATE];
+ int last_non_dead;
+ int total_non_dead;
+ int active_thread_count = 0;
+
+ /* initialize the free_list */
+ free_length = 0;
+
+ idle_thread_count = 0;
+ last_non_dead = -1;
+ total_non_dead = 0;
+
+ for (i = 0; i < ap_daemons_limit; ++i) {
+ /* Initialization to satisfy the compiler. It doesn't know
+ * that ap_threads_per_child is always > 0 */
+ int status = SERVER_DEAD;
+ int any_dying_threads = 0;
+ int any_dead_threads = 0;
+ int all_dead_threads = 1;
+
+ if (i >= ap_max_daemons_limit && totally_free_length == idle_spawn_rate)
+ break;
+ ps = &ap_scoreboard_image->parent[i];
+ for (j = 0; j < ap_threads_per_child; j++) {
+ ws = &ap_scoreboard_image->servers[i][j];
+ status = ws->status;
+
+ /* XXX any_dying_threads is probably no longer needed GLA */
+ any_dying_threads = any_dying_threads ||
+ (status == SERVER_GRACEFUL);
+ any_dead_threads = any_dead_threads || (status == SERVER_DEAD);
+ all_dead_threads = all_dead_threads &&
+ (status == SERVER_DEAD ||
+ status == SERVER_GRACEFUL);
+
+ /* We consider a starting server as idle because we started it
+ * at least a cycle ago, and if it still hasn't finished starting
+ * then we're just going to swamp things worse by forking more.
+ * So we hopefully won't need to fork more if we count it.
+ * This depends on the ordering of SERVER_READY and SERVER_STARTING.
+ */
+ if (ps->pid != 0) { /* XXX just set all_dead_threads in outer for
+ loop if no pid? not much else matters */
+ if (status <= SERVER_READY &&
+ !ps->quiescing &&
+ ps->generation == ap_my_generation) {
+ ++idle_thread_count;
+ }
+ if (status >= SERVER_READY && status < SERVER_GRACEFUL) {
+ ++active_thread_count;
+ }
+ }
+ }
+ if (any_dead_threads && totally_free_length < idle_spawn_rate
+ && free_length < MAX_SPAWN_RATE
+ && (!ps->pid /* no process in the slot */
+ || ps->quiescing)) { /* or at least one is going away */
+ if (all_dead_threads) {
+ /* great! we prefer these, because the new process can
+ * start more threads sooner. So prioritize this slot
+ * by putting it ahead of any slots with active threads.
+ *
+ * first, make room by moving a slot that's potentially still
+ * in use to the end of the array
+ */
+ free_slots[free_length] = free_slots[totally_free_length];
+ free_slots[totally_free_length++] = i;
+ }
+ else {
+ /* slot is still in use - back of the bus
+ */
+ free_slots[free_length] = i;
+ }
+ ++free_length;
+ }
+ /* XXX if (!ps->quiescing) is probably more reliable GLA */
+ if (!any_dying_threads) {
+ last_non_dead = i;
+ ++total_non_dead;
+ }
+ }
+
+ if (sick_child_detected) {
+ if (active_thread_count > 0) {
+ /* some child processes appear to be working. don't kill the
+ * whole server.
+ */
+ sick_child_detected = 0;
+ }
+ else {
+ /* looks like a basket case. give up.
+ */
+ shutdown_pending = 1;
+ child_fatal = 1;
+ ap_log_error(APLOG_MARK, APLOG_ALERT, 0,
+ ap_server_conf,
+ "No active workers found..."
+ " Apache is exiting!");
+ /* the child already logged the failure details */
+ return;
+ }
+ }
+
+ ap_max_daemons_limit = last_non_dead + 1;
+
+ if (idle_thread_count > max_spare_threads) {
+ /* Kill off one child */
+ ap_mpm_pod_signal(pod, TRUE);
+ idle_spawn_rate = 1;
+ }
+ else if (idle_thread_count < min_spare_threads) {
+ /* terminate the free list */
+ if (free_length == 0) {
+ /* only report this condition once */
+ static int reported = 0;
+
+ if (!reported) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0,
+ ap_server_conf,
+ "server reached MaxClients setting, consider"
+ " raising the MaxClients setting");
+ reported = 1;
+ }
+ idle_spawn_rate = 1;
+ }
+ else {
+ if (free_length > idle_spawn_rate) {
+ free_length = idle_spawn_rate;
+ }
+ if (idle_spawn_rate >= 8) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0,
+ ap_server_conf,
+ "server seems busy, (you may need "
+ "to increase StartServers, ThreadsPerChild "
+ "or Min/MaxSpareThreads), "
+ "spawning %d children, there are around %d idle "
+ "threads, and %d total children", free_length,
+ idle_thread_count, total_non_dead);
+ }
+ for (i = 0; i < free_length; ++i) {
+ make_child(ap_server_conf, free_slots[i]);
+ }
+ /* the next time around we want to spawn twice as many if this
+ * wasn't good enough, but not if we've just done a graceful
+ */
+ if (hold_off_on_exponential_spawning) {
+ --hold_off_on_exponential_spawning;
+ }
+ else if (idle_spawn_rate < MAX_SPAWN_RATE) {
+ idle_spawn_rate *= 2;
+ }
+ }
+ }
+ else {
+ idle_spawn_rate = 1;
+ }
+}
+
+static void server_main_loop(int remaining_children_to_start)
+{
+ int child_slot;
+ apr_exit_why_e exitwhy;
+ int status, processed_status;
+ apr_proc_t pid;
+ int i;
+
+ while (!restart_pending && !shutdown_pending) {
+ ap_wait_or_timeout(&exitwhy, &status, &pid, pconf);
+
+ if (pid.pid != -1) {
+ processed_status = ap_process_child_status(&pid, exitwhy, status);
+ if (processed_status == APEXIT_CHILDFATAL) {
+ shutdown_pending = 1;
+ child_fatal = 1;
+ return;
+ }
+ else if (processed_status == APEXIT_CHILDSICK) {
+ /* tell perform_idle_server_maintenance to check into this
+ * on the next timer pop
+ */
+ sick_child_detected = 1;
+ }
+ /* non-fatal death... note that it's gone in the scoreboard. */
+ child_slot = find_child_by_pid(&pid);
+ if (child_slot >= 0) {
+ for (i = 0; i < ap_threads_per_child; i++)
+ ap_update_child_status_from_indexes(child_slot, i, SERVER_DEAD,
+ (request_rec *) NULL);
+
+ ap_scoreboard_image->parent[child_slot].pid = 0;
+ ap_scoreboard_image->parent[child_slot].quiescing = 0;
+ if (processed_status == APEXIT_CHILDSICK) {
+ /* resource shortage, minimize the fork rate */
+ idle_spawn_rate = 1;
+ }
+ else if (remaining_children_to_start
+ && child_slot < ap_daemons_limit) {
+ /* we're still doing a 1-for-1 replacement of dead
+ * children with new children
+ */
+ make_child(ap_server_conf, child_slot);
+ --remaining_children_to_start;
+ }
+ }
+ else if (ap_unregister_extra_mpm_process(pid.pid) == 1) {
+ /* handled */
+#if APR_HAS_OTHER_CHILD
+ }
+ else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH,
+ status) == 0) {
+ /* handled */
+#endif
+ }
+ else if (is_graceful) {
+ /* Great, we've probably just lost a slot in the
+ * scoreboard. Somehow we don't know about this child.
+ */
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
+ ap_server_conf,
+ "long lost child came home! (pid %ld)",
+ (long)pid.pid);
+ }
+ /* Don't perform idle maintenance when a child dies,
+ * only do it when there's a timeout. Remember only a
+ * finite number of children can die, and it's pretty
+ * pathological for a lot to die suddenly.
+ */
+ continue;
+ }
+ else if (remaining_children_to_start) {
+ /* we hit a 1 second timeout in which none of the previous
+ * generation of children needed to be reaped... so assume
+ * they're all done, and pick up the slack if any is left.
+ */
+ startup_children(remaining_children_to_start);
+ remaining_children_to_start = 0;
+ /* In any event we really shouldn't do the code below because
+ * few of the servers we just started are in the IDLE state
+ * yet, so we'd mistakenly create an extra server.
+ */
+ continue;
+ }
+
+ perform_idle_server_maintenance();
+ }
+}
+
+int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
+{
+ int remaining_children_to_start;
+ apr_status_t rv;
+
+ ap_log_pid(pconf, ap_pid_fname);
+
+ first_server_limit = server_limit;
+ first_thread_limit = thread_limit;
+ if (changed_limit_at_restart) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "WARNING: Attempt to change ServerLimit or ThreadLimit "
+ "ignored during restart");
+ changed_limit_at_restart = 0;
+ }
+
+ /* Initialize cross-process accept lock */
+ ap_lock_fname = apr_psprintf(_pconf, "%s.%" APR_PID_T_FMT,
+ ap_server_root_relative(_pconf, ap_lock_fname),
+ ap_my_pid);
+
+ rv = apr_proc_mutex_create(&accept_mutex, ap_lock_fname,
+ ap_accept_lock_mech, _pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
+ "Couldn't create accept lock");
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+
+#if APR_USE_SYSVSEM_SERIALIZE
+ if (ap_accept_lock_mech == APR_LOCK_DEFAULT ||
+ ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
+#else
+ if (ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
+#endif
+ rv = unixd_set_proc_mutex_perms(accept_mutex);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
+ "Couldn't set permissions on cross-process lock; "
+ "check User and Group directives");
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+ }
+
+ if (!is_graceful) {
+ if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
+ mpm_state = AP_MPMQ_STOPPING;
+ return 1;
+ }
+ /* fix the generation number in the global score; we just got a new,
+ * cleared scoreboard
+ */
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+ }
+
+ set_signals();
+ /* Don't thrash... */
+ if (max_spare_threads < min_spare_threads + ap_threads_per_child)
+ max_spare_threads = min_spare_threads + ap_threads_per_child;
+
+ /* If we're doing a graceful_restart then we're going to see a lot
+ * of children exiting immediately when we get into the main loop
+ * below (because we just sent them AP_SIG_GRACEFUL). This happens pretty
+ * rapidly... and for each one that exits we'll start a new one until
+ * we reach at least daemons_min_free. But we may be permitted to
+ * start more than that, so we'll just keep track of how many we're
+ * supposed to start up without the 1 second penalty between each fork.
+ */
+ remaining_children_to_start = ap_daemons_to_start;
+ if (remaining_children_to_start > ap_daemons_limit) {
+ remaining_children_to_start = ap_daemons_limit;
+ }
+ if (!is_graceful) {
+ startup_children(remaining_children_to_start);
+ remaining_children_to_start = 0;
+ }
+ else {
+ /* give the system some time to recover before kicking into
+ * exponential mode */
+ hold_off_on_exponential_spawning = 10;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "%s configured -- resuming normal operations",
+ ap_get_server_version());
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf,
+ "Server built: %s", ap_get_server_built());
+#ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "AcceptMutex: %s (default: %s)",
+ apr_proc_mutex_name(accept_mutex),
+ apr_proc_mutex_defname());
+#endif
+ restart_pending = shutdown_pending = 0;
+ mpm_state = AP_MPMQ_RUNNING;
+
+ server_main_loop(remaining_children_to_start);
+ mpm_state = AP_MPMQ_STOPPING;
+
+ if (shutdown_pending && !is_graceful) {
+ /* Time to shut down:
+ * Kill child processes, tell them to call child_exit, etc...
+ */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, FALSE);
+ ap_reclaim_child_processes(1); /* Start with SIGTERM */
+
+ if (!child_fatal) {
+ /* cleanup pid file on normal shutdown */
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+ if ( pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0,
+ ap_server_conf,
+ "removed PID file %s (pid=%" APR_PID_T_FMT ")",
+ pidfile, getpid());
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0,
+ ap_server_conf, "caught SIGTERM, shutting down");
+ }
+ return 1;
+ } else if (shutdown_pending) {
+ /* Time to gracefully shut down:
+ * Kill child processes, tell them to call child_exit, etc...
+ */
+ int active_children;
+ int index;
+ apr_time_t cutoff = 0;
+
+ /* Close our listeners, and then ask our children to do same */
+ ap_close_listeners();
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, TRUE);
+ ap_relieve_child_processes();
+
+ if (!child_fatal) {
+ /* cleanup pid file on normal shutdown */
+ const char *pidfile = NULL;
+ pidfile = ap_server_root_relative (pconf, ap_pid_fname);
+ if ( pidfile != NULL && unlink(pidfile) == 0)
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0,
+ ap_server_conf,
+ "removed PID file %s (pid=%" APR_PID_T_FMT ")",
+ pidfile, getpid());
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "caught " AP_SIG_GRACEFUL_STOP_STRING
+ ", shutting down gracefully");
+ }
+
+ if (ap_graceful_shutdown_timeout) {
+ cutoff = apr_time_now() +
+ apr_time_from_sec(ap_graceful_shutdown_timeout);
+ }
+
+ /* Don't really exit until each child has finished */
+ shutdown_pending = 0;
+ do {
+ /* Pause for a second */
+ apr_sleep(apr_time_from_sec(1));
+
+ /* Relieve any children which have now exited */
+ ap_relieve_child_processes();
+
+ active_children = 0;
+ for (index = 0; index < ap_daemons_limit; ++index) {
+ if (MPM_CHILD_PID(index) != 0) {
+ if (kill(MPM_CHILD_PID(index), 0) == 0) {
+ active_children = 1;
+ /* Having just one child is enough to stay around */
+ break;
+ }
+ }
+ }
+ } while (!shutdown_pending && active_children &&
+ (!ap_graceful_shutdown_timeout || apr_time_now() < cutoff));
+
+ /* We might be here because we received SIGTERM, either
+ * way, try and make sure that all of our processes are
+ * really dead.
+ */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, FALSE);
+ ap_reclaim_child_processes(1);
+
+ return 1;
+ }
+
+ /* we've been told to restart */
+ apr_signal(SIGHUP, SIG_IGN);
+
+ if (one_process) {
+ /* not worth thinking about */
+ return 1;
+ }
+
+ /* advance to the next generation */
+ /* XXX: we really need to make sure this new generation number isn't in
+ * use by any of the children.
+ */
+ ++ap_my_generation;
+ ap_scoreboard_image->global->running_generation = ap_my_generation;
+
+ if (is_graceful) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ AP_SIG_GRACEFUL_STRING " received. Doing graceful restart");
+ /* wake up the children...time to die. But we'll have more soon */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, TRUE);
+
+
+ /* This is mostly for debugging... so that we know what is still
+ * gracefully dealing with existing request.
+ */
+
+ }
+ else {
+ /* Kill 'em all. Since the child acts the same on the parents SIGTERM
+ * and a SIGHUP, we may as well use the same signal, because some user
+ * pthreads are stealing signals from us left and right.
+ */
+ ap_mpm_pod_killpg(pod, ap_daemons_limit, FALSE);
+
+ ap_reclaim_child_processes(1); /* Start with SIGTERM */
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
+ "SIGHUP received. Attempting to restart");
+ }
+
+ return 0;
+}
+
+/* This really should be a post_config hook, but the error log is already
+ * redirected by that point, so we need to do this in the open_logs phase.
+ */
+static int worker_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
+{
+ apr_status_t rv;
+
+ pconf = p;
+ ap_server_conf = s;
+
+ if ((num_listensocks = ap_setup_listeners(ap_server_conf)) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0,
+ NULL, "no listening sockets available, shutting down");
+ return DONE;
+ }
+
+ if (!one_process) {
+ if ((rv = ap_mpm_pod_open(pconf, &pod))) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_STARTUP, rv, NULL,
+ "Could not open pipe-of-death.");
+ return DONE;
+ }
+ }
+ return OK;
+}
+
+static int worker_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ static int restart_num = 0;
+ int no_detach, debug, foreground;
+ ap_directive_t *pdir;
+ ap_directive_t *max_clients = NULL;
+ apr_status_t rv;
+
+ mpm_state = AP_MPMQ_STARTING;
+
+ /* make sure that "ThreadsPerChild" gets set before "MaxClients" */
+ for (pdir = ap_conftree; pdir != NULL; pdir = pdir->next) {
+ if (strncasecmp(pdir->directive, "ThreadsPerChild", 15) == 0) {
+ if (!max_clients) {
+ break; /* we're in the clear, got ThreadsPerChild first */
+ }
+ else {
+ /* now to swap the data */
+ ap_directive_t temp;
+
+ temp.directive = pdir->directive;
+ temp.args = pdir->args;
+ /* Make sure you don't change 'next', or you may get loops! */
+ /* XXX: first_child, parent, and data can never be set
+ * for these directives, right? -aaron */
+ temp.filename = pdir->filename;
+ temp.line_num = pdir->line_num;
+
+ pdir->directive = max_clients->directive;
+ pdir->args = max_clients->args;
+ pdir->filename = max_clients->filename;
+ pdir->line_num = max_clients->line_num;
+
+ max_clients->directive = temp.directive;
+ max_clients->args = temp.args;
+ max_clients->filename = temp.filename;
+ max_clients->line_num = temp.line_num;
+ break;
+ }
+ }
+ else if (!max_clients
+ && strncasecmp(pdir->directive, "MaxClients", 10) == 0) {
+ max_clients = pdir;
+ }
+ }
+
+ debug = ap_exists_config_define("DEBUG");
+
+ if (debug) {
+ foreground = one_process = 1;
+ no_detach = 0;
+ }
+ else {
+ one_process = ap_exists_config_define("ONE_PROCESS");
+ no_detach = ap_exists_config_define("NO_DETACH");
+ foreground = ap_exists_config_define("FOREGROUND");
+ }
+
+ /* sigh, want this only the second time around */
+ if (restart_num++ == 1) {
+ is_graceful = 0;
+
+ if (!one_process && !foreground) {
+ rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND
+ : APR_PROC_DETACH_DAEMONIZE);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "apr_proc_detach failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+ parent_pid = ap_my_pid = getpid();
+ }
+
+ unixd_pre_config(ptemp);
+ ap_listen_pre_config();
+ ap_daemons_to_start = DEFAULT_START_DAEMON;
+ min_spare_threads = DEFAULT_MIN_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD;
+ max_spare_threads = DEFAULT_MAX_FREE_DAEMON * DEFAULT_THREADS_PER_CHILD;
+ ap_daemons_limit = server_limit;
+ ap_threads_per_child = DEFAULT_THREADS_PER_CHILD;
+ ap_pid_fname = DEFAULT_PIDLOG;
+ ap_lock_fname = DEFAULT_LOCKFILE;
+ ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD;
+ ap_extended_status = 0;
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+ ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+#endif
+
+ apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir));
+
+ return OK;
+}
+
+static void worker_hooks(apr_pool_t *p)
+{
+ /* The worker open_logs phase must run before the core's, or stderr
+ * will be redirected to a file, and the messages won't print to the
+ * console.
+ */
+ static const char *const aszSucc[] = {"core.c", NULL};
+ one_process = 0;
+
+ ap_hook_open_logs(worker_open_logs, NULL, aszSucc, APR_HOOK_MIDDLE);
+ /* we need to set the MPM state before other pre-config hooks use MPM query
+ * to retrieve it, so register as REALLY_FIRST
+ */
+ ap_hook_pre_config(worker_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+}
+
+static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_daemons_to_start = atoi(arg);
+ return NULL;
+}
+
+static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ min_spare_threads = atoi(arg);
+ if (min_spare_threads <= 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: detected MinSpareThreads set to non-positive.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Resetting to 1 to avoid almost certain Apache failure.");
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Please read the documentation.");
+ min_spare_threads = 1;
+ }
+
+ return NULL;
+}
+
+static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ max_spare_threads = atoi(arg);
+ return NULL;
+}
+
+static const char *set_max_clients (cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ int max_clients;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ /* It is ok to use ap_threads_per_child here because we are
+ * sure that it gets set before MaxClients in the pre_config stage. */
+ max_clients = atoi(arg);
+ if (max_clients < ap_threads_per_child) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients (%d) must be at least as large",
+ max_clients);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " as ThreadsPerChild (%d). Automatically",
+ ap_threads_per_child);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " increasing MaxClients to %d.",
+ ap_threads_per_child);
+ max_clients = ap_threads_per_child;
+ }
+ ap_daemons_limit = max_clients / ap_threads_per_child;
+ if ((max_clients > 0) && (max_clients % ap_threads_per_child)) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients (%d) is not an integer multiple",
+ max_clients);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " of ThreadsPerChild (%d), lowering MaxClients to %d",
+ ap_threads_per_child,
+ ap_daemons_limit * ap_threads_per_child);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " for a maximum of %d child processes,",
+ ap_daemons_limit);
+ max_clients = ap_daemons_limit * ap_threads_per_child;
+ }
+ if (ap_daemons_limit > server_limit) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: MaxClients of %d would require %d servers,",
+ max_clients, ap_daemons_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " and would exceed the ServerLimit value of %d.",
+ server_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " Automatically lowering MaxClients to %d. To increase,",
+ server_limit * ap_threads_per_child);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " please see the ServerLimit directive.");
+ ap_daemons_limit = server_limit;
+ }
+ else if (ap_daemons_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require MaxClients > 0, setting to 1");
+ ap_daemons_limit = 1;
+ }
+ return NULL;
+}
+
+static const char *set_threads_per_child (cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_threads_per_child = atoi(arg);
+ if (ap_threads_per_child > thread_limit) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ThreadsPerChild of %d exceeds ThreadLimit "
+ "value of %d", ap_threads_per_child,
+ thread_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "threads, lowering ThreadsPerChild to %d. To increase, please"
+ " see the", thread_limit);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " ThreadLimit directive.");
+ ap_threads_per_child = thread_limit;
+ }
+ else if (ap_threads_per_child < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ThreadsPerChild > 0, setting to 1");
+ ap_threads_per_child = 1;
+ }
+ return NULL;
+}
+
+static const char *set_server_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ int tmp_server_limit;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ tmp_server_limit = atoi(arg);
+ /* you cannot change ServerLimit across a restart; ignore
+ * any such attempts
+ */
+ if (first_server_limit &&
+ tmp_server_limit != server_limit) {
+ /* how do we log a message? the error log is a bit bucket at this
+ * point; we'll just have to set a flag so that ap_mpm_run()
+ * logs a warning later
+ */
+ changed_limit_at_restart = 1;
+ return NULL;
+ }
+ server_limit = tmp_server_limit;
+
+ if (server_limit > MAX_SERVER_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ServerLimit of %d exceeds compile time limit "
+ "of %d servers,", server_limit, MAX_SERVER_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ServerLimit to %d.", MAX_SERVER_LIMIT);
+ server_limit = MAX_SERVER_LIMIT;
+ }
+ else if (server_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ServerLimit > 0, setting to 1");
+ server_limit = 1;
+ }
+ return NULL;
+}
+
+static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg)
+{
+ int tmp_thread_limit;
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ tmp_thread_limit = atoi(arg);
+ /* you cannot change ThreadLimit across a restart; ignore
+ * any such attempts
+ */
+ if (first_thread_limit &&
+ tmp_thread_limit != thread_limit) {
+ /* how do we log a message? the error log is a bit bucket at this
+ * point; we'll just have to set a flag so that ap_mpm_run()
+ * logs a warning later
+ */
+ changed_limit_at_restart = 1;
+ return NULL;
+ }
+ thread_limit = tmp_thread_limit;
+
+ if (thread_limit > MAX_THREAD_LIMIT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: ThreadLimit of %d exceeds compile time limit "
+ "of %d servers,", thread_limit, MAX_THREAD_LIMIT);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ " lowering ThreadLimit to %d.", MAX_THREAD_LIMIT);
+ thread_limit = MAX_THREAD_LIMIT;
+ }
+ else if (thread_limit < 1) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "WARNING: Require ThreadLimit > 0, setting to 1");
+ thread_limit = 1;
+ }
+ return NULL;
+}
+
+static const command_rec worker_cmds[] = {
+UNIX_DAEMON_COMMANDS,
+LISTEN_COMMANDS,
+AP_INIT_TAKE1("StartServers", set_daemons_to_start, NULL, RSRC_CONF,
+ "Number of child processes launched at server startup"),
+AP_INIT_TAKE1("MinSpareThreads", set_min_spare_threads, NULL, RSRC_CONF,
+ "Minimum number of idle threads, to handle request spikes"),
+AP_INIT_TAKE1("MaxSpareThreads", set_max_spare_threads, NULL, RSRC_CONF,
+ "Maximum number of idle threads"),
+AP_INIT_TAKE1("MaxClients", set_max_clients, NULL, RSRC_CONF,
+ "Maximum number of threads alive at the same time"),
+AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF,
+ "Number of threads each child creates"),
+AP_INIT_TAKE1("ServerLimit", set_server_limit, NULL, RSRC_CONF,
+ "Maximum number of child processes for this run of Apache"),
+AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF,
+ "Maximum number of worker threads per child process for this run of Apache - Upper limit for ThreadsPerChild"),
+AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND,
+{ NULL }
+};
+
+module AP_MODULE_DECLARE_DATA mpm_worker_module = {
+ MPM20_MODULE_STUFF,
+ ap_mpm_rewrite_args, /* hook to run before apache parses args */
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ worker_cmds, /* command apr_table_t */
+ worker_hooks /* register_hooks */
+};
+
diff --git a/server/mpm_common.c b/server/mpm_common.c
new file mode 100644
index 00000000..ba60155d
--- /dev/null
+++ b/server/mpm_common.c
@@ -0,0 +1,1245 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* The purpose of this file is to store the code that MOST mpm's will need
+ * this does not mean a function only goes into this file if every MPM needs
+ * it. It means that if a function is needed by more than one MPM, and
+ * future maintenance would be served by making the code common, then the
+ * function belongs here.
+ *
+ * This is going in src/main because it is not platform specific, it is
+ * specific to multi-process servers, but NOT to Unix. Which is why it
+ * does not belong in src/os/unix
+ */
+
+#include "apr.h"
+#include "apr_thread_proc.h"
+#include "apr_signal.h"
+#include "apr_strings.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_getopt.h"
+#include "apr_optional.h"
+#include "apr_allocator.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "mpm.h"
+#include "mpm_common.h"
+#include "ap_mpm.h"
+#include "ap_listen.h"
+#include "mpm_default.h"
+
+#ifdef AP_MPM_WANT_SET_SCOREBOARD
+#include "scoreboard.h"
+#endif
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if AP_ENABLE_EXCEPTION_HOOK
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(fatal_exception)
+ APR_HOOK_LINK(monitor)
+)
+AP_IMPLEMENT_HOOK_RUN_ALL(int, fatal_exception,
+ (ap_exception_info_t *ei), (ei), OK, DECLINED)
+#else
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(monitor)
+)
+#endif
+AP_IMPLEMENT_HOOK_RUN_ALL(int, monitor,
+ (apr_pool_t *p), (p), OK, DECLINED)
+
+
+#ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+
+typedef enum {DO_NOTHING, SEND_SIGTERM, SEND_SIGKILL, GIVEUP} action_t;
+
+typedef struct extra_process_t {
+ struct extra_process_t *next;
+ pid_t pid;
+} extra_process_t;
+
+static extra_process_t *extras;
+
+void ap_register_extra_mpm_process(pid_t pid)
+{
+ extra_process_t *p = (extra_process_t *)malloc(sizeof(extra_process_t));
+
+ p->next = extras;
+ p->pid = pid;
+ extras = p;
+}
+
+int ap_unregister_extra_mpm_process(pid_t pid)
+{
+ extra_process_t *cur = extras;
+ extra_process_t *prev = NULL;
+
+ while (cur && cur->pid != pid) {
+ prev = cur;
+ cur = cur->next;
+ }
+
+ if (cur) {
+ if (prev) {
+ prev->next = cur->next;
+ }
+ else {
+ extras = cur->next;
+ }
+ free(cur);
+ return 1; /* found */
+ }
+ else {
+ /* we don't know about any such process */
+ return 0;
+ }
+}
+
+static int reclaim_one_pid(pid_t pid, action_t action)
+{
+ apr_proc_t proc;
+ apr_status_t waitret;
+
+ proc.pid = pid;
+ waitret = apr_proc_wait(&proc, NULL, NULL, APR_NOWAIT);
+ if (waitret != APR_CHILD_NOTDONE) {
+ return 1;
+ }
+
+ switch(action) {
+ case DO_NOTHING:
+ break;
+
+ case SEND_SIGTERM:
+ /* ok, now it's being annoying */
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, ap_server_conf,
+ "child process %" APR_PID_T_FMT
+ " still did not exit, "
+ "sending a SIGTERM",
+ pid);
+ kill(pid, SIGTERM);
+ break;
+
+ case SEND_SIGKILL:
+ ap_log_error(APLOG_MARK, APLOG_ERR,
+ 0, ap_server_conf,
+ "child process %" APR_PID_T_FMT
+ " still did not exit, "
+ "sending a SIGKILL",
+ pid);
+#ifndef BEOS
+ kill(pid, SIGKILL);
+#else
+ /* sending a SIGKILL kills the entire team on BeOS, and as
+ * httpd thread is part of that team it removes any chance
+ * of ever doing a restart. To counter this I'm changing to
+ * use a kinder, gentler way of killing a specific thread
+ * that is just as effective.
+ */
+ kill_thread(pid);
+#endif
+ break;
+
+ case GIVEUP:
+ /* gave it our best shot, but alas... If this really
+ * is a child we are trying to kill and it really hasn't
+ * exited, we will likely fail to bind to the port
+ * after the restart.
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR,
+ 0, ap_server_conf,
+ "could not make child process %" APR_PID_T_FMT
+ " exit, "
+ "attempting to continue anyway",
+ pid);
+ break;
+ }
+
+ return 0;
+}
+
+void ap_reclaim_child_processes(int terminate)
+{
+ apr_time_t waittime = 1024 * 16;
+ int i;
+ extra_process_t *cur_extra;
+ int not_dead_yet;
+ int max_daemons;
+ apr_time_t starttime = apr_time_now();
+ /* this table of actions and elapsed times tells what action is taken
+ * at which elapsed time from starting the reclaim
+ */
+ struct {
+ action_t action;
+ apr_time_t action_time;
+ } action_table[] = {
+ {DO_NOTHING, 0}, /* dummy entry for iterations where we reap
+ * children but take no action against
+ * stragglers
+ */
+ {SEND_SIGTERM, apr_time_from_sec(3)},
+ {SEND_SIGTERM, apr_time_from_sec(5)},
+ {SEND_SIGTERM, apr_time_from_sec(7)},
+ {SEND_SIGKILL, apr_time_from_sec(9)},
+ {GIVEUP, apr_time_from_sec(10)}
+ };
+ int cur_action; /* index of action we decided to take this
+ * iteration
+ */
+ int next_action = 1; /* index of first real action */
+
+ ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons);
+
+ do {
+ apr_sleep(waittime);
+ /* don't let waittime get longer than 1 second; otherwise, we don't
+ * react quickly to the last child exiting, and taking action can
+ * be delayed
+ */
+ waittime = waittime * 4;
+ if (waittime > apr_time_from_sec(1)) {
+ waittime = apr_time_from_sec(1);
+ }
+
+ /* see what action to take, if any */
+ if (action_table[next_action].action_time <= apr_time_now() - starttime) {
+ cur_action = next_action;
+ ++next_action;
+ }
+ else {
+ cur_action = 0; /* nothing to do */
+ }
+
+ /* now see who is done */
+ not_dead_yet = 0;
+ for (i = 0; i < max_daemons; ++i) {
+ pid_t pid = MPM_CHILD_PID(i);
+
+ if (pid == 0) {
+ continue; /* not every scoreboard entry is in use */
+ }
+
+ if (reclaim_one_pid(pid, action_table[cur_action].action)) {
+ MPM_NOTE_CHILD_KILLED(i);
+ }
+ else {
+ ++not_dead_yet;
+ }
+ }
+
+ cur_extra = extras;
+ while (cur_extra) {
+ extra_process_t *next = cur_extra->next;
+
+ if (reclaim_one_pid(cur_extra->pid, action_table[cur_action].action)) {
+ AP_DEBUG_ASSERT(1 == ap_unregister_extra_mpm_process(cur_extra->pid));
+ }
+ else {
+ ++not_dead_yet;
+ }
+ cur_extra = next;
+ }
+#if APR_HAS_OTHER_CHILD
+ apr_proc_other_child_refresh_all(APR_OC_REASON_RESTART);
+#endif
+
+ } while (not_dead_yet > 0 &&
+ action_table[cur_action].action != GIVEUP);
+}
+
+void ap_relieve_child_processes(void)
+{
+ int i;
+ extra_process_t *cur_extra;
+ int max_daemons;
+
+ ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons);
+
+ /* now see who is done */
+ for (i = 0; i < max_daemons; ++i) {
+ pid_t pid = MPM_CHILD_PID(i);
+
+ if (pid == 0) {
+ continue; /* not every scoreboard entry is in use */
+ }
+
+ if (reclaim_one_pid(pid, DO_NOTHING)) {
+ MPM_NOTE_CHILD_KILLED(i);
+ }
+ }
+
+ cur_extra = extras;
+ while (cur_extra) {
+ extra_process_t *next = cur_extra->next;
+
+ if (reclaim_one_pid(cur_extra->pid, DO_NOTHING)) {
+ AP_DEBUG_ASSERT(1 == ap_unregister_extra_mpm_process(cur_extra->pid));
+ }
+ cur_extra = next;
+ }
+}
+#endif /* AP_MPM_WANT_RECLAIM_CHILD_PROCESSES */
+
+#ifdef AP_MPM_WANT_WAIT_OR_TIMEOUT
+
+/* number of calls to wait_or_timeout between writable probes */
+#ifndef INTERVAL_OF_WRITABLE_PROBES
+#define INTERVAL_OF_WRITABLE_PROBES 10
+#endif
+static int wait_or_timeout_counter;
+
+void ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, apr_proc_t *ret,
+ apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ ++wait_or_timeout_counter;
+ if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) {
+ wait_or_timeout_counter = 0;
+ ap_run_monitor(p);
+ }
+
+ rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p);
+ if (APR_STATUS_IS_EINTR(rv)) {
+ ret->pid = -1;
+ return;
+ }
+
+ if (APR_STATUS_IS_CHILD_DONE(rv)) {
+ return;
+ }
+
+#ifdef NEED_WAITPID
+ if ((ret = reap_children(exitcode, status)) > 0) {
+ return;
+ }
+#endif
+
+ apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL);
+ ret->pid = -1;
+ return;
+}
+#endif /* AP_MPM_WANT_WAIT_OR_TIMEOUT */
+
+#ifdef AP_MPM_WANT_PROCESS_CHILD_STATUS
+int ap_process_child_status(apr_proc_t *pid, apr_exit_why_e why, int status)
+{
+ int signum = status;
+ const char *sigdesc = apr_signal_description_get(signum);
+
+ /* Child died... if it died due to a fatal error,
+ * we should simply bail out. The caller needs to
+ * check for bad rc from us and exit, running any
+ * appropriate cleanups.
+ *
+ * If the child died due to a resource shortage,
+ * the parent should limit the rate of forking
+ */
+ if (APR_PROC_CHECK_EXIT(why)) {
+ if (status == APEXIT_CHILDSICK) {
+ return status;
+ }
+
+ if (status == APEXIT_CHILDFATAL) {
+ ap_log_error(APLOG_MARK, APLOG_ALERT,
+ 0, ap_server_conf,
+ "Child %" APR_PID_T_FMT
+ " returned a Fatal error... Apache is exiting!",
+ pid->pid);
+ return APEXIT_CHILDFATAL;
+ }
+
+ return 0;
+ }
+
+ if (APR_PROC_CHECK_SIGNALED(why)) {
+ switch (signum) {
+ case SIGTERM:
+ case SIGHUP:
+ case AP_SIG_GRACEFUL:
+ case SIGKILL:
+ break;
+
+ default:
+ if (APR_PROC_CHECK_CORE_DUMP(why)) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE,
+ 0, ap_server_conf,
+ "child pid %ld exit signal %s (%d), "
+ "possible coredump in %s",
+ (long)pid->pid, sigdesc, signum,
+ ap_coredump_dir);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE,
+ 0, ap_server_conf,
+ "child pid %ld exit signal %s (%d)",
+ (long)pid->pid, sigdesc, signum);
+ }
+ }
+ }
+ return 0;
+}
+#endif /* AP_MPM_WANT_PROCESS_CHILD_STATUS */
+
+#if defined(TCP_NODELAY) && !defined(MPE) && !defined(TPF)
+void ap_sock_disable_nagle(apr_socket_t *s)
+{
+ /* The Nagle algorithm says that we should delay sending partial
+ * packets in hopes of getting more data. We don't want to do
+ * this; we are not telnet. There are bad interactions between
+ * persistent connections and Nagle's algorithm that have very severe
+ * performance penalties. (Failing to disable Nagle is not much of a
+ * problem with simple HTTP.)
+ *
+ * In spite of these problems, failure here is not a shooting offense.
+ */
+ apr_status_t status = apr_socket_opt_set(s, APR_TCP_NODELAY, 1);
+
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf,
+ "apr_socket_opt_set: (TCP_NODELAY)");
+ }
+}
+#endif
+
+#ifdef HAVE_GETPWNAM
+AP_DECLARE(uid_t) ap_uname2id(const char *name)
+{
+ struct passwd *ent;
+
+ if (name[0] == '#')
+ return (atoi(&name[1]));
+
+ if (!(ent = getpwnam(name))) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s: bad user name %s", ap_server_argv0, name);
+ exit(1);
+ }
+
+ return (ent->pw_uid);
+}
+#endif
+
+#ifdef HAVE_GETGRNAM
+AP_DECLARE(gid_t) ap_gname2id(const char *name)
+{
+ struct group *ent;
+
+ if (name[0] == '#')
+ return (atoi(&name[1]));
+
+ if (!(ent = getgrnam(name))) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "%s: bad group name %s", ap_server_argv0, name);
+ exit(1);
+ }
+
+ return (ent->gr_gid);
+}
+#endif
+
+#ifndef HAVE_INITGROUPS
+int initgroups(const char *name, gid_t basegid)
+{
+#if defined(QNX) || defined(MPE) || defined(BEOS) || defined(_OSD_POSIX) || defined(TPF) || defined(__TANDEM) || defined(OS2) || defined(WIN32) || defined(NETWARE)
+/* QNX, MPE and BeOS do not appear to support supplementary groups. */
+ return 0;
+#else /* ndef QNX */
+ gid_t groups[NGROUPS_MAX];
+ struct group *g;
+ int index = 0;
+
+ setgrent();
+
+ groups[index++] = basegid;
+
+ while (index < NGROUPS_MAX && ((g = getgrent()) != NULL)) {
+ if (g->gr_gid != basegid) {
+ char **names;
+
+ for (names = g->gr_mem; *names != NULL; ++names) {
+ if (!strcmp(*names, name))
+ groups[index++] = g->gr_gid;
+ }
+ }
+ }
+
+ endgrent();
+
+ return setgroups(index, groups);
+#endif /* def QNX */
+}
+#endif /* def NEED_INITGROUPS */
+
+#ifdef AP_MPM_USES_POD
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod)
+{
+ apr_status_t rv;
+
+ *pod = apr_palloc(p, sizeof(**pod));
+ rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ apr_file_pipe_timeout_set((*pod)->pod_in, 0);
+ (*pod)->p = p;
+
+ /* close these before exec. */
+ apr_file_inherit_unset((*pod)->pod_in);
+ apr_file_inherit_unset((*pod)->pod_out);
+
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_check(ap_pod_t *pod)
+{
+ char c;
+ apr_size_t len = 1;
+ apr_status_t rv;
+
+ rv = apr_file_read(pod->pod_in, &c, &len);
+
+ if ((rv == APR_SUCCESS) && (len == 1)) {
+ return APR_SUCCESS;
+ }
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ return AP_NORESTART;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod)
+{
+ apr_status_t rv;
+
+ rv = apr_file_close(pod->pod_out);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_file_close(pod->pod_in);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t pod_signal_internal(ap_pod_t *pod)
+{
+ apr_status_t rv;
+ char char_of_death = '!';
+ apr_size_t one = 1;
+
+ rv = apr_file_write(pod->pod_out, &char_of_death, &one);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
+ "write pipe_of_death");
+ }
+
+ return rv;
+}
+
+/* This function connects to the server, then immediately closes the connection.
+ * This permits the MPM to skip the poll when there is only one listening
+ * socket, because it provides a alternate way to unblock an accept() when
+ * the pod is used.
+ */
+static apr_status_t dummy_connection(ap_pod_t *pod)
+{
+ char *srequest;
+ apr_status_t rv;
+ apr_socket_t *sock;
+ apr_pool_t *p;
+ apr_size_t len;
+
+ /* create a temporary pool for the socket. pconf stays around too long */
+ rv = apr_pool_create(&p, pod->p);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_socket_create(&sock, ap_listeners->bind_addr->family,
+ SOCK_STREAM, 0, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
+ "get socket to connect to listener");
+ apr_pool_destroy(p);
+ return rv;
+ }
+
+ /* on some platforms (e.g., FreeBSD), the kernel won't accept many
+ * queued connections before it starts blocking local connects...
+ * we need to keep from blocking too long and instead return an error,
+ * because the MPM won't want to hold up a graceful restart for a
+ * long time
+ */
+ rv = apr_socket_timeout_set(sock, apr_time_from_sec(3));
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf,
+ "set timeout on socket to connect to listener");
+ apr_socket_close(sock);
+ apr_pool_destroy(p);
+ return rv;
+ }
+
+ rv = apr_socket_connect(sock, ap_listeners->bind_addr);
+ if (rv != APR_SUCCESS) {
+ int log_level = APLOG_WARNING;
+
+ if (APR_STATUS_IS_TIMEUP(rv)) {
+ /* probably some server processes bailed out already and there
+ * is nobody around to call accept and clear out the kernel
+ * connection queue; usually this is not worth logging
+ */
+ log_level = APLOG_DEBUG;
+ }
+
+ ap_log_error(APLOG_MARK, log_level, rv, ap_server_conf,
+ "connect to listener on %pI", ap_listeners->bind_addr);
+ }
+
+ /* Create the request string. We include a User-Agent so that
+ * adminstrators can track down the cause of the odd-looking
+ * requests in their logs.
+ */
+ srequest = apr_pstrcat(p, "GET / HTTP/1.0\r\nUser-Agent: ",
+ ap_get_server_version(),
+ " (internal dummy connection)\r\n\r\n", NULL);
+
+ /* Since some operating systems support buffering of data or entire
+ * requests in the kernel, we send a simple request, to make sure
+ * the server pops out of a blocking accept().
+ */
+ /* XXX: This is HTTP specific. We should look at the Protocol for each
+ * listener, and send the correct type of request to trigger any Accept
+ * Filters.
+ */
+ len = strlen(srequest);
+ apr_socket_send(sock, srequest, &len);
+ apr_socket_close(sock);
+ apr_pool_destroy(p);
+
+ return rv;
+}
+
+AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod)
+{
+ apr_status_t rv;
+
+ rv = pod_signal_internal(pod);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ return dummy_connection(pod);
+}
+
+void ap_mpm_pod_killpg(ap_pod_t *pod, int num)
+{
+ int i;
+ apr_status_t rv = APR_SUCCESS;
+
+ /* we don't write anything to the pod here... we assume
+ * that the would-be reader of the pod has another way to
+ * see that it is time to die once we wake it up
+ *
+ * writing lots of things to the pod at once is very
+ * problematic... we can fill the kernel pipe buffer and
+ * be blocked until somebody consumes some bytes or
+ * we hit a timeout... if we hit a timeout we can't just
+ * keep trying because maybe we'll never successfully
+ * write again... but then maybe we'll leave would-be
+ * readers stranded (a number of them could be tied up for
+ * a while serving time-consuming requests)
+ */
+ for (i = 0; i < num && rv == APR_SUCCESS; i++) {
+ rv = dummy_connection(pod);
+ }
+}
+#endif /* #ifdef AP_MPM_USES_POD */
+
+/* standard mpm configuration handling */
+#ifdef AP_MPM_WANT_SET_PIDFILE
+const char *ap_pid_fname = NULL;
+
+const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (cmd->server->is_virtual) {
+ return "PidFile directive not allowed in <VirtualHost>";
+ }
+
+ ap_pid_fname = arg;
+ return NULL;
+}
+#endif
+
+#ifdef AP_MPM_WANT_SET_SCOREBOARD
+const char * ap_mpm_set_scoreboard(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_scoreboard_fname = arg;
+ return NULL;
+}
+#endif
+
+#ifdef AP_MPM_WANT_SET_LOCKFILE
+const char *ap_lock_fname = NULL;
+
+const char *ap_mpm_set_lockfile(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_lock_fname = arg;
+ return NULL;
+}
+#endif
+
+#ifdef AP_MPM_WANT_SET_MAX_REQUESTS
+int ap_max_requests_per_child = 0;
+
+const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ ap_max_requests_per_child = atoi(arg);
+
+ return NULL;
+}
+#endif
+
+#ifdef AP_MPM_WANT_SET_COREDUMPDIR
+char ap_coredump_dir[MAX_STRING_LEN];
+int ap_coredumpdir_configured;
+
+const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_status_t rv;
+ apr_finfo_t finfo;
+ const char *fname;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ fname = ap_server_root_relative(cmd->pool, arg);
+ if (!fname) {
+ return apr_pstrcat(cmd->pool, "Invalid CoreDumpDirectory path ",
+ arg, NULL);
+ }
+ if ((rv = apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool)) != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname,
+ " does not exist", NULL);
+ }
+ if (finfo.filetype != APR_DIR) {
+ return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname,
+ " is not a directory", NULL);
+ }
+ apr_cpystrn(ap_coredump_dir, fname, sizeof(ap_coredump_dir));
+ ap_coredumpdir_configured = 1;
+ return NULL;
+}
+#endif
+
+#ifdef AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
+int ap_graceful_shutdown_timeout = 0;
+
+const char * ap_mpm_set_graceful_shutdown(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+ ap_graceful_shutdown_timeout = atoi(arg);
+ return NULL;
+}
+#endif
+
+#ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH
+apr_lockmech_e ap_accept_lock_mech = APR_LOCK_DEFAULT;
+
+const char ap_valid_accept_mutex_string[] =
+ "Valid accept mutexes for this platform and MPM are: default"
+#if APR_HAS_FLOCK_SERIALIZE
+ ", flock"
+#endif
+#if APR_HAS_FCNTL_SERIALIZE
+ ", fcntl"
+#endif
+#if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)
+ ", sysvsem"
+#endif
+#if APR_HAS_POSIXSEM_SERIALIZE
+ ", posixsem"
+#endif
+#if APR_HAS_PROC_PTHREAD_SERIALIZE
+ ", pthread"
+#endif
+ ".";
+
+AP_DECLARE(const char *) ap_mpm_set_accept_lock_mech(cmd_parms *cmd,
+ void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (!strcasecmp(arg, "default")) {
+ ap_accept_lock_mech = APR_LOCK_DEFAULT;
+ }
+#if APR_HAS_FLOCK_SERIALIZE
+ else if (!strcasecmp(arg, "flock")) {
+ ap_accept_lock_mech = APR_LOCK_FLOCK;
+ }
+#endif
+#if APR_HAS_FCNTL_SERIALIZE
+ else if (!strcasecmp(arg, "fcntl")) {
+ ap_accept_lock_mech = APR_LOCK_FCNTL;
+ }
+#endif
+
+ /* perchild can't use SysV sems because the permissions on the accept
+ * mutex can't be set to allow all processes to use the mutex and
+ * at the same time keep all users from being able to dink with the
+ * mutex
+ */
+#if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM)
+ else if (!strcasecmp(arg, "sysvsem")) {
+ ap_accept_lock_mech = APR_LOCK_SYSVSEM;
+ }
+#endif
+#if APR_HAS_POSIXSEM_SERIALIZE
+ else if (!strcasecmp(arg, "posixsem")) {
+ ap_accept_lock_mech = APR_LOCK_POSIXSEM;
+ }
+#endif
+#if APR_HAS_PROC_PTHREAD_SERIALIZE
+ else if (!strcasecmp(arg, "pthread")) {
+ ap_accept_lock_mech = APR_LOCK_PROC_PTHREAD;
+ }
+#endif
+ else {
+ return apr_pstrcat(cmd->pool, arg, " is an invalid mutex mechanism; ",
+ ap_valid_accept_mutex_string, NULL);
+ }
+ return NULL;
+}
+
+#endif
+
+#ifdef AP_MPM_WANT_SIGNAL_SERVER
+
+static const char *dash_k_arg;
+
+static int send_signal(pid_t pid, int sig)
+{
+ if (kill(pid, sig) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, errno, NULL,
+ "sending signal to server");
+ return 1;
+ }
+ return 0;
+}
+
+int ap_signal_server(int *exit_status, apr_pool_t *pconf)
+{
+ apr_status_t rv;
+ pid_t otherpid;
+ int running = 0;
+ int have_pid_file = 0;
+ const char *status;
+
+ *exit_status = 0;
+
+ rv = ap_read_pid(pconf, ap_pid_fname, &otherpid);
+ if (rv != APR_SUCCESS) {
+ if (rv != APR_ENOENT) {
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL,
+ "Error retrieving pid file %s", ap_pid_fname);
+ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
+ "Remove it before continuing if it is corrupted.");
+ *exit_status = 1;
+ return 1;
+ }
+ status = "httpd (no pid file) not running";
+ }
+ else {
+ have_pid_file = 1;
+ if (kill(otherpid, 0) == 0) {
+ running = 1;
+ status = apr_psprintf(pconf,
+ "httpd (pid %" APR_PID_T_FMT ") already "
+ "running", otherpid);
+ }
+ else {
+ status = apr_psprintf(pconf,
+ "httpd (pid %" APR_PID_T_FMT "?) not running",
+ otherpid);
+ }
+ }
+
+ if (!strcmp(dash_k_arg, "start")) {
+ if (running) {
+ printf("%s\n", status);
+ return 1;
+ }
+ }
+
+ if (!strcmp(dash_k_arg, "stop")) {
+ if (!running) {
+ printf("%s\n", status);
+ }
+ else {
+ send_signal(otherpid, SIGTERM);
+ }
+ return 1;
+ }
+
+ if (!strcmp(dash_k_arg, "restart")) {
+ if (!running) {
+ printf("httpd not running, trying to start\n");
+ }
+ else {
+ *exit_status = send_signal(otherpid, SIGHUP);
+ return 1;
+ }
+ }
+
+ if (!strcmp(dash_k_arg, "graceful")) {
+ if (!running) {
+ printf("httpd not running, trying to start\n");
+ }
+ else {
+ *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL);
+ return 1;
+ }
+ }
+
+ if (!strcmp(dash_k_arg, "graceful-stop")) {
+#ifdef AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
+ if (!running) {
+ printf("%s\n", status);
+ }
+ else {
+ *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL_STOP);
+ }
+#else
+ printf("httpd MPM \"" MPM_NAME "\" does not support graceful-stop\n");
+#endif
+ return 1;
+ }
+
+ return 0;
+}
+
+void ap_mpm_rewrite_args(process_rec *process)
+{
+ apr_array_header_t *mpm_new_argv;
+ apr_status_t rv;
+ apr_getopt_t *opt;
+ char optbuf[3];
+ const char *optarg;
+ int fixed_args;
+
+ mpm_new_argv = apr_array_make(process->pool, process->argc,
+ sizeof(const char **));
+ *(const char **)apr_array_push(mpm_new_argv) = process->argv[0];
+ fixed_args = mpm_new_argv->nelts;
+ apr_getopt_init(&opt, process->pool, process->argc, process->argv);
+ opt->errfn = NULL;
+ optbuf[0] = '-';
+ /* option char returned by apr_getopt() will be stored in optbuf[1] */
+ optbuf[2] = '\0';
+ while ((rv = apr_getopt(opt, "k:" AP_SERVER_BASEARGS,
+ optbuf + 1, &optarg)) == APR_SUCCESS) {
+ switch(optbuf[1]) {
+ case 'k':
+ if (!dash_k_arg) {
+ if (!strcmp(optarg, "start") || !strcmp(optarg, "stop") ||
+ !strcmp(optarg, "restart") || !strcmp(optarg, "graceful") ||
+ !strcmp(optarg, "graceful-stop")) {
+ dash_k_arg = optarg;
+ break;
+ }
+ }
+ default:
+ *(const char **)apr_array_push(mpm_new_argv) =
+ apr_pstrdup(process->pool, optbuf);
+ if (optarg) {
+ *(const char **)apr_array_push(mpm_new_argv) = optarg;
+ }
+ }
+ }
+
+ /* back up to capture the bad argument */
+ if (rv == APR_BADCH || rv == APR_BADARG) {
+ opt->ind--;
+ }
+
+ while (opt->ind < opt->argc) {
+ *(const char **)apr_array_push(mpm_new_argv) =
+ apr_pstrdup(process->pool, opt->argv[opt->ind++]);
+ }
+
+ process->argc = mpm_new_argv->nelts;
+ process->argv = (const char * const *)mpm_new_argv->elts;
+
+ if (dash_k_arg) {
+ APR_REGISTER_OPTIONAL_FN(ap_signal_server);
+ }
+}
+
+#endif /* AP_MPM_WANT_SIGNAL_SERVER */
+
+#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE
+apr_uint32_t ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED;
+
+const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ long value;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ value = strtol(arg, NULL, 0);
+ if (value < 0 || errno == ERANGE)
+ return apr_pstrcat(cmd->pool, "Invalid MaxMemFree value: ",
+ arg, NULL);
+
+ ap_max_mem_free = (apr_uint32_t)value * 1024;
+
+ return NULL;
+}
+
+#endif /* AP_MPM_WANT_SET_MAX_MEM_FREE */
+
+#ifdef AP_MPM_WANT_SET_STACKSIZE
+apr_size_t ap_thread_stacksize = 0; /* use system default */
+
+const char *ap_mpm_set_thread_stacksize(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ long value;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ value = strtol(arg, NULL, 0);
+ if (value < 0 || errno == ERANGE)
+ return apr_pstrcat(cmd->pool, "Invalid ThreadStackSize value: ",
+ arg, NULL);
+
+ ap_thread_stacksize = (apr_size_t)value;
+
+ return NULL;
+}
+
+#endif /* AP_MPM_WANT_SET_STACKSIZE */
+
+#ifdef AP_MPM_WANT_FATAL_SIGNAL_HANDLER
+
+static pid_t parent_pid, my_pid;
+apr_pool_t *pconf;
+
+#if AP_ENABLE_EXCEPTION_HOOK
+
+static int exception_hook_enabled;
+
+const char *ap_mpm_set_exception_hook(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (cmd->server->is_virtual) {
+ return "EnableExceptionHook directive not allowed in <VirtualHost>";
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ exception_hook_enabled = 1;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ exception_hook_enabled = 0;
+ }
+ else {
+ return "parameter must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
+static void run_fatal_exception_hook(int sig)
+{
+ ap_exception_info_t ei = {0};
+
+ if (exception_hook_enabled &&
+ geteuid() != 0 &&
+ my_pid != parent_pid) {
+ ei.sig = sig;
+ ei.pid = my_pid;
+ ap_run_fatal_exception(&ei);
+ }
+}
+#endif /* AP_ENABLE_EXCEPTION_HOOK */
+
+/* handle all varieties of core dumping signals */
+static void sig_coredump(int sig)
+{
+ apr_filepath_set(ap_coredump_dir, pconf);
+ apr_signal(sig, SIG_DFL);
+#if AP_ENABLE_EXCEPTION_HOOK
+ run_fatal_exception_hook(sig);
+#endif
+ /* linuxthreads issue calling getpid() here:
+ * This comparison won't match if the crashing thread is
+ * some module's thread that runs in the parent process.
+ * The fallout, which is limited to linuxthreads:
+ * The special log message won't be written when such a
+ * thread in the parent causes the parent to crash.
+ */
+ if (getpid() == parent_pid) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE,
+ 0, ap_server_conf,
+ "seg fault or similar nasty error detected "
+ "in the parent process");
+ /* XXX we can probably add some rudimentary cleanup code here,
+ * like getting rid of the pid file. If any additional bad stuff
+ * happens, we are protected from recursive errors taking down the
+ * system since this function is no longer the signal handler GLA
+ */
+ }
+ kill(getpid(), sig);
+ /* At this point we've got sig blocked, because we're still inside
+ * the signal handler. When we leave the signal handler it will
+ * be unblocked, and we'll take the signal... and coredump or whatever
+ * is appropriate for this particular Unix. In addition the parent
+ * will see the real signal we received -- whereas if we called
+ * abort() here, the parent would only see SIGABRT.
+ */
+}
+
+apr_status_t ap_fatal_signal_child_setup(server_rec *s)
+{
+ my_pid = getpid();
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_fatal_signal_setup(server_rec *s, apr_pool_t *in_pconf)
+{
+#ifndef NO_USE_SIGACTION
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+
+#if defined(SA_ONESHOT)
+ sa.sa_flags = SA_ONESHOT;
+#elif defined(SA_RESETHAND)
+ sa.sa_flags = SA_RESETHAND;
+#else
+ sa.sa_flags = 0;
+#endif
+
+ sa.sa_handler = sig_coredump;
+ if (sigaction(SIGSEGV, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, "sigaction(SIGSEGV)");
+#ifdef SIGBUS
+ if (sigaction(SIGBUS, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, "sigaction(SIGBUS)");
+#endif
+#ifdef SIGABORT
+ if (sigaction(SIGABORT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, "sigaction(SIGABORT)");
+#endif
+#ifdef SIGABRT
+ if (sigaction(SIGABRT, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, "sigaction(SIGABRT)");
+#endif
+#ifdef SIGILL
+ if (sigaction(SIGILL, &sa, NULL) < 0)
+ ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, "sigaction(SIGILL)");
+#endif
+
+#else /* NO_USE_SIGACTION */
+
+ apr_signal(SIGSEGV, sig_coredump);
+#ifdef SIGBUS
+ apr_signal(SIGBUS, sig_coredump);
+#endif /* SIGBUS */
+#ifdef SIGABORT
+ apr_signal(SIGABORT, sig_coredump);
+#endif /* SIGABORT */
+#ifdef SIGABRT
+ apr_signal(SIGABRT, sig_coredump);
+#endif /* SIGABRT */
+#ifdef SIGILL
+ apr_signal(SIGILL, sig_coredump);
+#endif /* SIGILL */
+
+#endif /* NO_USE_SIGACTION */
+
+ pconf = in_pconf;
+ parent_pid = my_pid = getpid();
+
+ return APR_SUCCESS;
+}
+
+#endif /* AP_MPM_WANT_FATAL_SIGNAL_HANDLER */
diff --git a/server/protocol.c b/server/protocol.c
new file mode 100644
index 00000000..b7145bb7
--- /dev/null
+++ b/server/protocol.c
@@ -0,0 +1,1643 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * http_protocol.c --- routines which directly communicate with the client.
+ *
+ * Code originally by Rob McCool; much redone by Robert S. Thau
+ * and the Apache Software Foundation.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_lib.h"
+#include "apr_signal.h"
+#include "apr_strmatch.h"
+
+#define APR_WANT_STDIO /* for sscanf */
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "util_filter.h"
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_main.h"
+#include "http_request.h"
+#include "http_vhost.h"
+#include "http_log.h" /* For errors detected in basic auth common
+ * support code... */
+#include "mod_core.h"
+#include "util_charset.h"
+#include "util_ebcdic.h"
+#include "scoreboard.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(post_read_request)
+ APR_HOOK_LINK(log_transaction)
+ APR_HOOK_LINK(http_scheme)
+ APR_HOOK_LINK(default_port)
+)
+
+AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL;
+
+
+/* Patterns to match in ap_make_content_type() */
+static const char *needcset[] = {
+ "text/plain",
+ "text/html",
+ NULL
+};
+static const apr_strmatch_pattern **needcset_patterns;
+static const apr_strmatch_pattern *charset_pattern;
+
+AP_DECLARE(void) ap_setup_make_content_type(apr_pool_t *pool)
+{
+ int i;
+ for (i = 0; needcset[i]; i++) {
+ continue;
+ }
+ needcset_patterns = (const apr_strmatch_pattern **)
+ apr_palloc(pool, (i + 1) * sizeof(apr_strmatch_pattern *));
+ for (i = 0; needcset[i]; i++) {
+ needcset_patterns[i] = apr_strmatch_precompile(pool, needcset[i], 0);
+ }
+ needcset_patterns[i] = NULL;
+ charset_pattern = apr_strmatch_precompile(pool, "charset=", 0);
+}
+
+/*
+ * Builds the content-type that should be sent to the client from the
+ * content-type specified. The following rules are followed:
+ * - if type is NULL, type is set to ap_default_type(r)
+ * - if charset adding is disabled, stop processing and return type.
+ * - then, if there are no parameters on type, add the default charset
+ * - return type
+ */
+AP_DECLARE(const char *)ap_make_content_type(request_rec *r, const char *type)
+{
+ const apr_strmatch_pattern **pcset;
+ core_dir_config *conf =
+ (core_dir_config *)ap_get_module_config(r->per_dir_config,
+ &core_module);
+ core_request_config *request_conf;
+ apr_size_t type_len;
+
+ if (!type) {
+ type = ap_default_type(r);
+ }
+
+ if (conf->add_default_charset != ADD_DEFAULT_CHARSET_ON) {
+ return type;
+ }
+
+ request_conf =
+ ap_get_module_config(r->request_config, &core_module);
+ if (request_conf->suppress_charset) {
+ return type;
+ }
+
+ type_len = strlen(type);
+
+ if (apr_strmatch(charset_pattern, type, type_len) != NULL) {
+ /* already has parameter, do nothing */
+ /* XXX we don't check the validity */
+ ;
+ }
+ else {
+ /* see if it makes sense to add the charset. At present,
+ * we only add it if the Content-type is one of needcset[]
+ */
+ for (pcset = needcset_patterns; *pcset ; pcset++) {
+ if (apr_strmatch(*pcset, type, type_len) != NULL) {
+ struct iovec concat[3];
+ concat[0].iov_base = (void *)type;
+ concat[0].iov_len = type_len;
+ concat[1].iov_base = (void *)"; charset=";
+ concat[1].iov_len = sizeof("; charset=") - 1;
+ concat[2].iov_base = (void *)(conf->add_default_charset_name);
+ concat[2].iov_len = strlen(conf->add_default_charset_name);
+ type = apr_pstrcatv(r->pool, concat, 3, NULL);
+ break;
+ }
+ }
+ }
+
+ return type;
+}
+
+AP_DECLARE(void) ap_set_content_length(request_rec *r, apr_off_t clength)
+{
+ r->clength = clength;
+ apr_table_setn(r->headers_out, "Content-Length",
+ apr_off_t_toa(r->pool, clength));
+}
+
+/*
+ * Return the latest rational time from a request/mtime (modification time)
+ * pair. We return the mtime unless it's in the future, in which case we
+ * return the current time. We use the request time as a reference in order
+ * to limit the number of calls to time(). We don't check for futurosity
+ * unless the mtime is at least as new as the reference.
+ */
+AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime)
+{
+ apr_time_t now;
+
+ /* For all static responses, it's almost certain that the file was
+ * last modified before the beginning of the request. So there's
+ * no reason to call time(NULL) again. But if the response has been
+ * created on demand, then it might be newer than the time the request
+ * started. In this event we really have to call time(NULL) again
+ * so that we can give the clients the most accurate Last-Modified. If we
+ * were given a time in the future, we return the current time - the
+ * Last-Modified can't be in the future.
+ */
+ now = (mtime < r->request_time) ? r->request_time : apr_time_now();
+ return (mtime > now) ? now : mtime;
+}
+
+/* Min # of bytes to allocate when reading a request line */
+#define MIN_LINE_ALLOC 80
+
+/* Get a line of protocol input, including any continuation lines
+ * caused by MIME folding (or broken clients) if fold != 0, and place it
+ * in the buffer s, of size n bytes, without the ending newline.
+ *
+ * If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool.
+ *
+ * Returns APR_SUCCESS if there are no problems and sets *read to be
+ * the full length of s.
+ *
+ * APR_ENOSPC is returned if there is not enough buffer space.
+ * Other errors may be returned on other errors.
+ *
+ * The LF is *not* returned in the buffer. Therefore, a *read of 0
+ * indicates that an empty line was read.
+ *
+ * Notes: Because the buffer uses 1 char for NUL, the most we can return is
+ * (n - 1) actual characters.
+ *
+ * If no LF is detected on the last line due to a dropped connection
+ * or a full buffer, that's considered an error.
+ */
+AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n,
+ apr_size_t *read, request_rec *r,
+ int fold, apr_bucket_brigade *bb)
+{
+ apr_status_t rv;
+ apr_bucket *e;
+ apr_size_t bytes_handled = 0, current_alloc = 0;
+ char *pos, *last_char = *s;
+ int do_alloc = (*s == NULL), saw_eos = 0;
+
+ /*
+ * Initialize last_char as otherwise a random value will be compared
+ * against APR_ASCII_LF at the end of the loop if bb only contains
+ * zero-length buckets.
+ */
+ if (last_char) {
+ *last_char = '\0';
+ }
+
+ for (;;) {
+ apr_brigade_cleanup(bb);
+ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE,
+ APR_BLOCK_READ, 0);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Something horribly wrong happened. Someone didn't block! */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ return APR_EGENERAL;
+ }
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ const char *str;
+ apr_size_t len;
+
+ /* If we see an EOS, don't bother doing anything more. */
+ if (APR_BUCKET_IS_EOS(e)) {
+ saw_eos = 1;
+ break;
+ }
+
+ rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (len == 0) {
+ /* no use attempting a zero-byte alloc (hurts when
+ * using --with-efence --enable-pool-debug) or
+ * doing any of the other logic either
+ */
+ continue;
+ }
+
+ /* Would this overrun our buffer? If so, we'll die. */
+ if (n < bytes_handled + len) {
+ *read = bytes_handled;
+ if (*s) {
+ /* ensure this string is NUL terminated */
+ if (bytes_handled > 0) {
+ (*s)[bytes_handled-1] = '\0';
+ }
+ else {
+ (*s)[0] = '\0';
+ }
+ }
+ return APR_ENOSPC;
+ }
+
+ /* Do we have to handle the allocation ourselves? */
+ if (do_alloc) {
+ /* We'll assume the common case where one bucket is enough. */
+ if (!*s) {
+ current_alloc = len;
+ if (current_alloc < MIN_LINE_ALLOC) {
+ current_alloc = MIN_LINE_ALLOC;
+ }
+ *s = apr_palloc(r->pool, current_alloc);
+ }
+ else if (bytes_handled + len > current_alloc) {
+ /* Increase the buffer size */
+ apr_size_t new_size = current_alloc * 2;
+ char *new_buffer;
+
+ if (bytes_handled + len > new_size) {
+ new_size = (bytes_handled + len) * 2;
+ }
+
+ new_buffer = apr_palloc(r->pool, new_size);
+
+ /* Copy what we already had. */
+ memcpy(new_buffer, *s, bytes_handled);
+ current_alloc = new_size;
+ *s = new_buffer;
+ }
+ }
+
+ /* Just copy the rest of the data to the end of the old buffer. */
+ pos = *s + bytes_handled;
+ memcpy(pos, str, len);
+ last_char = pos + len - 1;
+
+ /* We've now processed that new data - update accordingly. */
+ bytes_handled += len;
+ }
+
+ /* If we got a full line of input, stop reading */
+ if (last_char && (*last_char == APR_ASCII_LF)) {
+ break;
+ }
+ }
+
+ /* Now NUL-terminate the string at the end of the line;
+ * if the last-but-one character is a CR, terminate there */
+ if (last_char > *s && last_char[-1] == APR_ASCII_CR) {
+ last_char--;
+ }
+ *last_char = '\0';
+ bytes_handled = last_char - *s;
+
+ /* If we're folding, we have more work to do.
+ *
+ * Note that if an EOS was seen, we know we can't have another line.
+ */
+ if (fold && bytes_handled && !saw_eos) {
+ for (;;) {
+ const char *str;
+ apr_size_t len;
+ char c;
+
+ /* Clear the temp brigade for this filter read. */
+ apr_brigade_cleanup(bb);
+
+ /* We only care about the first byte. */
+ rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE,
+ APR_BLOCK_READ, 1);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (APR_BRIGADE_EMPTY(bb)) {
+ break;
+ }
+
+ e = APR_BRIGADE_FIRST(bb);
+
+ /* If we see an EOS, don't bother doing anything more. */
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+
+ rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bb);
+ return rv;
+ }
+
+ /* Found one, so call ourselves again to get the next line.
+ *
+ * FIXME: If the folding line is completely blank, should we
+ * stop folding? Does that require also looking at the next
+ * char?
+ */
+ /* When we call destroy, the buckets are deleted, so save that
+ * one character we need. This simplifies our execution paths
+ * at the cost of one character read.
+ */
+ c = *str;
+ if (c == APR_ASCII_BLANK || c == APR_ASCII_TAB) {
+ /* Do we have enough space? We may be full now. */
+ if (bytes_handled >= n) {
+ *read = n;
+ /* ensure this string is terminated */
+ (*s)[n-1] = '\0';
+ return APR_ENOSPC;
+ }
+ else {
+ apr_size_t next_size, next_len;
+ char *tmp;
+
+ /* If we're doing the allocations for them, we have to
+ * give ourselves a NULL and copy it on return.
+ */
+ if (do_alloc) {
+ tmp = NULL;
+ } else {
+ /* We're null terminated. */
+ tmp = last_char;
+ }
+
+ next_size = n - bytes_handled;
+
+ rv = ap_rgetline_core(&tmp, next_size,
+ &next_len, r, 0, bb);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (do_alloc && next_len > 0) {
+ char *new_buffer;
+ apr_size_t new_size = bytes_handled + next_len + 1;
+
+ /* we need to alloc an extra byte for a null */
+ new_buffer = apr_palloc(r->pool, new_size);
+
+ /* Copy what we already had. */
+ memcpy(new_buffer, *s, bytes_handled);
+
+ /* copy the new line, including the trailing null */
+ memcpy(new_buffer + bytes_handled, tmp, next_len + 1);
+ *s = new_buffer;
+ }
+
+ last_char += next_len;
+ bytes_handled += next_len;
+ }
+ }
+ else { /* next character is not tab or space */
+ break;
+ }
+ }
+ }
+
+ *read = bytes_handled;
+ return APR_SUCCESS;
+}
+
+#if APR_CHARSET_EBCDIC
+AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n,
+ apr_size_t *read, request_rec *r,
+ int fold, apr_bucket_brigade *bb)
+{
+ /* on ASCII boxes, ap_rgetline is a macro which simply invokes
+ * ap_rgetline_core with the same parms
+ *
+ * on EBCDIC boxes, each complete http protocol input line needs to be
+ * translated into the code page used by the compiler. Since
+ * ap_rgetline_core uses recursion, we do the translation in a wrapper
+ * function to insure that each input character gets translated only once.
+ */
+ apr_status_t rv;
+
+ rv = ap_rgetline_core(s, n, read, r, fold, bb);
+ if (rv == APR_SUCCESS) {
+ ap_xlate_proto_from_ascii(*s, *read);
+ }
+ return rv;
+}
+#endif
+
+AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold)
+{
+ char *tmp_s = s;
+ apr_status_t rv;
+ apr_size_t len;
+ apr_bucket_brigade *tmp_bb;
+
+ tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ rv = ap_rgetline(&tmp_s, n, &len, r, fold, tmp_bb);
+ apr_brigade_destroy(tmp_bb);
+
+ /* Map the out-of-space condition to the old API. */
+ if (rv == APR_ENOSPC) {
+ return n;
+ }
+
+ /* Anything else is just bad. */
+ if (rv != APR_SUCCESS) {
+ return -1;
+ }
+
+ return (int)len;
+}
+
+/* parse_uri: break apart the uri
+ * Side Effects:
+ * - sets r->args to rest after '?' (or NULL if no '?')
+ * - sets r->uri to request uri (without r->args part)
+ * - sets r->hostname (if not set already) from request (scheme://host:port)
+ */
+AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri)
+{
+ int status = HTTP_OK;
+
+ r->unparsed_uri = apr_pstrdup(r->pool, uri);
+
+ /* http://issues.apache.org/bugzilla/show_bug.cgi?id=31875
+ * http://issues.apache.org/bugzilla/show_bug.cgi?id=28450
+ *
+ * This is not in fact a URI, it's a path. That matters in the
+ * case of a leading double-slash. We need to resolve the issue
+ * by normalising that out before treating it as a URI.
+ */
+ while ((uri[0] == '/') && (uri[1] == '/')) {
+ ++uri ;
+ }
+ if (r->method_number == M_CONNECT) {
+ status = apr_uri_parse_hostinfo(r->pool, uri, &r->parsed_uri);
+ }
+ else {
+ /* Simple syntax Errors in URLs are trapped by
+ * parse_uri_components().
+ */
+ status = apr_uri_parse(r->pool, uri, &r->parsed_uri);
+ }
+
+ if (status == APR_SUCCESS) {
+ /* if it has a scheme we may need to do absoluteURI vhost stuff */
+ if (r->parsed_uri.scheme
+ && !strcasecmp(r->parsed_uri.scheme, ap_http_scheme(r))) {
+ r->hostname = r->parsed_uri.hostname;
+ }
+ else if (r->method_number == M_CONNECT) {
+ r->hostname = r->parsed_uri.hostname;
+ }
+
+ r->args = r->parsed_uri.query;
+ r->uri = r->parsed_uri.path ? r->parsed_uri.path
+ : apr_pstrdup(r->pool, "/");
+
+#if defined(OS2) || defined(WIN32)
+ /* Handle path translations for OS/2 and plug security hole.
+ * This will prevent "http://www.wherever.com/..\..\/" from
+ * returning a directory for the root drive.
+ */
+ {
+ char *x;
+
+ for (x = r->uri; (x = strchr(x, '\\')) != NULL; )
+ *x = '/';
+ }
+#endif /* OS2 || WIN32 */
+ }
+ else {
+ r->args = NULL;
+ r->hostname = NULL;
+ r->status = HTTP_BAD_REQUEST; /* set error status */
+ r->uri = apr_pstrdup(r->pool, uri);
+ }
+}
+
+static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
+{
+ const char *ll;
+ const char *uri;
+ const char *pro;
+
+#if 0
+ conn_rec *conn = r->connection;
+#endif
+ int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */
+ char http[5];
+ apr_size_t len;
+ int num_blank_lines = 0;
+ int max_blank_lines = r->server->limit_req_fields;
+
+ if (max_blank_lines <= 0) {
+ max_blank_lines = DEFAULT_LIMIT_REQUEST_FIELDS;
+ }
+
+ /* Read past empty lines until we get a real request line,
+ * a read error, the connection closes (EOF), or we timeout.
+ *
+ * We skip empty lines because browsers have to tack a CRLF on to the end
+ * of POSTs to support old CERN webservers. But note that we may not
+ * have flushed any previous response completely to the client yet.
+ * We delay the flush as long as possible so that we can improve
+ * performance for clients that are pipelining requests. If a request
+ * is pipelined then we won't block during the (implicit) read() below.
+ * If the requests aren't pipelined, then the client is still waiting
+ * for the final buffer flush from us, and we will block in the implicit
+ * read(). B_SAFEREAD ensures that the BUFF layer flushes if it will
+ * have to block during a read.
+ */
+
+ do {
+ apr_status_t rv;
+
+ /* insure ap_rgetline allocates memory each time thru the loop
+ * if there are empty lines
+ */
+ r->the_request = NULL;
+ rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2),
+ &len, r, 0, bb);
+
+ if (rv != APR_SUCCESS) {
+ r->request_time = apr_time_now();
+
+ /* ap_rgetline returns APR_ENOSPC if it fills up the
+ * buffer before finding the end-of-line. This is only going to
+ * happen if it exceeds the configured limit for a request-line.
+ */
+ if (rv == APR_ENOSPC) {
+ r->status = HTTP_REQUEST_URI_TOO_LARGE;
+ r->proto_num = HTTP_VERSION(1,0);
+ r->protocol = apr_pstrdup(r->pool, "HTTP/1.0");
+ }
+ return 0;
+ }
+ } while ((len <= 0) && (++num_blank_lines < max_blank_lines));
+
+ /* we've probably got something to do, ignore graceful restart requests */
+
+ r->request_time = apr_time_now();
+ ll = r->the_request;
+ r->method = ap_getword_white(r->pool, &ll);
+
+#if 0
+/* XXX If we want to keep track of the Method, the protocol module should do
+ * it. That support isn't in the scoreboard yet. Hopefully next week
+ * sometime. rbb */
+ ap_update_connection_status(AP_CHILD_THREAD_FROM_ID(conn->id), "Method",
+ r->method);
+#endif
+
+ uri = ap_getword_white(r->pool, &ll);
+
+ /* Provide quick information about the request method as soon as known */
+
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
+ }
+
+ ap_parse_uri(r, uri);
+
+ if (ll[0]) {
+ r->assbackwards = 0;
+ pro = ll;
+ len = strlen(ll);
+ } else {
+ r->assbackwards = 1;
+ pro = "HTTP/0.9";
+ len = 8;
+ }
+ r->protocol = apr_pstrmemdup(r->pool, pro, len);
+
+ /* XXX ap_update_connection_status(conn->id, "Protocol", r->protocol); */
+
+ /* Avoid sscanf in the common case */
+ if (len == 8
+ && pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P'
+ && pro[4] == '/' && apr_isdigit(pro[5]) && pro[6] == '.'
+ && apr_isdigit(pro[7])) {
+ r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0');
+ }
+ else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor)
+ && (strcasecmp("http", http) == 0)
+ && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */
+ r->proto_num = HTTP_VERSION(major, minor);
+ else
+ r->proto_num = HTTP_VERSION(1, 0);
+
+ return 1;
+}
+
+AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb)
+{
+ char *last_field = NULL;
+ apr_size_t last_len = 0;
+ apr_size_t alloc_len = 0;
+ char *field;
+ char *value;
+ apr_size_t len;
+ int fields_read = 0;
+ char *tmp_field;
+
+ /*
+ * Read header lines until we get the empty separator line, a read error,
+ * the connection closes (EOF), reach the server limit, or we timeout.
+ */
+ while(1) {
+ apr_status_t rv;
+ int folded = 0;
+
+ field = NULL;
+ rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2,
+ &len, r, 0, bb);
+
+ if (rv != APR_SUCCESS) {
+ r->status = HTTP_BAD_REQUEST;
+
+ /* ap_rgetline returns APR_ENOSPC if it fills up the buffer before
+ * finding the end-of-line. This is only going to happen if it
+ * exceeds the configured limit for a field size.
+ */
+ if (rv == APR_ENOSPC && field) {
+ /* insure ap_escape_html will terminate correctly */
+ field[len - 1] = '\0';
+ apr_table_setn(r->notes, "error-notes",
+ apr_pstrcat(r->pool,
+ "Size of a request header field "
+ "exceeds server limit.<br />\n"
+ "<pre>\n",
+ ap_escape_html(r->pool, field),
+ "</pre>\n", NULL));
+ }
+ return;
+ }
+
+ if (last_field != NULL) {
+ if ((len > 0) && ((*field == '\t') || *field == ' ')) {
+ /* This line is a continuation of the preceding line(s),
+ * so append it to the line that we've set aside.
+ * Note: this uses a power-of-two allocator to avoid
+ * doing O(n) allocs and using O(n^2) space for
+ * continuations that span many many lines.
+ */
+ apr_size_t fold_len = last_len + len + 1; /* trailing null */
+
+ if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) {
+ r->status = HTTP_BAD_REQUEST;
+ /* report what we have accumulated so far before the
+ * overflow (last_field) as the field with the problem
+ */
+ apr_table_setn(r->notes, "error-notes",
+ apr_pstrcat(r->pool,
+ "Size of a request header field "
+ "after folding "
+ "exceeds server limit.<br />\n"
+ "<pre>\n",
+ ap_escape_html(r->pool, last_field),
+ "</pre>\n", NULL));
+ return;
+ }
+
+ if (fold_len > alloc_len) {
+ char *fold_buf;
+ alloc_len += alloc_len;
+ if (fold_len > alloc_len) {
+ alloc_len = fold_len;
+ }
+ fold_buf = (char *)apr_palloc(r->pool, alloc_len);
+ memcpy(fold_buf, last_field, last_len);
+ last_field = fold_buf;
+ }
+ memcpy(last_field + last_len, field, len +1); /* +1 for nul */
+ last_len += len;
+ folded = 1;
+ }
+ else /* not a continuation line */ {
+
+ if (r->server->limit_req_fields
+ && (++fields_read > r->server->limit_req_fields)) {
+ r->status = HTTP_BAD_REQUEST;
+ apr_table_setn(r->notes, "error-notes",
+ "The number of request header fields "
+ "exceeds this server's limit.");
+ return;
+ }
+
+ if (!(value = strchr(last_field, ':'))) { /* Find ':' or */
+ r->status = HTTP_BAD_REQUEST; /* abort bad request */
+ apr_table_setn(r->notes, "error-notes",
+ apr_pstrcat(r->pool,
+ "Request header field is "
+ "missing ':' separator.<br />\n"
+ "<pre>\n",
+ ap_escape_html(r->pool,
+ last_field),
+ "</pre>\n", NULL));
+ return;
+ }
+
+ tmp_field = value - 1; /* last character of field-name */
+
+ *value++ = '\0'; /* NUL-terminate at colon */
+
+ while (*value == ' ' || *value == '\t') {
+ ++value; /* Skip to start of value */
+ }
+
+ /* Strip LWS after field-name: */
+ while (tmp_field > last_field
+ && (*tmp_field == ' ' || *tmp_field == '\t')) {
+ *tmp_field-- = '\0';
+ }
+
+ /* Strip LWS after field-value: */
+ tmp_field = last_field + last_len - 1;
+ while (tmp_field > value
+ && (*tmp_field == ' ' || *tmp_field == '\t')) {
+ *tmp_field-- = '\0';
+ }
+
+ apr_table_addn(r->headers_in, last_field, value);
+
+ /* reset the alloc_len so that we'll allocate a new
+ * buffer if we have to do any more folding: we can't
+ * use the previous buffer because its contents are
+ * now part of r->headers_in
+ */
+ alloc_len = 0;
+
+ } /* end if current line is not a continuation starting with tab */
+ }
+
+ /* Found a blank line, stop. */
+ if (len == 0) {
+ break;
+ }
+
+ /* Keep track of this line so that we can parse it on
+ * the next loop iteration. (In the folded case, last_field
+ * has been updated already.)
+ */
+ if (!folded) {
+ last_field = field;
+ last_len = len;
+ }
+ }
+
+ apr_table_compress(r->headers_in, APR_OVERLAP_TABLES_MERGE);
+}
+
+AP_DECLARE(void) ap_get_mime_headers(request_rec *r)
+{
+ apr_bucket_brigade *tmp_bb;
+ tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ ap_get_mime_headers_core(r, tmp_bb);
+ apr_brigade_destroy(tmp_bb);
+}
+
+request_rec *ap_read_request(conn_rec *conn)
+{
+ request_rec *r;
+ apr_pool_t *p;
+ const char *expect;
+ int access_status;
+ apr_bucket_brigade *tmp_bb;
+ apr_socket_t *csd;
+ apr_interval_time_t cur_timeout;
+
+ apr_pool_create(&p, conn->pool);
+ apr_pool_tag(p, "request");
+ r = apr_pcalloc(p, sizeof(request_rec));
+ r->pool = p;
+ r->connection = conn;
+ r->server = conn->base_server;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_make(r->pool, 25);
+ r->subprocess_env = apr_table_make(r->pool, 25);
+ r->headers_out = apr_table_make(r->pool, 12);
+ r->err_headers_out = apr_table_make(r->pool, 5);
+ r->notes = apr_table_make(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ /* Must be set before we run create request hook */
+
+ r->proto_output_filters = conn->output_filters;
+ r->output_filters = r->proto_output_filters;
+ r->proto_input_filters = conn->input_filters;
+ r->input_filters = r->proto_input_filters;
+ ap_run_create_request(r);
+ r->per_dir_config = r->server->lookup_defaults;
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_REQUEST_TIME_OUT; /* Until we get a request */
+ r->the_request = NULL;
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+ tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ /* Get the request... */
+ if (!read_request_line(r, tmp_bb)) {
+ if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "request failed: URI too long (longer than %d)", r->server->limit_req_line);
+ ap_send_error_response(r, 0);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ apr_brigade_destroy(tmp_bb);
+ return r;
+ }
+
+ apr_brigade_destroy(tmp_bb);
+ return NULL;
+ }
+
+ /* We may have been in keep_alive_timeout mode, so toggle back
+ * to the normal timeout mode as we fetch the header lines,
+ * as necessary.
+ */
+ csd = ap_get_module_config(conn->conn_config, &core_module);
+ apr_socket_timeout_get(csd, &cur_timeout);
+ if (cur_timeout != conn->base_server->timeout) {
+ apr_socket_timeout_set(csd, conn->base_server->timeout);
+ cur_timeout = conn->base_server->timeout;
+ }
+
+ if (!r->assbackwards) {
+ ap_get_mime_headers_core(r, tmp_bb);
+ if (r->status != HTTP_REQUEST_TIME_OUT) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "request failed: error reading the headers");
+ ap_send_error_response(r, 0);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ apr_brigade_destroy(tmp_bb);
+ return r;
+ }
+
+ if (apr_table_get(r->headers_in, "Transfer-Encoding")
+ && apr_table_get(r->headers_in, "Content-Length")) {
+ /* 2616 section 4.4, point 3: "if both Transfer-Encoding
+ * and Content-Length are received, the latter MUST be
+ * ignored"; so unset it here to prevent any confusion
+ * later. */
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+ }
+ else {
+ if (r->header_only) {
+ /*
+ * Client asked for headers only with HTTP/0.9, which doesn't send
+ * headers! Have to dink things just to make sure the error message
+ * comes through...
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "client sent invalid HTTP/0.9 request: HEAD %s",
+ r->uri);
+ r->header_only = 0;
+ r->status = HTTP_BAD_REQUEST;
+ ap_send_error_response(r, 0);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ apr_brigade_destroy(tmp_bb);
+ return r;
+ }
+ }
+
+ apr_brigade_destroy(tmp_bb);
+
+ r->status = HTTP_OK; /* Until further notice. */
+
+ /* update what we think the virtual host is based on the headers we've
+ * now read. may update status.
+ */
+ ap_update_vhost_from_headers(r);
+
+ /* Toggle to the Host:-based vhost's timeout mode to fetch the
+ * request body and send the response body, if needed.
+ */
+ if (cur_timeout != r->server->timeout) {
+ apr_socket_timeout_set(csd, r->server->timeout);
+ cur_timeout = r->server->timeout;
+ }
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ if ((!r->hostname && (r->proto_num >= HTTP_VERSION(1, 1)))
+ || ((r->proto_num == HTTP_VERSION(1, 1))
+ && !apr_table_get(r->headers_in, "Host"))) {
+ /*
+ * Client sent us an HTTP/1.1 or later request without telling us the
+ * hostname, either with a full URL or a Host: header. We therefore
+ * need to (as per the 1.1 spec) send an error. As a special case,
+ * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain
+ * a Host: header, and the server MUST respond with 400 if it doesn't.
+ */
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "client sent HTTP/1.1 request without hostname "
+ "(see RFC2616 section 14.23): %s", r->uri);
+ }
+
+ /*
+ * Add the HTTP_IN filter here to ensure that ap_discard_request_body
+ * called by ap_die and by ap_send_error_response works correctly on
+ * status codes that do not cause the connection to be dropped and
+ * in situations where the connection should be kept alive.
+ */
+
+ ap_add_input_filter_handle(ap_http_input_filter_handle,
+ NULL, r, r->connection);
+
+ if (r->status != HTTP_OK) {
+ ap_send_error_response(r, 0);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ return r;
+ }
+
+ if ((access_status = ap_run_post_read_request(r))) {
+ ap_die(access_status, r);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ return NULL;
+ }
+
+ if (((expect = apr_table_get(r->headers_in, "Expect")) != NULL)
+ && (expect[0] != '\0')) {
+ /*
+ * The Expect header field was added to HTTP/1.1 after RFC 2068
+ * as a means to signal when a 100 response is desired and,
+ * unfortunately, to signal a poor man's mandatory extension that
+ * the server must understand or return 417 Expectation Failed.
+ */
+ if (strcasecmp(expect, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ r->status = HTTP_EXPECTATION_FAILED;
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "client sent an unrecognized expectation value of "
+ "Expect: %s", expect);
+ ap_send_error_response(r, 0);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ return r;
+ }
+ }
+
+ return r;
+}
+
+/* if a request with a body creates a subrequest, clone the original request's
+ * input headers minus any headers pertaining to the body which has already
+ * been read. out-of-line helper function for ap_set_sub_req_protocol.
+ */
+
+static void clone_headers_no_body(request_rec *rnew,
+ const request_rec *r)
+{
+ rnew->headers_in = apr_table_copy(rnew->pool, r->headers_in);
+ apr_table_unset(rnew->headers_in, "Content-Encoding");
+ apr_table_unset(rnew->headers_in, "Content-Language");
+ apr_table_unset(rnew->headers_in, "Content-Length");
+ apr_table_unset(rnew->headers_in, "Content-Location");
+ apr_table_unset(rnew->headers_in, "Content-MD5");
+ apr_table_unset(rnew->headers_in, "Content-Range");
+ apr_table_unset(rnew->headers_in, "Content-Type");
+ apr_table_unset(rnew->headers_in, "Expires");
+ apr_table_unset(rnew->headers_in, "Last-Modified");
+ apr_table_unset(rnew->headers_in, "Transfer-Encoding");
+}
+
+/*
+ * A couple of other functions which initialize some of the fields of
+ * a request structure, as appropriate for adjuncts of one kind or another
+ * to a request in progress. Best here, rather than elsewhere, since
+ * *someone* has to set the protocol-specific fields...
+ */
+
+AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew,
+ const request_rec *r)
+{
+ rnew->the_request = r->the_request; /* Keep original request-line */
+
+ rnew->assbackwards = 1; /* Don't send headers from this. */
+ rnew->no_local_copy = 1; /* Don't try to send HTTP_NOT_MODIFIED for a
+ * fragment. */
+ rnew->method = "GET";
+ rnew->method_number = M_GET;
+ rnew->protocol = "INCLUDED";
+
+ rnew->status = HTTP_OK;
+
+ /* did the original request have a body? (e.g. POST w/SSI tags)
+ * if so, make sure the subrequest doesn't inherit body headers
+ */
+ if (apr_table_get(r->headers_in, "Content-Length")
+ || apr_table_get(r->headers_in, "Transfer-Encoding")) {
+ clone_headers_no_body(rnew, r);
+ } else {
+ /* no body (common case). clone headers the cheap way */
+ rnew->headers_in = r->headers_in;
+ }
+ rnew->subprocess_env = apr_table_copy(rnew->pool, r->subprocess_env);
+ rnew->headers_out = apr_table_make(rnew->pool, 5);
+ rnew->err_headers_out = apr_table_make(rnew->pool, 5);
+ rnew->notes = apr_table_make(rnew->pool, 5);
+
+ rnew->expecting_100 = r->expecting_100;
+ rnew->read_length = r->read_length;
+ rnew->read_body = REQUEST_NO_BODY;
+
+ rnew->main = (request_rec *) r;
+}
+
+static void end_output_stream(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(r->output_filters, bb);
+}
+
+AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub)
+{
+ /* tell the filter chain there is no more content coming */
+ if (!sub->eos_sent) {
+ end_output_stream(sub);
+ }
+}
+
+/* finalize_request_protocol is called at completion of sending the
+ * response. Its sole purpose is to send the terminating protocol
+ * information for any wrappers around the response message body
+ * (i.e., transfer encodings). It should have been named finalize_response.
+ */
+AP_DECLARE(void) ap_finalize_request_protocol(request_rec *r)
+{
+ (void) ap_discard_request_body(r);
+
+ /* tell the filter chain there is no more content coming */
+ if (!r->eos_sent) {
+ end_output_stream(r);
+ }
+}
+
+/*
+ * Support for the Basic authentication protocol, and a bit for Digest.
+ */
+AP_DECLARE(void) ap_note_auth_failure(request_rec *r)
+{
+ const char *type = ap_auth_type(r);
+ if (type) {
+ if (!strcasecmp(type, "Basic"))
+ ap_note_basic_auth_failure(r);
+ else if (!strcasecmp(type, "Digest"))
+ ap_note_digest_auth_failure(r);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR,
+ 0, r, "need AuthType to note auth failure: %s", r->uri);
+ }
+}
+
+AP_DECLARE(void) ap_note_basic_auth_failure(request_rec *r)
+{
+ const char *type = ap_auth_type(r);
+
+ /* if there is no AuthType configure or it is something other than
+ * Basic, let ap_note_auth_failure() deal with it
+ */
+ if (!type || strcasecmp(type, "Basic"))
+ ap_note_auth_failure(r);
+ else
+ apr_table_setn(r->err_headers_out,
+ (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
+ : "WWW-Authenticate",
+ apr_pstrcat(r->pool, "Basic realm=\"", ap_auth_name(r),
+ "\"", NULL));
+}
+
+AP_DECLARE(void) ap_note_digest_auth_failure(request_rec *r)
+{
+ apr_table_setn(r->err_headers_out,
+ (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
+ : "WWW-Authenticate",
+ apr_psprintf(r->pool, "Digest realm=\"%s\", nonce=\""
+ "%" APR_UINT64_T_HEX_FMT "\"",
+ ap_auth_name(r), (apr_uint64_t)r->request_time));
+}
+
+AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw)
+{
+ const char *auth_line = apr_table_get(r->headers_in,
+ (PROXYREQ_PROXY == r->proxyreq)
+ ? "Proxy-Authorization"
+ : "Authorization");
+ const char *t;
+
+ if (!(t = ap_auth_type(r)) || strcasecmp(t, "Basic"))
+ return DECLINED;
+
+ if (!ap_auth_name(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR,
+ 0, r, "need AuthName: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!auth_line) {
+ ap_note_basic_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+ /* Client tried to authenticate using wrong auth scheme */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "client used wrong authentication scheme: %s", r->uri);
+ ap_note_basic_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ while (*auth_line == ' ' || *auth_line == '\t') {
+ auth_line++;
+ }
+
+ t = ap_pbase64decode(r->pool, auth_line);
+ r->user = ap_getword_nulls (r->pool, &t, ':');
+ r->ap_auth_type = "Basic";
+
+ *pw = t;
+
+ return OK;
+}
+
+struct content_length_ctx {
+ int data_sent; /* true if the C-L filter has already sent at
+ * least one bucket on to the next output filter
+ * for this request
+ */
+};
+
+/* This filter computes the content length, but it also computes the number
+ * of bytes sent to the client. This means that this filter will always run
+ * through all of the buckets in all brigades
+ */
+AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter(
+ ap_filter_t *f,
+ apr_bucket_brigade *b)
+{
+ request_rec *r = f->r;
+ struct content_length_ctx *ctx;
+ apr_bucket *e;
+ int eos = 0;
+ apr_read_type_e eblock = APR_NONBLOCK_READ;
+
+ ctx = f->ctx;
+ if (!ctx) {
+ f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
+ ctx->data_sent = 0;
+ }
+
+ /* Loop through this set of buckets to compute their length
+ */
+ e = APR_BRIGADE_FIRST(b);
+ while (e != APR_BRIGADE_SENTINEL(b)) {
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos = 1;
+ break;
+ }
+ if (e->length == (apr_size_t)-1) {
+ apr_size_t len;
+ const char *ignored;
+ apr_status_t rv;
+
+ /* This is probably a pipe bucket. Send everything
+ * prior to this, and then read the data for this bucket.
+ */
+ rv = apr_bucket_read(e, &ignored, &len, eblock);
+ if (rv == APR_SUCCESS) {
+ /* Attempt a nonblocking read next time through */
+ eblock = APR_NONBLOCK_READ;
+ r->bytes_sent += len;
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* Output everything prior to this bucket, and then
+ * do a blocking read on the next batch.
+ */
+ if (e != APR_BRIGADE_FIRST(b)) {
+ apr_bucket_brigade *split = apr_brigade_split(b, e);
+ apr_bucket *flush = apr_bucket_flush_create(r->connection->bucket_alloc);
+
+ APR_BRIGADE_INSERT_TAIL(b, flush);
+ rv = ap_pass_brigade(f->next, b);
+ if (rv != APR_SUCCESS || f->c->aborted) {
+ apr_brigade_destroy(split);
+ return rv;
+ }
+ b = split;
+ e = APR_BRIGADE_FIRST(b);
+
+ ctx->data_sent = 1;
+ }
+ eblock = APR_BLOCK_READ;
+ continue;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "ap_content_length_filter: "
+ "apr_bucket_read() failed");
+ return rv;
+ }
+ }
+ else {
+ r->bytes_sent += e->length;
+ }
+ e = APR_BUCKET_NEXT(e);
+ }
+
+ /* If we've now seen the entire response and it's otherwise
+ * okay to set the C-L in the response header, then do so now.
+ *
+ * We can only set a C-L in the response header if we haven't already
+ * sent any buckets on to the next output filter for this request.
+ */
+ if (ctx->data_sent == 0 && eos &&
+ /* don't whack the C-L if it has already been set for a HEAD
+ * by something like proxy. the brigade only has an EOS bucket
+ * in this case, making r->bytes_sent zero.
+ *
+ * if r->bytes_sent > 0 we have a (temporary) body whose length may
+ * have been changed by a filter. the C-L header might not have been
+ * updated so we do it here. long term it would be cleaner to have
+ * such filters update or remove the C-L header, and just use it
+ * if present.
+ */
+ !(r->header_only && r->bytes_sent == 0 &&
+ apr_table_get(r->headers_out, "Content-Length"))) {
+ ap_set_content_length(r, r->bytes_sent);
+ }
+
+ ctx->data_sent = 1;
+ return ap_pass_brigade(f->next, b);
+}
+
+/*
+ * Send the body of a response to the client.
+ */
+AP_DECLARE(apr_status_t) ap_send_fd(apr_file_t *fd, request_rec *r,
+ apr_off_t offset, apr_size_t len,
+ apr_size_t *nbytes)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb = NULL;
+ apr_bucket *b;
+ apr_status_t rv;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_file_create(fd, offset, len, r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ rv = ap_pass_brigade(r->output_filters, bb);
+ if (rv != APR_SUCCESS) {
+ *nbytes = 0; /* no way to tell how many were actually sent */
+ }
+ else {
+ *nbytes = len;
+ }
+
+ return rv;
+}
+
+#if APR_HAS_MMAP
+/* send data from an in-memory buffer */
+AP_DECLARE(size_t) ap_send_mmap(apr_mmap_t *mm, request_rec *r, size_t offset,
+ size_t length)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb = NULL;
+ apr_bucket *b;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_mmap_create(mm, offset, length, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(r->output_filters, bb);
+
+ return mm->size; /* XXX - change API to report apr_status_t? */
+}
+#endif /* APR_HAS_MMAP */
+
+typedef struct {
+ apr_bucket_brigade *bb;
+} old_write_filter_ctx;
+
+AP_CORE_DECLARE_NONSTD(apr_status_t) ap_old_write_filter(
+ ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ old_write_filter_ctx *ctx = f->ctx;
+
+ AP_DEBUG_ASSERT(ctx);
+
+ if (ctx->bb != 0) {
+ /* whatever is coming down the pipe (we don't care), we
+ * can simply insert our buffered data at the front and
+ * pass the whole bundle down the chain.
+ */
+ APR_BRIGADE_CONCAT(ctx->bb, bb);
+ bb = ctx->bb;
+ ctx->bb = NULL;
+ }
+
+ return ap_pass_brigade(f->next, bb);
+}
+
+static apr_status_t buffer_output(request_rec *r,
+ const char *str, apr_size_t len)
+{
+ conn_rec *c = r->connection;
+ ap_filter_t *f;
+ old_write_filter_ctx *ctx;
+
+ if (len == 0)
+ return APR_SUCCESS;
+
+ /* future optimization: record some flags in the request_rec to
+ * say whether we've added our filter, and whether it is first.
+ */
+
+ /* this will typically exit on the first test */
+ for (f = r->output_filters; f != NULL; f = f->next) {
+ if (ap_old_write_func == f->frec)
+ break;
+ }
+
+ if (f == NULL) {
+ /* our filter hasn't been added yet */
+ ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ap_add_output_filter("OLD_WRITE", ctx, r, r->connection);
+ f = r->output_filters;
+ }
+
+ /* if the first filter is not our buffering filter, then we have to
+ * deliver the content through the normal filter chain
+ */
+ if (f != r->output_filters) {
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ apr_bucket *b = apr_bucket_transient_create(str, len, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ return ap_pass_brigade(r->output_filters, bb);
+ }
+
+ /* grab the context from our filter */
+ ctx = r->output_filters->ctx;
+
+ if (ctx->bb == NULL) {
+ ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ }
+
+ return ap_fwrite(f->next, ctx->bb, str, len);
+}
+
+AP_DECLARE(int) ap_rputc(int c, request_rec *r)
+{
+ char c2 = (char)c;
+
+ if (r->connection->aborted) {
+ return -1;
+ }
+
+ if (buffer_output(r, &c2, 1) != APR_SUCCESS)
+ return -1;
+
+ return c;
+}
+
+AP_DECLARE(int) ap_rputs(const char *str, request_rec *r)
+{
+ apr_size_t len;
+
+ if (r->connection->aborted)
+ return -1;
+
+ if (buffer_output(r, str, len = strlen(str)) != APR_SUCCESS)
+ return -1;
+
+ return len;
+}
+
+AP_DECLARE(int) ap_rwrite(const void *buf, int nbyte, request_rec *r)
+{
+ if (r->connection->aborted)
+ return -1;
+
+ if (buffer_output(r, buf, nbyte) != APR_SUCCESS)
+ return -1;
+
+ return nbyte;
+}
+
+struct ap_vrprintf_data {
+ apr_vformatter_buff_t vbuff;
+ request_rec *r;
+ char *buff;
+};
+
+static apr_status_t r_flush(apr_vformatter_buff_t *buff)
+{
+ /* callback function passed to ap_vformatter to be called when
+ * vformatter needs to write into buff and buff.curpos > buff.endpos */
+
+ /* ap_vrprintf_data passed as a apr_vformatter_buff_t, which is then
+ * "downcast" to an ap_vrprintf_data */
+ struct ap_vrprintf_data *vd = (struct ap_vrprintf_data*)buff;
+
+ if (vd->r->connection->aborted)
+ return -1;
+
+ /* r_flush is called when vbuff is completely full */
+ if (buffer_output(vd->r, vd->buff, AP_IOBUFSIZE)) {
+ return -1;
+ }
+
+ /* reset the buffer position */
+ vd->vbuff.curpos = vd->buff;
+ vd->vbuff.endpos = vd->buff + AP_IOBUFSIZE;
+
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(int) ap_vrprintf(request_rec *r, const char *fmt, va_list va)
+{
+ apr_size_t written;
+ struct ap_vrprintf_data vd;
+ char vrprintf_buf[AP_IOBUFSIZE];
+
+ vd.vbuff.curpos = vrprintf_buf;
+ vd.vbuff.endpos = vrprintf_buf + AP_IOBUFSIZE;
+ vd.r = r;
+ vd.buff = vrprintf_buf;
+
+ if (r->connection->aborted)
+ return -1;
+
+ written = apr_vformatter(r_flush, &vd.vbuff, fmt, va);
+
+ /* tack on null terminator on remaining string */
+ *(vd.vbuff.curpos) = '\0';
+
+ if (written != -1) {
+ int n = vd.vbuff.curpos - vrprintf_buf;
+
+ /* last call to buffer_output, to finish clearing the buffer */
+ if (buffer_output(r, vrprintf_buf,n) != APR_SUCCESS)
+ return -1;
+
+ written += n;
+ }
+
+ return written;
+}
+
+AP_DECLARE_NONSTD(int) ap_rprintf(request_rec *r, const char *fmt, ...)
+{
+ va_list va;
+ int n;
+
+ if (r->connection->aborted)
+ return -1;
+
+ va_start(va, fmt);
+ n = ap_vrprintf(r, fmt, va);
+ va_end(va);
+
+ return n;
+}
+
+AP_DECLARE_NONSTD(int) ap_rvputs(request_rec *r, ...)
+{
+ va_list va;
+ const char *s;
+ apr_size_t len;
+ apr_size_t written = 0;
+
+ if (r->connection->aborted)
+ return -1;
+
+ /* ### TODO: if the total output is large, put all the strings
+ * ### into a single brigade, rather than flushing each time we
+ * ### fill the buffer
+ */
+ va_start(va, r);
+ while (1) {
+ s = va_arg(va, const char *);
+ if (s == NULL)
+ break;
+
+ len = strlen(s);
+ if (buffer_output(r, s, len) != APR_SUCCESS) {
+ return -1;
+ }
+
+ written += len;
+ }
+ va_end(va);
+
+ return written;
+}
+
+AP_DECLARE(int) ap_rflush(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * This function sets the Last-Modified output header field to the value
+ * of the mtime field in the request structure - rationalized to keep it from
+ * being in the future.
+ */
+AP_DECLARE(void) ap_set_last_modified(request_rec *r)
+{
+ if (!r->assbackwards) {
+ apr_time_t mod_time = ap_rationalize_mtime(r, r->mtime);
+ char *datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+
+ apr_rfc822_date(datestr, mod_time);
+ apr_table_setn(r->headers_out, "Last-Modified", datestr);
+ }
+}
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int,post_read_request,
+ (request_rec *r), (r), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,log_transaction,
+ (request_rec *r), (r), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,http_scheme,
+ (const request_rec *r), (r), NULL)
+AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port,
+ (const request_rec *r), (r), 0)
diff --git a/server/provider.c b/server/provider.c
new file mode 100644
index 00000000..a5406ab1
--- /dev/null
+++ b/server/provider.c
@@ -0,0 +1,165 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_pools.h"
+#include "apr_hash.h"
+#include "apr_tables.h"
+#include "apr_strings.h"
+
+#include "ap_provider.h"
+
+static apr_hash_t *global_providers = NULL;
+static apr_hash_t *global_providers_names = NULL;
+
+
+static apr_status_t cleanup_global_providers(void *ctx)
+{
+ global_providers = NULL;
+ global_providers_names = NULL;
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool,
+ const char *provider_group,
+ const char *provider_name,
+ const char *provider_version,
+ const void *provider)
+{
+ apr_hash_t *provider_group_hash, *provider_version_hash;
+
+ if (global_providers == NULL) {
+ global_providers = apr_hash_make(pool);
+ global_providers_names = apr_hash_make(pool);;
+ apr_pool_cleanup_register(pool, NULL, cleanup_global_providers,
+ apr_pool_cleanup_null);
+ }
+
+ /* First, deal with storing the provider away */
+ provider_group_hash = apr_hash_get(global_providers, provider_group,
+ APR_HASH_KEY_STRING);
+
+ if (!provider_group_hash) {
+ provider_group_hash = apr_hash_make(pool);
+ apr_hash_set(global_providers, provider_group, APR_HASH_KEY_STRING,
+ provider_group_hash);
+
+ }
+
+ provider_version_hash = apr_hash_get(provider_group_hash, provider_name,
+ APR_HASH_KEY_STRING);
+
+ if (!provider_version_hash) {
+ provider_version_hash = apr_hash_make(pool);
+ apr_hash_set(provider_group_hash, provider_name, APR_HASH_KEY_STRING,
+ provider_version_hash);
+
+ }
+
+ /* just set it. no biggy if it was there before. */
+ apr_hash_set(provider_version_hash, provider_version, APR_HASH_KEY_STRING,
+ provider);
+
+ /* Now, tuck away the provider names in an easy-to-get format */
+ provider_group_hash = apr_hash_get(global_providers_names, provider_group,
+ APR_HASH_KEY_STRING);
+
+ if (!provider_group_hash) {
+ provider_group_hash = apr_hash_make(pool);
+ apr_hash_set(global_providers_names, provider_group, APR_HASH_KEY_STRING,
+ provider_group_hash);
+
+ }
+
+ provider_version_hash = apr_hash_get(provider_group_hash, provider_version,
+ APR_HASH_KEY_STRING);
+
+ if (!provider_version_hash) {
+ provider_version_hash = apr_hash_make(pool);
+ apr_hash_set(provider_group_hash, provider_version, APR_HASH_KEY_STRING,
+ provider_version_hash);
+
+ }
+
+ /* just set it. no biggy if it was there before. */
+ apr_hash_set(provider_version_hash, provider_name, APR_HASH_KEY_STRING,
+ provider_name);
+
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(void *) ap_lookup_provider(const char *provider_group,
+ const char *provider_name,
+ const char *provider_version)
+{
+ apr_hash_t *provider_group_hash, *provider_name_hash;
+
+ if (global_providers == NULL) {
+ return NULL;
+ }
+
+ provider_group_hash = apr_hash_get(global_providers, provider_group,
+ APR_HASH_KEY_STRING);
+
+ if (provider_group_hash == NULL) {
+ return NULL;
+ }
+
+ provider_name_hash = apr_hash_get(provider_group_hash, provider_name,
+ APR_HASH_KEY_STRING);
+
+ if (provider_name_hash == NULL) {
+ return NULL;
+ }
+
+ return apr_hash_get(provider_name_hash, provider_version,
+ APR_HASH_KEY_STRING);
+}
+
+AP_DECLARE(apr_array_header_t *) ap_list_provider_names(apr_pool_t *pool,
+ const char *provider_group,
+ const char *provider_version)
+{
+ apr_array_header_t *ret = apr_array_make(pool, 10, sizeof(ap_list_provider_names_t));
+ ap_list_provider_names_t *entry;
+ apr_hash_t *provider_group_hash, *h;
+ apr_hash_index_t *hi;
+ char *val, *key;
+
+ if (global_providers_names == NULL) {
+ return ret;
+ }
+
+ provider_group_hash = apr_hash_get(global_providers_names, provider_group,
+ APR_HASH_KEY_STRING);
+
+ if (provider_group_hash == NULL) {
+ return ret;
+ }
+
+ h = apr_hash_get(provider_group_hash, provider_version,
+ APR_HASH_KEY_STRING);
+
+ if (h == NULL) {
+ return ret;
+ }
+
+ for (hi = apr_hash_first(pool, h); hi; hi = apr_hash_next(hi)) {
+ apr_hash_this(hi, (void *)&key, NULL, (void *)&val);
+ entry = apr_array_push(ret);
+ entry->provider_name = apr_pstrdup(pool, val);
+ }
+ return ret;
+}
diff --git a/server/request.c b/server/request.c
new file mode 100644
index 00000000..1aff7304
--- /dev/null
+++ b/server/request.c
@@ -0,0 +1,1912 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * @file request.c
+ * @brief functions to get and process requests
+ *
+ * @author Rob McCool 3/21/93
+ *
+ * Thoroughly revamped by rst for Apache. NB this file reads
+ * best from the bottom up.
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_fnmatch.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "util_filter.h"
+#include "util_charset.h"
+#include "util_script.h"
+
+#include "mod_core.h"
+
+#if APR_HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(translate_name)
+ APR_HOOK_LINK(map_to_storage)
+ APR_HOOK_LINK(check_user_id)
+ APR_HOOK_LINK(fixups)
+ APR_HOOK_LINK(type_checker)
+ APR_HOOK_LINK(access_checker)
+ APR_HOOK_LINK(auth_checker)
+ APR_HOOK_LINK(insert_filter)
+ APR_HOOK_LINK(create_request)
+)
+
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name,
+ (request_rec *r), (r), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,map_to_storage,
+ (request_rec *r), (r), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,check_user_id,
+ (request_rec *r), (r), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,fixups,
+ (request_rec *r), (r), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,type_checker,
+ (request_rec *r), (r), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,access_checker,
+ (request_rec *r), (r), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,auth_checker,
+ (request_rec *r), (r), DECLINED)
+AP_IMPLEMENT_HOOK_VOID(insert_filter, (request_rec *r), (r))
+AP_IMPLEMENT_HOOK_RUN_ALL(int, create_request,
+ (request_rec *r), (r), OK, DECLINED)
+
+
+static int decl_die(int status, char *phase, request_rec *r)
+{
+ if (status == DECLINED) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ "configuration error: couldn't %s: %s", phase, r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else {
+ return status;
+ }
+}
+
+/* This is the master logic for processing requests. Do NOT duplicate
+ * this logic elsewhere, or the security model will be broken by future
+ * API changes. Each phase must be individually optimized to pick up
+ * redundant/duplicate calls by subrequests, and redirects.
+ */
+AP_DECLARE(int) ap_process_request_internal(request_rec *r)
+{
+ int file_req = (r->main && r->filename);
+ int access_status;
+
+ /* Ignore embedded %2F's in path for proxy requests */
+ if (!r->proxyreq && r->parsed_uri.path) {
+ core_dir_config *d;
+ d = ap_get_module_config(r->per_dir_config, &core_module);
+ if (d->allow_encoded_slashes) {
+ access_status = ap_unescape_url_keep2f(r->parsed_uri.path);
+ }
+ else {
+ access_status = ap_unescape_url(r->parsed_uri.path);
+ }
+ if (access_status) {
+ if (access_status == HTTP_NOT_FOUND) {
+ if (! d->allow_encoded_slashes) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "found %%2f (encoded '/') in URI "
+ "(decoded='%s'), returning 404",
+ r->parsed_uri.path);
+ }
+ }
+ return access_status;
+ }
+ }
+
+ ap_getparents(r->uri); /* OK --- shrinking transformations... */
+
+ /* All file subrequests are a huge pain... they cannot bubble through the
+ * next several steps. Only file subrequests are allowed an empty uri,
+ * otherwise let translate_name kill the request.
+ */
+ if (!file_req) {
+ if ((access_status = ap_location_walk(r))) {
+ return access_status;
+ }
+
+ if ((access_status = ap_run_translate_name(r))) {
+ return decl_die(access_status, "translate", r);
+ }
+ }
+
+ /* Reset to the server default config prior to running map_to_storage
+ */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ if ((access_status = ap_run_map_to_storage(r))) {
+ /* This request wasn't in storage (e.g. TRACE) */
+ return access_status;
+ }
+
+ /* Excluding file-specific requests with no 'true' URI...
+ */
+ if (!file_req) {
+ /* Rerun the location walk, which overrides any map_to_storage config.
+ */
+ if ((access_status = ap_location_walk(r))) {
+ return access_status;
+ }
+ }
+
+ /* Only on the main request! */
+ if (r->main == NULL) {
+ if ((access_status = ap_run_header_parser(r))) {
+ return access_status;
+ }
+ }
+
+ /* Skip authn/authz if the parent or prior request passed the authn/authz,
+ * and that configuration didn't change (this requires optimized _walk()
+ * functions in map_to_storage that use the same merge results given
+ * identical input.) If the config changes, we must re-auth.
+ */
+ if (r->main && (r->main->per_dir_config == r->per_dir_config)) {
+ r->user = r->main->user;
+ r->ap_auth_type = r->main->ap_auth_type;
+ }
+ else if (r->prev && (r->prev->per_dir_config == r->per_dir_config)) {
+ r->user = r->prev->user;
+ r->ap_auth_type = r->prev->ap_auth_type;
+ }
+ else {
+ switch (ap_satisfies(r)) {
+ case SATISFY_ALL:
+ case SATISFY_NOSPEC:
+ if ((access_status = ap_run_access_checker(r)) != 0) {
+ return decl_die(access_status, "check access", r);
+ }
+
+ if (ap_some_auth_required(r)) {
+ if (((access_status = ap_run_check_user_id(r)) != 0)
+ || !ap_auth_type(r)) {
+ return decl_die(access_status, ap_auth_type(r)
+ ? "check user. No user file?"
+ : "perform authentication. AuthType not set!",
+ r);
+ }
+
+ if (((access_status = ap_run_auth_checker(r)) != 0)
+ || !ap_auth_type(r)) {
+ return decl_die(access_status, ap_auth_type(r)
+ ? "check access. No groups file?"
+ : "perform authentication. AuthType not set!",
+ r);
+ }
+ }
+ break;
+
+ case SATISFY_ANY:
+ if (((access_status = ap_run_access_checker(r)) != 0)) {
+ if (!ap_some_auth_required(r)) {
+ return decl_die(access_status, "check access", r);
+ }
+
+ if (((access_status = ap_run_check_user_id(r)) != 0)
+ || !ap_auth_type(r)) {
+ return decl_die(access_status, ap_auth_type(r)
+ ? "check user. No user file?"
+ : "perform authentication. AuthType not set!",
+ r);
+ }
+
+ if (((access_status = ap_run_auth_checker(r)) != 0)
+ || !ap_auth_type(r)) {
+ return decl_die(access_status, ap_auth_type(r)
+ ? "check access. No groups file?"
+ : "perform authentication. AuthType not set!",
+ r);
+ }
+ }
+ break;
+ }
+ }
+ /* XXX Must make certain the ap_run_type_checker short circuits mime
+ * in mod-proxy for r->proxyreq && r->parsed_uri.scheme
+ * && !strcmp(r->parsed_uri.scheme, "http")
+ */
+ if ((access_status = ap_run_type_checker(r)) != 0) {
+ return decl_die(access_status, "find types", r);
+ }
+
+ if ((access_status = ap_run_fixups(r)) != 0) {
+ return access_status;
+ }
+
+ return OK;
+}
+
+
+/* Useful caching structures to repeat _walk/merge sequences as required
+ * when a subrequest or redirect reuses substantially the same config.
+ *
+ * Directive order in the httpd.conf file and its Includes significantly
+ * impact this optimization. Grouping common blocks at the front of the
+ * config that are less likely to change between a request and
+ * its subrequests, or between a request and its redirects reduced
+ * the work of these functions significantly.
+ */
+
+typedef struct walk_walked_t {
+ ap_conf_vector_t *matched; /* A dir_conf sections we matched */
+ ap_conf_vector_t *merged; /* The dir_conf merged result */
+} walk_walked_t;
+
+typedef struct walk_cache_t {
+ const char *cached; /* The identifier we matched */
+ ap_conf_vector_t **dir_conf_tested; /* The sections we matched against */
+ ap_conf_vector_t *dir_conf_merged; /* Base per_dir_config */
+ ap_conf_vector_t *per_dir_result; /* per_dir_config += walked result */
+ apr_array_header_t *walked; /* The list of walk_walked_t results */
+} walk_cache_t;
+
+static walk_cache_t *prep_walk_cache(apr_size_t t, request_rec *r)
+{
+ walk_cache_t *cache;
+ void **note;
+
+ /* Find the most relevant, recent entry to work from. That would be
+ * this request (on the second call), or the parent request of a
+ * subrequest, or the prior request of an internal redirect. Provide
+ * this _walk()er with a copy it is allowed to munge. If there is no
+ * parent or prior cached request, then create a new walk cache.
+ */
+ note = ap_get_request_note(r, t);
+ if (!note) {
+ return NULL;
+ }
+
+ if (!(cache = *note)) {
+ void **inherit_note;
+
+ if ((r->main
+ && ((inherit_note = ap_get_request_note(r->main, t)))
+ && *inherit_note)
+ || (r->prev
+ && ((inherit_note = ap_get_request_note(r->prev, t)))
+ && *inherit_note)) {
+ cache = apr_pmemdup(r->pool, *inherit_note,
+ sizeof(*cache));
+ cache->walked = apr_array_copy(r->pool, cache->walked);
+ }
+ else {
+ cache = apr_pcalloc(r->pool, sizeof(*cache));
+ cache->walked = apr_array_make(r->pool, 4, sizeof(walk_walked_t));
+ }
+
+ *note = cache;
+ }
+ return cache;
+}
+
+/*****************************************************************
+ *
+ * Getting and checking directory configuration. Also checks the
+ * FollowSymlinks and FollowSymOwner stuff, since this is really the
+ * only place that can happen (barring a new mid_dir_walk callout).
+ *
+ * We can't do it as an access_checker module function which gets
+ * called with the final per_dir_config, since we could have a directory
+ * with FollowSymLinks disabled, which contains a symlink to another
+ * with a .htaccess file which turns FollowSymLinks back on --- and
+ * access in such a case must be denied. So, whatever it is that
+ * checks FollowSymLinks needs to know the state of the options as
+ * they change, all the way down.
+ */
+
+
+/*
+ * resolve_symlink must _always_ be called on an APR_LNK file type!
+ * It will resolve the actual target file type, modification date, etc,
+ * and provide any processing required for symlink evaluation.
+ * Path must already be cleaned, no trailing slash, no multi-slashes,
+ * and don't call this on the root!
+ *
+ * Simply, the number of times we deref a symlink are minimal compared
+ * to the number of times we had an extra lstat() since we 'weren't sure'.
+ *
+ * To optimize, we stat() anything when given (opts & OPT_SYM_LINKS), otherwise
+ * we start off with an lstat(). Every lstat() must be dereferenced in case
+ * it points at a 'nasty' - we must always rerun check_safe_file (or similar.)
+ */
+static int resolve_symlink(char *d, apr_finfo_t *lfi, int opts, apr_pool_t *p)
+{
+ apr_finfo_t fi;
+ int res;
+ const char *savename;
+
+ if (!(opts & (OPT_SYM_OWNER | OPT_SYM_LINKS))) {
+ return HTTP_FORBIDDEN;
+ }
+
+ /* Save the name from the valid bits. */
+ savename = (lfi->valid & APR_FINFO_NAME) ? lfi->name : NULL;
+
+ if (opts & OPT_SYM_LINKS) {
+ if ((res = apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME
+ | APR_FINFO_LINK), p))
+ != APR_SUCCESS) {
+ return HTTP_FORBIDDEN;
+ }
+
+ /* Give back the target */
+ memcpy(lfi, &fi, sizeof(fi));
+ if (savename) {
+ lfi->name = savename;
+ lfi->valid |= APR_FINFO_NAME;
+ }
+
+ return OK;
+ }
+
+ /* OPT_SYM_OWNER only works if we can get the owner of
+ * both the file and symlink. First fill in a missing
+ * owner of the symlink, then get the info of the target.
+ */
+ if (!(lfi->valid & APR_FINFO_OWNER)) {
+ if ((res = apr_stat(&fi, d,
+ lfi->valid | APR_FINFO_LINK | APR_FINFO_OWNER, p))
+ != APR_SUCCESS) {
+ return HTTP_FORBIDDEN;
+ }
+ }
+
+ if ((res = apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME), p))
+ != APR_SUCCESS) {
+ return HTTP_FORBIDDEN;
+ }
+
+ if (apr_uid_compare(fi.user, lfi->user) != APR_SUCCESS) {
+ return HTTP_FORBIDDEN;
+ }
+
+ /* Give back the target */
+ memcpy(lfi, &fi, sizeof(fi));
+ if (savename) {
+ lfi->name = savename;
+ lfi->valid |= APR_FINFO_NAME;
+ }
+
+ return OK;
+}
+
+
+/*
+ * As we walk the directory configuration, the merged config won't
+ * be 'rooted' to a specific vhost until the very end of the merge.
+ *
+ * We need a very fast mini-merge to a real, vhost-rooted merge
+ * of core.opts and core.override, the only options tested within
+ * directory_walk itself.
+ *
+ * See core.c::merge_core_dir_configs() for explanation.
+ */
+
+typedef struct core_opts_t {
+ allow_options_t opts;
+ allow_options_t add;
+ allow_options_t remove;
+ overrides_t override;
+ overrides_t override_opts;
+} core_opts_t;
+
+static void core_opts_merge(const ap_conf_vector_t *sec, core_opts_t *opts)
+{
+ core_dir_config *this_dir = ap_get_module_config(sec, &core_module);
+
+ if (!this_dir) {
+ return;
+ }
+
+ if (this_dir->opts & OPT_UNSET) {
+ opts->add = (opts->add & ~this_dir->opts_remove)
+ | this_dir->opts_add;
+ opts->remove = (opts->remove & ~this_dir->opts_add)
+ | this_dir->opts_remove;
+ opts->opts = (opts->opts & ~opts->remove) | opts->add;
+ }
+ else {
+ opts->opts = this_dir->opts;
+ opts->add = this_dir->opts_add;
+ opts->remove = this_dir->opts_remove;
+ }
+
+ if (!(this_dir->override & OR_UNSET)) {
+ opts->override = this_dir->override;
+ opts->override_opts = this_dir->override_opts;
+ }
+}
+
+
+/*****************************************************************
+ *
+ * Getting and checking directory configuration. Also checks the
+ * FollowSymlinks and FollowSymOwner stuff, since this is really the
+ * only place that can happen (barring a new mid_dir_walk callout).
+ *
+ * We can't do it as an access_checker module function which gets
+ * called with the final per_dir_config, since we could have a directory
+ * with FollowSymLinks disabled, which contains a symlink to another
+ * with a .htaccess file which turns FollowSymLinks back on --- and
+ * access in such a case must be denied. So, whatever it is that
+ * checks FollowSymLinks needs to know the state of the options as
+ * they change, all the way down.
+ */
+
+AP_DECLARE(int) ap_directory_walk(request_rec *r)
+{
+ ap_conf_vector_t *now_merged = NULL;
+ core_server_config *sconf = ap_get_module_config(r->server->module_config,
+ &core_module);
+ ap_conf_vector_t **sec_ent = (ap_conf_vector_t **) sconf->sec_dir->elts;
+ int num_sec = sconf->sec_dir->nelts;
+ walk_cache_t *cache;
+ char *entry_dir;
+ apr_status_t rv;
+
+ /* XXX: Better (faster) tests needed!!!
+ *
+ * "OK" as a response to a real problem is not _OK_, but to allow broken
+ * modules to proceed, we will permit the not-a-path filename to pass the
+ * following two tests. This behavior may be revoked in future versions
+ * of Apache. We still must catch it later if it's heading for the core
+ * handler. Leave INFO notes here for module debugging.
+ */
+ if (r->filename == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "Module bug? Request filename is missing for URI %s",
+ r->uri);
+ return OK;
+ }
+
+ /* Canonicalize the file path without resolving filename case or aliases
+ * so we can begin by checking the cache for a recent directory walk.
+ * This call will ensure we have an absolute path in the same pass.
+ */
+ if ((rv = apr_filepath_merge(&entry_dir, NULL, r->filename,
+ APR_FILEPATH_NOTRELATIVE, r->pool))
+ != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "Module bug? Request filename path %s is invalid or "
+ "or not absolute for uri %s",
+ r->filename, r->uri);
+ return OK;
+ }
+
+ /* XXX Notice that this forces path_info to be canonical. That might
+ * not be desired by all apps. However, some of those same apps likely
+ * have significant security holes.
+ */
+ r->filename = entry_dir;
+
+ cache = prep_walk_cache(AP_NOTE_DIRECTORY_WALK, r);
+
+ /* If this is not a dirent subrequest with a preconstructed
+ * r->finfo value, then we can simply stat the filename to
+ * save burning mega-cycles with unneeded stats - if this is
+ * an exact file match. We don't care about failure... we
+ * will stat by component failing this meager attempt.
+ *
+ * It would be nice to distinguish APR_ENOENT from other
+ * types of failure, such as APR_ENOTDIR. We can do something
+ * with APR_ENOENT, knowing that the path is good.
+ */
+ if (!r->finfo.filetype || r->finfo.filetype == APR_LNK) {
+ rv = apr_stat(&r->finfo, r->filename, APR_FINFO_MIN, r->pool);
+
+ /* some OSs will return APR_SUCCESS/APR_REG if we stat
+ * a regular file but we have '/' at the end of the name;
+ *
+ * other OSs will return APR_ENOTDIR for that situation;
+ *
+ * handle it the same everywhere by simulating a failure
+ * if it looks like a directory but really isn't
+ *
+ * Also reset if the stat failed, just for safety.
+ */
+ if ((rv != APR_SUCCESS) ||
+ (r->finfo.filetype &&
+ (r->finfo.filetype != APR_DIR) &&
+ (r->filename[strlen(r->filename) - 1] == '/'))) {
+ r->finfo.filetype = 0; /* forget what we learned */
+ }
+ }
+
+ if (r->finfo.filetype == APR_REG) {
+ entry_dir = ap_make_dirstr_parent(r->pool, entry_dir);
+ }
+ else if (r->filename[strlen(r->filename) - 1] != '/') {
+ entry_dir = apr_pstrcat(r->pool, r->filename, "/", NULL);
+ }
+
+ /* If we have a file already matches the path of r->filename,
+ * and the vhost's list of directory sections hasn't changed,
+ * we can skip rewalking the directory_walk entries.
+ */
+ if (cache->cached
+ && ((r->finfo.filetype == APR_REG)
+ || ((r->finfo.filetype == APR_DIR)
+ && (!r->path_info || !*r->path_info)))
+ && (cache->dir_conf_tested == sec_ent)
+ && (strcmp(entry_dir, cache->cached) == 0)) {
+ /* Well this looks really familiar! If our end-result (per_dir_result)
+ * didn't change, we have absolutely nothing to do :)
+ * Otherwise (as is the case with most dir_merged/file_merged requests)
+ * we must merge our dir_conf_merged onto this new r->per_dir_config.
+ */
+ if (r->per_dir_config == cache->per_dir_result) {
+ return OK;
+ }
+
+ if (r->per_dir_config == cache->dir_conf_merged) {
+ r->per_dir_config = cache->per_dir_result;
+ return OK;
+ }
+
+ if (cache->walked->nelts) {
+ now_merged = ((walk_walked_t*)cache->walked->elts)
+ [cache->walked->nelts - 1].merged;
+ }
+ }
+ else {
+ /* We start now_merged from NULL since we want to build
+ * a locations list that can be merged to any vhost.
+ */
+ int sec_idx;
+ int matches = cache->walked->nelts;
+ walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts;
+ core_dir_config *this_dir;
+ core_opts_t opts;
+ apr_finfo_t thisinfo;
+ char *save_path_info;
+ apr_size_t buflen;
+ char *buf;
+ unsigned int seg, startseg;
+
+ /* Invariant: from the first time filename_len is set until
+ * it goes out of scope, filename_len==strlen(r->filename)
+ */
+ apr_size_t filename_len;
+#ifdef CASE_BLIND_FILESYSTEM
+ apr_size_t canonical_len;
+#endif
+
+ /*
+ * We must play our own mini-merge game here, for the few
+ * running dir_config values we care about within dir_walk.
+ * We didn't start the merge from r->per_dir_config, so we
+ * accumulate opts and override as we merge, from the globals.
+ */
+ this_dir = ap_get_module_config(r->per_dir_config, &core_module);
+ opts.opts = this_dir->opts;
+ opts.add = this_dir->opts_add;
+ opts.remove = this_dir->opts_remove;
+ opts.override = this_dir->override;
+
+ /* Set aside path_info to merge back onto path_info later.
+ * If r->filename is a directory, we must remerge the path_info,
+ * before we continue! [Directories cannot, by defintion, have
+ * path info. Either the next segment is not-found, or a file.]
+ *
+ * r->path_info tracks the unconsumed source path.
+ * r->filename tracks the path as we process it
+ */
+ if ((r->finfo.filetype == APR_DIR) && r->path_info && *r->path_info)
+ {
+ if ((rv = apr_filepath_merge(&r->path_info, r->filename,
+ r->path_info,
+ APR_FILEPATH_NOTABOVEROOT, r->pool))
+ != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "dir_walk error, path_info %s is not relative "
+ "to the filename path %s for uri %s",
+ r->path_info, r->filename, r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ save_path_info = NULL;
+ }
+ else {
+ save_path_info = r->path_info;
+ r->path_info = r->filename;
+ }
+
+#ifdef CASE_BLIND_FILESYSTEM
+
+ canonical_len = 0;
+ while (r->canonical_filename && r->canonical_filename[canonical_len]
+ && (r->canonical_filename[canonical_len]
+ == r->path_info[canonical_len])) {
+ ++canonical_len;
+ }
+
+ while (canonical_len
+ && ((r->canonical_filename[canonical_len - 1] != '/'
+ && r->canonical_filename[canonical_len - 1])
+ || (r->path_info[canonical_len - 1] != '/'
+ && r->path_info[canonical_len - 1]))) {
+ --canonical_len;
+ }
+
+ /*
+ * Now build r->filename component by component, starting
+ * with the root (on Unix, simply "/"). We will make a huge
+ * assumption here for efficiency, that any canonical path
+ * already given included a canonical root.
+ */
+ rv = apr_filepath_root((const char **)&r->filename,
+ (const char **)&r->path_info,
+ canonical_len ? 0 : APR_FILEPATH_TRUENAME,
+ r->pool);
+ filename_len = strlen(r->filename);
+
+ /*
+ * Bad assumption above? If the root's length is longer
+ * than the canonical length, then it cannot be trusted as
+ * a truename. So try again, this time more seriously.
+ */
+ if ((rv == APR_SUCCESS) && canonical_len
+ && (filename_len > canonical_len)) {
+ rv = apr_filepath_root((const char **)&r->filename,
+ (const char **)&r->path_info,
+ APR_FILEPATH_TRUENAME, r->pool);
+ filename_len = strlen(r->filename);
+ canonical_len = 0;
+ }
+
+#else /* ndef CASE_BLIND_FILESYSTEM, really this simple for Unix today; */
+
+ rv = apr_filepath_root((const char **)&r->filename,
+ (const char **)&r->path_info,
+ 0, r->pool);
+ filename_len = strlen(r->filename);
+
+#endif
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "dir_walk error, could not determine the root "
+ "path of filename %s%s for uri %s",
+ r->filename, r->path_info, r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Working space for terminating null and an extra / is required.
+ */
+ buflen = filename_len + strlen(r->path_info) + 2;
+ buf = apr_palloc(r->pool, buflen);
+ memcpy(buf, r->filename, filename_len + 1);
+ r->filename = buf;
+ thisinfo.valid = APR_FINFO_TYPE;
+ thisinfo.filetype = APR_DIR; /* It's the root, of course it's a dir */
+
+ /*
+ * seg keeps track of which segment we've copied.
+ * sec_idx keeps track of which section we're on, since sections are
+ * ordered by number of segments. See core_reorder_directories
+ * startseg tells us how many segments describe the root path
+ * e.g. the complete path "//host/foo/" to a UNC share (4)
+ */
+ startseg = seg = ap_count_dirs(r->filename);
+ sec_idx = 0;
+
+ /*
+ * Go down the directory hierarchy. Where we have to check for
+ * symlinks, do so. Where a .htaccess file has permission to
+ * override anything, try to find one.
+ */
+ do {
+ int res;
+ char *seg_name;
+ char *delim;
+ int temp_slash=0;
+
+ /* We have no trailing slash, but we sure would appreciate one.
+ * However, we don't want to append a / our first time through.
+ */
+ if ((seg > startseg) && r->filename[filename_len-1] != '/') {
+ r->filename[filename_len++] = '/';
+ r->filename[filename_len] = 0;
+ temp_slash=1;
+ }
+
+ /* Begin *this* level by looking for matching <Directory> sections
+ * from the server config.
+ */
+ for (; sec_idx < num_sec; ++sec_idx) {
+
+ ap_conf_vector_t *entry_config = sec_ent[sec_idx];
+ core_dir_config *entry_core;
+ entry_core = ap_get_module_config(entry_config, &core_module);
+
+ /* No more possible matches for this many segments?
+ * We are done when we find relative/regex/longer components.
+ */
+ if (entry_core->r || entry_core->d_components > seg) {
+ break;
+ }
+
+ /* We will never skip '0' element components, e.g. plain old
+ * <Directory >, and <Directory "/"> are classified as zero
+ * so that Win32/Netware/OS2 etc all pick them up.
+ * Otherwise, skip over the mismatches.
+ */
+ if (entry_core->d_components
+ && ((entry_core->d_components < seg)
+ || (entry_core->d_is_fnmatch
+ ? (apr_fnmatch(entry_core->d, r->filename,
+ APR_FNM_PATHNAME) != APR_SUCCESS)
+ : (strcmp(r->filename, entry_core->d) != 0)))) {
+ continue;
+ }
+
+ /* If we haven't continue'd above, we have a match.
+ *
+ * Calculate our full-context core opts & override.
+ */
+ core_opts_merge(sec_ent[sec_idx], &opts);
+
+ /* If we merged this same section last time, reuse it
+ */
+ if (matches) {
+ if (last_walk->matched == sec_ent[sec_idx]) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ continue;
+ }
+
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged) {
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ sec_ent[sec_idx]);
+ }
+ else {
+ now_merged = sec_ent[sec_idx];
+ }
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = sec_ent[sec_idx];
+ last_walk->merged = now_merged;
+ }
+
+ /* If .htaccess files are enabled, check for one, provided we
+ * have reached a real path.
+ */
+ do { /* Not really a loop, just a break'able code block */
+
+ ap_conf_vector_t *htaccess_conf = NULL;
+
+ /* No htaccess in an incomplete root path,
+ * nor if it's disabled
+ */
+ if (seg < startseg || !opts.override) {
+ break;
+ }
+
+ res = ap_parse_htaccess(&htaccess_conf, r, opts.override,
+ opts.override_opts,
+ apr_pstrdup(r->pool, r->filename),
+ sconf->access_name);
+ if (res) {
+ return res;
+ }
+
+ if (!htaccess_conf) {
+ break;
+ }
+
+ /* If we are still here, we found our htaccess.
+ *
+ * Calculate our full-context core opts & override.
+ */
+ core_opts_merge(htaccess_conf, &opts);
+
+ /* If we merged this same htaccess last time, reuse it...
+ * this wouldn't work except that we cache the htaccess
+ * sections for the lifetime of the request, so we match
+ * the same conf. Good planning (no, pure luck ;)
+ */
+ if (matches) {
+ if (last_walk->matched == htaccess_conf) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ break;
+ }
+
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset
+ * remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged) {
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ htaccess_conf);
+ }
+ else {
+ now_merged = htaccess_conf;
+ }
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = htaccess_conf;
+ last_walk->merged = now_merged;
+
+ } while (0); /* Only one htaccess, not a real loop */
+
+ /* That temporary trailing slash was useful, now drop it.
+ */
+ if (temp_slash) {
+ r->filename[--filename_len] = '\0';
+ }
+
+ /* Time for all good things to come to an end?
+ */
+ if (!r->path_info || !*r->path_info) {
+ break;
+ }
+
+ /* Now it's time for the next segment...
+ * We will assume the next element is an end node, and fix it up
+ * below as necessary...
+ */
+
+ seg_name = r->filename + filename_len;
+ delim = strchr(r->path_info + (*r->path_info == '/' ? 1 : 0), '/');
+ if (delim) {
+ size_t path_info_len = delim - r->path_info;
+ *delim = '\0';
+ memcpy(seg_name, r->path_info, path_info_len + 1);
+ filename_len += path_info_len;
+ r->path_info = delim;
+ *delim = '/';
+ }
+ else {
+ size_t path_info_len = strlen(r->path_info);
+ memcpy(seg_name, r->path_info, path_info_len + 1);
+ filename_len += path_info_len;
+ r->path_info += path_info_len;
+ }
+ if (*seg_name == '/')
+ ++seg_name;
+
+ /* If nothing remained but a '/' string, we are finished
+ * XXX: NO WE ARE NOT!!! Now process this puppy!!! */
+ if (!*seg_name) {
+ break;
+ }
+
+ /* First optimization;
+ * If...we knew r->filename was a file, and
+ * if...we have strict (case-sensitive) filenames, or
+ * we know the canonical_filename matches to _this_ name, and
+ * if...we have allowed symlinks
+ * skip the lstat and dummy up an APR_DIR value for thisinfo.
+ */
+ if (r->finfo.filetype
+#ifdef CASE_BLIND_FILESYSTEM
+ && (filename_len <= canonical_len)
+#endif
+ && ((opts.opts & (OPT_SYM_OWNER | OPT_SYM_LINKS)) == OPT_SYM_LINKS))
+ {
+
+ thisinfo.filetype = APR_DIR;
+ ++seg;
+ continue;
+ }
+
+ /* We choose apr_stat with flag APR_FINFO_LINK here, rather that
+ * plain apr_stat, so that we capture this path object rather than
+ * its target. We will replace the info with our target's info
+ * below. We especially want the name of this 'link' object, not
+ * the name of its target, if we are fixing the filename
+ * case/resolving aliases.
+ */
+ rv = apr_stat(&thisinfo, r->filename,
+ APR_FINFO_MIN | APR_FINFO_NAME | APR_FINFO_LINK,
+ r->pool);
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* Nothing? That could be nice. But our directory
+ * walk is done.
+ */
+ thisinfo.filetype = APR_NOFILE;
+ break;
+ }
+ else if (APR_STATUS_IS_EACCES(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "access to %s denied", r->uri);
+ return r->status = HTTP_FORBIDDEN;
+ }
+ else if ((rv != APR_SUCCESS && rv != APR_INCOMPLETE)
+ || !(thisinfo.valid & APR_FINFO_TYPE)) {
+ /* If we hit ENOTDIR, we must have over-optimized, deny
+ * rather than assume not found.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "access to %s failed", r->uri);
+ return r->status = HTTP_FORBIDDEN;
+ }
+
+ /* Fix up the path now if we have a name, and they don't agree
+ */
+ if ((thisinfo.valid & APR_FINFO_NAME)
+ && strcmp(seg_name, thisinfo.name)) {
+ /* TODO: provide users an option that an internal/external
+ * redirect is required here? We need to walk the URI and
+ * filename in tandem to properly correlate these.
+ */
+ strcpy(seg_name, thisinfo.name);
+ filename_len = strlen(r->filename);
+ }
+
+ if (thisinfo.filetype == APR_LNK) {
+ /* Is this a possibly acceptable symlink?
+ */
+ if ((res = resolve_symlink(r->filename, &thisinfo,
+ opts.opts, r->pool)) != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Symbolic link not allowed "
+ "or link target not accessible: %s",
+ r->filename);
+ return r->status = res;
+ }
+ }
+
+ /* Ok, we are done with the link's info, test the real target
+ */
+ if (thisinfo.filetype == APR_REG ||
+ thisinfo.filetype == APR_NOFILE) {
+ /* That was fun, nothing left for us here
+ */
+ break;
+ }
+ else if (thisinfo.filetype != APR_DIR) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Forbidden: %s doesn't point to "
+ "a file or directory",
+ r->filename);
+ return r->status = HTTP_FORBIDDEN;
+ }
+
+ ++seg;
+ } while (thisinfo.filetype == APR_DIR);
+
+ /* If we have _not_ optimized, this is the time to recover
+ * the final stat result.
+ */
+ if (!r->finfo.filetype || r->finfo.filetype == APR_LNK) {
+ r->finfo = thisinfo;
+ }
+
+ /* Now splice the saved path_info back onto any new path_info
+ */
+ if (save_path_info) {
+ if (r->path_info && *r->path_info) {
+ r->path_info = ap_make_full_path(r->pool, r->path_info,
+ save_path_info);
+ }
+ else {
+ r->path_info = save_path_info;
+ }
+ }
+
+ /*
+ * Now we'll deal with the regexes, note we pick up sec_idx
+ * where we left off (we gave up after we hit entry_core->r)
+ */
+ for (; sec_idx < num_sec; ++sec_idx) {
+
+ core_dir_config *entry_core;
+ entry_core = ap_get_module_config(sec_ent[sec_idx], &core_module);
+
+ if (!entry_core->r) {
+ continue;
+ }
+
+ if (ap_regexec(entry_core->r, r->filename, 0, NULL, AP_REG_NOTEOL)) {
+ continue;
+ }
+
+ /* If we haven't already continue'd above, we have a match.
+ *
+ * Calculate our full-context core opts & override.
+ */
+ core_opts_merge(sec_ent[sec_idx], &opts);
+
+ /* If we merged this same section last time, reuse it
+ */
+ if (matches) {
+ if (last_walk->matched == sec_ent[sec_idx]) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ continue;
+ }
+
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged) {
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ sec_ent[sec_idx]);
+ }
+ else {
+ now_merged = sec_ent[sec_idx];
+ }
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = sec_ent[sec_idx];
+ last_walk->merged = now_merged;
+ }
+
+ /* Whoops - everything matched in sequence, but the original walk
+ * found some additional matches. Truncate them.
+ */
+ if (matches) {
+ cache->walked->nelts -= matches;
+ }
+ }
+
+/* It seems this shouldn't be needed anymore. We translated the
+ x symlink above into a real resource, and should have died up there.
+ x Even if we keep this, it needs more thought (maybe an r->file_is_symlink)
+ x perhaps it should actually happen in file_walk, so we catch more
+ x obscure cases in autoindex subrequests, etc.
+ x
+ x * Symlink permissions are determined by the parent. If the request is
+ x * for a directory then applying the symlink test here would use the
+ x * permissions of the directory as opposed to its parent. Consider a
+ x * symlink pointing to a dir with a .htaccess disallowing symlinks. If
+ x * you access /symlink (or /symlink/) you would get a 403 without this
+ x * APR_DIR test. But if you accessed /symlink/index.html, for example,
+ x * you would *not* get the 403.
+ x
+ x if (r->finfo.filetype != APR_DIR
+ x && (res = resolve_symlink(r->filename, r->info, ap_allow_options(r),
+ x r->pool))) {
+ x ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ x "Symbolic link not allowed: %s", r->filename);
+ x return res;
+ x }
+ */
+
+ /* Save future sub-requestors much angst in processing
+ * this subrequest. If dir_walk couldn't canonicalize
+ * the file path, nothing can.
+ */
+ r->canonical_filename = r->filename;
+
+ if (r->finfo.filetype == APR_DIR) {
+ cache->cached = r->filename;
+ }
+ else {
+ cache->cached = ap_make_dirstr_parent(r->pool, r->filename);
+ }
+
+ cache->dir_conf_tested = sec_ent;
+ cache->dir_conf_merged = r->per_dir_config;
+
+ /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs,
+ * and note the end result to (potentially) skip this step next time.
+ */
+ if (now_merged) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool,
+ r->per_dir_config,
+ now_merged);
+ }
+ cache->per_dir_result = r->per_dir_config;
+
+ return OK;
+}
+
+
+AP_DECLARE(int) ap_location_walk(request_rec *r)
+{
+ ap_conf_vector_t *now_merged = NULL;
+ core_server_config *sconf = ap_get_module_config(r->server->module_config,
+ &core_module);
+ ap_conf_vector_t **sec_ent = (ap_conf_vector_t **)sconf->sec_url->elts;
+ int num_sec = sconf->sec_url->nelts;
+ walk_cache_t *cache;
+ const char *entry_uri;
+
+ /* No tricks here, there are no <Locations > to parse in this vhost.
+ * We won't destroy the cache, just in case _this_ redirect is later
+ * redirected again to a vhost with <Location > blocks to optimize.
+ */
+ if (!num_sec) {
+ return OK;
+ }
+
+ cache = prep_walk_cache(AP_NOTE_LOCATION_WALK, r);
+
+ /* Location and LocationMatch differ on their behaviour w.r.t. multiple
+ * slashes. Location matches multiple slashes with a single slash,
+ * LocationMatch doesn't. An exception, for backwards brokenness is
+ * absoluteURIs... in which case neither match multiple slashes.
+ */
+ if (r->uri[0] != '/') {
+ entry_uri = r->uri;
+ }
+ else {
+ char *uri = apr_pstrdup(r->pool, r->uri);
+ ap_no2slash(uri);
+ entry_uri = uri;
+ }
+
+ /* If we have an cache->cached location that matches r->uri,
+ * and the vhost's list of locations hasn't changed, we can skip
+ * rewalking the location_walk entries.
+ */
+ if (cache->cached
+ && (cache->dir_conf_tested == sec_ent)
+ && (strcmp(entry_uri, cache->cached) == 0)) {
+ /* Well this looks really familiar! If our end-result (per_dir_result)
+ * didn't change, we have absolutely nothing to do :)
+ * Otherwise (as is the case with most dir_merged/file_merged requests)
+ * we must merge our dir_conf_merged onto this new r->per_dir_config.
+ */
+ if (r->per_dir_config == cache->per_dir_result) {
+ return OK;
+ }
+
+ if (r->per_dir_config == cache->dir_conf_merged) {
+ r->per_dir_config = cache->per_dir_result;
+ return OK;
+ }
+
+ if (cache->walked->nelts) {
+ now_merged = ((walk_walked_t*)cache->walked->elts)
+ [cache->walked->nelts - 1].merged;
+ }
+ }
+ else {
+ /* We start now_merged from NULL since we want to build
+ * a locations list that can be merged to any vhost.
+ */
+ int len, sec_idx;
+ int matches = cache->walked->nelts;
+ walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts;
+ cache->cached = entry_uri;
+
+ /* Go through the location entries, and check for matches.
+ * We apply the directive sections in given order, we should
+ * really try them with the most general first.
+ */
+ for (sec_idx = 0; sec_idx < num_sec; ++sec_idx) {
+
+ core_dir_config *entry_core;
+ entry_core = ap_get_module_config(sec_ent[sec_idx], &core_module);
+
+ /* ### const strlen can be optimized in location config parsing */
+ len = strlen(entry_core->d);
+
+ /* Test the regex, fnmatch or string as appropriate.
+ * If it's a strcmp, and the <Location > pattern was
+ * not slash terminated, then this uri must be slash
+ * terminated (or at the end of the string) to match.
+ */
+ if (entry_core->r
+ ? ap_regexec(entry_core->r, r->uri, 0, NULL, 0)
+ : (entry_core->d_is_fnmatch
+ ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME)
+ : (strncmp(entry_core->d, cache->cached, len)
+ || (entry_core->d[len - 1] != '/'
+ && cache->cached[len] != '/'
+ && cache->cached[len] != '\0')))) {
+ continue;
+ }
+
+ /* If we merged this same section last time, reuse it
+ */
+ if (matches) {
+ if (last_walk->matched == sec_ent[sec_idx]) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ continue;
+ }
+
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged) {
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ sec_ent[sec_idx]);
+ }
+ else {
+ now_merged = sec_ent[sec_idx];
+ }
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = sec_ent[sec_idx];
+ last_walk->merged = now_merged;
+ }
+
+ /* Whoops - everything matched in sequence, but the original walk
+ * found some additional matches. Truncate them.
+ */
+ if (matches) {
+ cache->walked->nelts -= matches;
+ }
+ }
+
+ cache->dir_conf_tested = sec_ent;
+ cache->dir_conf_merged = r->per_dir_config;
+
+ /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs,
+ * and note the end result to (potentially) skip this step next time.
+ */
+ if (now_merged) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool,
+ r->per_dir_config,
+ now_merged);
+ }
+ cache->per_dir_result = r->per_dir_config;
+
+ return OK;
+}
+
+AP_DECLARE(int) ap_file_walk(request_rec *r)
+{
+ ap_conf_vector_t *now_merged = NULL;
+ core_dir_config *dconf = ap_get_module_config(r->per_dir_config,
+ &core_module);
+ ap_conf_vector_t **sec_ent = (ap_conf_vector_t **)dconf->sec_file->elts;
+ int num_sec = dconf->sec_file->nelts;
+ walk_cache_t *cache;
+ const char *test_file;
+
+ /* To allow broken modules to proceed, we allow missing filenames to pass.
+ * We will catch it later if it's heading for the core handler.
+ * directory_walk already posted an INFO note for module debugging.
+ */
+ if (r->filename == NULL) {
+ return OK;
+ }
+
+ cache = prep_walk_cache(AP_NOTE_FILE_WALK, r);
+
+ /* No tricks here, there are just no <Files > to parse in this context.
+ * We won't destroy the cache, just in case _this_ redirect is later
+ * redirected again to a context containing the same or similar <Files >.
+ */
+ if (!num_sec) {
+ return OK;
+ }
+
+ /* Get the basename .. and copy for the cache just
+ * in case r->filename is munged by another module
+ */
+ test_file = strrchr(r->filename, '/');
+ if (test_file == NULL) {
+ test_file = apr_pstrdup(r->pool, r->filename);
+ }
+ else {
+ test_file = apr_pstrdup(r->pool, ++test_file);
+ }
+
+ /* If we have an cache->cached file name that matches test_file,
+ * and the directory's list of file sections hasn't changed, we
+ * can skip rewalking the file_walk entries.
+ */
+ if (cache->cached
+ && (cache->dir_conf_tested == sec_ent)
+ && (strcmp(test_file, cache->cached) == 0)) {
+ /* Well this looks really familiar! If our end-result (per_dir_result)
+ * didn't change, we have absolutely nothing to do :)
+ * Otherwise (as is the case with most dir_merged requests)
+ * we must merge our dir_conf_merged onto this new r->per_dir_config.
+ */
+ if (r->per_dir_config == cache->per_dir_result) {
+ return OK;
+ }
+
+ if (r->per_dir_config == cache->dir_conf_merged) {
+ r->per_dir_config = cache->per_dir_result;
+ return OK;
+ }
+
+ if (cache->walked->nelts) {
+ now_merged = ((walk_walked_t*)cache->walked->elts)
+ [cache->walked->nelts - 1].merged;
+ }
+ }
+ else {
+ /* We start now_merged from NULL since we want to build
+ * a file section list that can be merged to any dir_walk.
+ */
+ int sec_idx;
+ int matches = cache->walked->nelts;
+ walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts;
+ cache->cached = test_file;
+
+ /* Go through the location entries, and check for matches.
+ * We apply the directive sections in given order, we should
+ * really try them with the most general first.
+ */
+ for (sec_idx = 0; sec_idx < num_sec; ++sec_idx) {
+
+ core_dir_config *entry_core;
+ entry_core = ap_get_module_config(sec_ent[sec_idx], &core_module);
+
+ if (entry_core->r
+ ? ap_regexec(entry_core->r, cache->cached , 0, NULL, 0)
+ : (entry_core->d_is_fnmatch
+ ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME)
+ : strcmp(entry_core->d, cache->cached))) {
+ continue;
+ }
+
+ /* If we merged this same section last time, reuse it
+ */
+ if (matches) {
+ if (last_walk->matched == sec_ent[sec_idx]) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ continue;
+ }
+
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged) {
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ sec_ent[sec_idx]);
+ }
+ else {
+ now_merged = sec_ent[sec_idx];
+ }
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = sec_ent[sec_idx];
+ last_walk->merged = now_merged;
+ }
+
+ /* Whoops - everything matched in sequence, but the original walk
+ * found some additional matches. Truncate them.
+ */
+ if (matches) {
+ cache->walked->nelts -= matches;
+ }
+ }
+
+ cache->dir_conf_tested = sec_ent;
+ cache->dir_conf_merged = r->per_dir_config;
+
+ /* Merge our cache->dir_conf_merged construct with the r->per_dir_configs,
+ * and note the end result to (potentially) skip this step next time.
+ */
+ if (now_merged) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool,
+ r->per_dir_config,
+ now_merged);
+ }
+ cache->per_dir_result = r->per_dir_config;
+
+ return OK;
+}
+
+/*****************************************************************
+ *
+ * The sub_request mechanism.
+ *
+ * Fns to look up a relative URI from, e.g., a map file or SSI document.
+ * These do all access checks, etc., but don't actually run the transaction
+ * ... use run_sub_req below for that. Also, be sure to use destroy_sub_req
+ * as appropriate if you're likely to be creating more than a few of these.
+ * (An early Apache version didn't destroy the sub_reqs used in directory
+ * indexing. The result, when indexing a directory with 800-odd files in
+ * it, was massively excessive storage allocation).
+ *
+ * Note more manipulation of protocol-specific vars in the request
+ * structure...
+ */
+
+static request_rec *make_sub_request(const request_rec *r,
+ ap_filter_t *next_filter)
+{
+ apr_pool_t *rrp;
+ request_rec *rnew;
+
+ apr_pool_create(&rrp, r->pool);
+ apr_pool_tag(rrp, "subrequest");
+ rnew = apr_pcalloc(rrp, sizeof(request_rec));
+ rnew->pool = rrp;
+
+ rnew->hostname = r->hostname;
+ rnew->request_time = r->request_time;
+ rnew->connection = r->connection;
+ rnew->server = r->server;
+
+ rnew->request_config = ap_create_request_config(rnew->pool);
+
+ /* Start a clean config from this subrequest's vhost. Optimization in
+ * Location/File/Dir walks from the parent request assure that if the
+ * config blocks of the subrequest match the parent request, no merges
+ * will actually occur (and generally a minimal number of merges are
+ * required, even if the parent and subrequest aren't quite identical.)
+ */
+ rnew->per_dir_config = r->server->lookup_defaults;
+
+ rnew->htaccess = r->htaccess;
+ rnew->allowed_methods = ap_make_method_list(rnew->pool, 2);
+
+ /* make a copy of the allowed-methods list */
+ ap_copy_method_list(rnew->allowed_methods, r->allowed_methods);
+
+ /* start with the same set of output filters */
+ if (next_filter) {
+ /* while there are no input filters for a subrequest, we will
+ * try to insert some, so if we don't have valid data, the code
+ * will seg fault.
+ */
+ rnew->input_filters = r->input_filters;
+ rnew->proto_input_filters = r->proto_input_filters;
+ rnew->output_filters = next_filter;
+ rnew->proto_output_filters = r->proto_output_filters;
+ ap_add_output_filter_handle(ap_subreq_core_filter_handle,
+ NULL, rnew, rnew->connection);
+ }
+ else {
+ /* If NULL - we are expecting to be internal_fast_redirect'ed
+ * to this subrequest - or this request will never be invoked.
+ * Ignore the original request filter stack entirely, and
+ * drill the input and output stacks back to the connection.
+ */
+ rnew->proto_input_filters = r->proto_input_filters;
+ rnew->proto_output_filters = r->proto_output_filters;
+
+ rnew->input_filters = r->proto_input_filters;
+ rnew->output_filters = r->proto_output_filters;
+ }
+
+ /* no input filters for a subrequest */
+
+ ap_set_sub_req_protocol(rnew, r);
+
+ /* We have to run this after we fill in sub req vars,
+ * or the r->main pointer won't be setup
+ */
+ ap_run_create_request(rnew);
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ rnew->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+ return rnew;
+}
+
+AP_CORE_DECLARE_NONSTD(apr_status_t) ap_sub_req_output_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb)
+{
+ apr_bucket *e = APR_BRIGADE_LAST(bb);
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ apr_bucket_delete(e);
+ }
+
+ if (!APR_BRIGADE_EMPTY(bb)) {
+ return ap_pass_brigade(f->next, bb);
+ }
+
+ return APR_SUCCESS;
+}
+
+
+AP_DECLARE(int) ap_some_auth_required(request_rec *r)
+{
+ /* Is there a require line configured for the type of *this* req? */
+
+ const apr_array_header_t *reqs_arr = ap_requires(r);
+ require_line *reqs;
+ int i;
+
+ if (!reqs_arr) {
+ return 0;
+ }
+
+ reqs = (require_line *) reqs_arr->elts;
+
+ for (i = 0; i < reqs_arr->nelts; ++i) {
+ if (reqs[i].method_mask & (AP_METHOD_BIT << r->method_number)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+AP_DECLARE(request_rec *) ap_sub_req_method_uri(const char *method,
+ const char *new_uri,
+ const request_rec *r,
+ ap_filter_t *next_filter)
+{
+ request_rec *rnew;
+ /* Initialise res, to avoid a gcc warning */
+ int res = HTTP_INTERNAL_SERVER_ERROR;
+ char *udir;
+
+ rnew = make_sub_request(r, next_filter);
+
+ /* would be nicer to pass "method" to ap_set_sub_req_protocol */
+ rnew->method = method;
+ rnew->method_number = ap_method_number_of(method);
+
+ if (new_uri[0] == '/') {
+ ap_parse_uri(rnew, new_uri);
+ }
+ else {
+ udir = ap_make_dirstr_parent(rnew->pool, r->uri);
+ udir = ap_escape_uri(rnew->pool, udir); /* re-escape it */
+ ap_parse_uri(rnew, ap_make_full_path(rnew->pool, udir, new_uri));
+ }
+
+ /* We cannot return NULL without violating the API. So just turn this
+ * subrequest into a 500 to indicate the failure. */
+ if (ap_is_recursion_limit_exceeded(r)) {
+ rnew->status = HTTP_INTERNAL_SERVER_ERROR;
+ return rnew;
+ }
+
+ /* lookup_uri
+ * If the content can be served by the quick_handler, we can
+ * safely bypass request_internal processing.
+ *
+ * If next_filter is NULL we are expecting to be
+ * internal_fast_redirect'ed to the subrequest, or the subrequest will
+ * never be invoked. We need to make sure that the quickhandler is not
+ * invoked by any lookups. Since an internal_fast_redirect will always
+ * occur too late for the quickhandler to handle the request.
+ */
+ if (next_filter) {
+ res = ap_run_quick_handler(rnew, 1);
+ }
+
+ if (next_filter == NULL || res != OK) {
+ if ((res = ap_process_request_internal(rnew))) {
+ rnew->status = res;
+ }
+ }
+
+ return rnew;
+}
+
+AP_DECLARE(request_rec *) ap_sub_req_lookup_uri(const char *new_uri,
+ const request_rec *r,
+ ap_filter_t *next_filter)
+{
+ return ap_sub_req_method_uri("GET", new_uri, r, next_filter);
+}
+
+AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent,
+ const request_rec *r,
+ int subtype,
+ ap_filter_t *next_filter)
+{
+ request_rec *rnew;
+ int res;
+ char *fdir;
+ char *udir;
+
+ rnew = make_sub_request(r, next_filter);
+
+ /* Special case: we are looking at a relative lookup in the same directory.
+ * This is 100% safe, since dirent->name just came from the filesystem.
+ */
+ if (r->path_info && *r->path_info) {
+ /* strip path_info off the end of the uri to keep it in sync
+ * with r->filename, which has already been stripped by directory_walk,
+ * merge the dirent->name, and then, if the caller wants us to remerge
+ * the original path info, do so. Note we never fix the path_info back
+ * to r->filename, since dir_walk would do so (but we don't expect it
+ * to happen in the usual cases)
+ */
+ udir = apr_pstrdup(rnew->pool, r->uri);
+ udir[ap_find_path_info(udir, r->path_info)] = '\0';
+ udir = ap_make_dirstr_parent(rnew->pool, udir);
+
+ rnew->uri = ap_make_full_path(rnew->pool, udir, dirent->name);
+ if (subtype == AP_SUBREQ_MERGE_ARGS) {
+ rnew->uri = ap_make_full_path(rnew->pool, rnew->uri, r->path_info + 1);
+ rnew->path_info = apr_pstrdup(rnew->pool, r->path_info);
+ }
+ rnew->uri = ap_escape_uri(rnew->pool, rnew->uri);
+ }
+ else {
+ udir = ap_make_dirstr_parent(rnew->pool, r->uri);
+ rnew->uri = ap_escape_uri(rnew->pool, ap_make_full_path(rnew->pool,
+ udir,
+ dirent->name));
+ }
+
+ fdir = ap_make_dirstr_parent(rnew->pool, r->filename);
+ rnew->filename = ap_make_full_path(rnew->pool, fdir, dirent->name);
+ if (r->canonical_filename == r->filename) {
+ rnew->canonical_filename = rnew->filename;
+ }
+
+ /* XXX This is now less relevant; we will do a full location walk
+ * these days for this case. Preserve the apr_stat results, and
+ * perhaps we also tag that symlinks were tested and/or found for
+ * r->filename.
+ */
+ rnew->per_dir_config = r->server->lookup_defaults;
+
+ if ((dirent->valid & APR_FINFO_MIN) != APR_FINFO_MIN) {
+ /*
+ * apr_dir_read isn't very complete on this platform, so
+ * we need another apr_stat (with or without APR_FINFO_LINK
+ * depending on whether we allow all symlinks here.) If this
+ * is an APR_LNK that resolves to an APR_DIR, then we will rerun
+ * everything anyways... this should be safe.
+ */
+ apr_status_t rv;
+ if (ap_allow_options(rnew) & OPT_SYM_LINKS) {
+ if (((rv = apr_stat(&rnew->finfo, rnew->filename,
+ APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS)
+ && (rv != APR_INCOMPLETE)) {
+ rnew->finfo.filetype = 0;
+ }
+ }
+ else {
+ if (((rv = apr_stat(&rnew->finfo, rnew->filename,
+ APR_FINFO_LINK | APR_FINFO_MIN,
+ rnew->pool)) != APR_SUCCESS)
+ && (rv != APR_INCOMPLETE)) {
+ rnew->finfo.filetype = 0;
+ }
+ }
+ }
+ else {
+ memcpy(&rnew->finfo, dirent, sizeof(apr_finfo_t));
+ }
+
+ if (rnew->finfo.filetype == APR_LNK) {
+ /*
+ * Resolve this symlink. We should tie this back to dir_walk's cache
+ */
+ if ((res = resolve_symlink(rnew->filename, &rnew->finfo,
+ ap_allow_options(rnew), rnew->pool))
+ != OK) {
+ rnew->status = res;
+ return rnew;
+ }
+ }
+
+ if (rnew->finfo.filetype == APR_DIR) {
+ /* ap_make_full_path overallocated the buffers
+ * by one character to help us out here.
+ */
+ strcpy(rnew->filename + strlen(rnew->filename), "/");
+ if (!rnew->path_info || !*rnew->path_info) {
+ strcpy(rnew->uri + strlen(rnew->uri ), "/");
+ }
+ }
+
+ /* fill in parsed_uri values
+ */
+ if (r->args && *r->args && (subtype == AP_SUBREQ_MERGE_ARGS)) {
+ ap_parse_uri(rnew, apr_pstrcat(r->pool, rnew->uri, "?",
+ r->args, NULL));
+ }
+ else {
+ ap_parse_uri(rnew, rnew->uri);
+ }
+
+ /* We cannot return NULL without violating the API. So just turn this
+ * subrequest into a 500. */
+ if (ap_is_recursion_limit_exceeded(r)) {
+ rnew->status = HTTP_INTERNAL_SERVER_ERROR;
+ return rnew;
+ }
+
+ if ((res = ap_process_request_internal(rnew))) {
+ rnew->status = res;
+ }
+
+ return rnew;
+}
+
+AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file,
+ const request_rec *r,
+ ap_filter_t *next_filter)
+{
+ request_rec *rnew;
+ int res;
+ char *fdir;
+ apr_size_t fdirlen;
+
+ rnew = make_sub_request(r, next_filter);
+
+ fdir = ap_make_dirstr_parent(rnew->pool, r->filename);
+ fdirlen = strlen(fdir);
+
+ /* Translate r->filename, if it was canonical, it stays canonical
+ */
+ if (r->canonical_filename == r->filename) {
+ rnew->canonical_filename = (char*)(1);
+ }
+
+ if (apr_filepath_merge(&rnew->filename, fdir, new_file,
+ APR_FILEPATH_TRUENAME, rnew->pool) != APR_SUCCESS) {
+ rnew->status = HTTP_FORBIDDEN;
+ return rnew;
+ }
+
+ if (rnew->canonical_filename) {
+ rnew->canonical_filename = rnew->filename;
+ }
+
+ /*
+ * Check for a special case... if there are no '/' characters in new_file
+ * at all, and the path was the same, then we are looking at a relative
+ * lookup in the same directory. Fixup the URI to match.
+ */
+
+ if (strncmp(rnew->filename, fdir, fdirlen) == 0
+ && rnew->filename[fdirlen]
+ && ap_strchr_c(rnew->filename + fdirlen, '/') == NULL) {
+ apr_status_t rv;
+ if (ap_allow_options(rnew) & OPT_SYM_LINKS) {
+ if (((rv = apr_stat(&rnew->finfo, rnew->filename,
+ APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS)
+ && (rv != APR_INCOMPLETE)) {
+ rnew->finfo.filetype = 0;
+ }
+ }
+ else {
+ if (((rv = apr_stat(&rnew->finfo, rnew->filename,
+ APR_FINFO_LINK | APR_FINFO_MIN,
+ rnew->pool)) != APR_SUCCESS)
+ && (rv != APR_INCOMPLETE)) {
+ rnew->finfo.filetype = 0;
+ }
+ }
+
+ if (r->uri && *r->uri) {
+ char *udir = ap_make_dirstr_parent(rnew->pool, r->uri);
+ rnew->uri = ap_make_full_path(rnew->pool, udir,
+ rnew->filename + fdirlen);
+ ap_parse_uri(rnew, rnew->uri); /* fill in parsed_uri values */
+ }
+ else {
+ ap_parse_uri(rnew, new_file); /* fill in parsed_uri values */
+ rnew->uri = apr_pstrdup(rnew->pool, "");
+ }
+ }
+ else {
+ /* XXX: @@@: What should be done with the parsed_uri values?
+ * We would be better off stripping down to the 'common' elements
+ * of the path, then reassembling the URI as best as we can.
+ */
+ ap_parse_uri(rnew, new_file); /* fill in parsed_uri values */
+ /*
+ * XXX: this should be set properly like it is in the same-dir case
+ * but it's actually sometimes to impossible to do it... because the
+ * file may not have a uri associated with it -djg
+ */
+ rnew->uri = apr_pstrdup(rnew->pool, "");
+ }
+
+ /* We cannot return NULL without violating the API. So just turn this
+ * subrequest into a 500. */
+ if (ap_is_recursion_limit_exceeded(r)) {
+ rnew->status = HTTP_INTERNAL_SERVER_ERROR;
+ return rnew;
+ }
+
+ if ((res = ap_process_request_internal(rnew))) {
+ rnew->status = res;
+ }
+
+ return rnew;
+}
+
+AP_DECLARE(int) ap_run_sub_req(request_rec *r)
+{
+ int retval = DECLINED;
+ /* Run the quick handler if the subrequest is not a dirent or file
+ * subrequest
+ */
+ if (!(r->filename && r->finfo.filetype)) {
+ retval = ap_run_quick_handler(r, 0);
+ }
+ if (retval != OK) {
+ retval = ap_invoke_handler(r);
+ if (retval == DONE) {
+ retval = OK;
+ }
+ }
+ ap_finalize_sub_req_protocol(r);
+ return retval;
+}
+
+AP_DECLARE(void) ap_destroy_sub_req(request_rec *r)
+{
+ /* Reclaim the space */
+ apr_pool_destroy(r->pool);
+}
+
+/*
+ * Function to set the r->mtime field to the specified value if it's later
+ * than what's already there.
+ */
+AP_DECLARE(void) ap_update_mtime(request_rec *r, apr_time_t dependency_mtime)
+{
+ if (r->mtime < dependency_mtime) {
+ r->mtime = dependency_mtime;
+ }
+}
+
+/*
+ * Is it the initial main request, which we only get *once* per HTTP request?
+ */
+AP_DECLARE(int) ap_is_initial_req(request_rec *r)
+{
+ return (r->main == NULL) /* otherwise, this is a sub-request */
+ && (r->prev == NULL); /* otherwise, this is an internal redirect */
+}
diff --git a/server/scoreboard.c b/server/scoreboard.c
new file mode 100644
index 00000000..9cb7359a
--- /dev/null
+++ b/server/scoreboard.c
@@ -0,0 +1,498 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "ap_mpm.h"
+
+#include "mpm.h"
+#include "scoreboard.h"
+
+AP_DECLARE_DATA scoreboard *ap_scoreboard_image = NULL;
+AP_DECLARE_DATA const char *ap_scoreboard_fname = NULL;
+AP_DECLARE_DATA int ap_extended_status = 0;
+
+#if APR_HAS_SHARED_MEMORY
+
+#include "apr_shm.h"
+
+#ifndef WIN32
+static /* but must be exported to mpm_winnt */
+#endif
+ apr_shm_t *ap_scoreboard_shm = NULL;
+
+#endif
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(pre_mpm)
+)
+
+AP_IMPLEMENT_HOOK_RUN_ALL(int,pre_mpm,
+ (apr_pool_t *p, ap_scoreboard_e sb_type),
+ (p, sb_type),OK,DECLINED)
+
+static APR_OPTIONAL_FN_TYPE(ap_proxy_lb_workers)
+ *proxy_lb_workers;
+
+struct ap_sb_handle_t {
+ int child_num;
+ int thread_num;
+};
+
+static int server_limit, thread_limit, lb_limit;
+static apr_size_t scoreboard_size;
+
+/*
+ * ToDo:
+ * This function should be renamed to cleanup_shared
+ * and it should handle cleaning up a scoreboard shared
+ * between processes using any form of IPC (file, shared memory
+ * segment, etc.). Leave it as is now because it is being used
+ * by various MPMs.
+ */
+static apr_status_t ap_cleanup_shared_mem(void *d)
+{
+#if APR_HAS_SHARED_MEMORY
+ free(ap_scoreboard_image);
+ ap_scoreboard_image = NULL;
+ apr_shm_destroy(ap_scoreboard_shm);
+#endif
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(int) ap_calc_scoreboard_size(void)
+{
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
+
+ if (!proxy_lb_workers)
+ proxy_lb_workers = APR_RETRIEVE_OPTIONAL_FN(ap_proxy_lb_workers);
+ if (proxy_lb_workers)
+ lb_limit = proxy_lb_workers();
+ else
+ lb_limit = 0;
+
+ scoreboard_size = sizeof(global_score);
+ scoreboard_size += sizeof(process_score) * server_limit;
+ scoreboard_size += sizeof(worker_score) * server_limit * thread_limit;
+ if (lb_limit)
+ scoreboard_size += sizeof(lb_score) * lb_limit;
+
+ return scoreboard_size;
+}
+
+void ap_init_scoreboard(void *shared_score)
+{
+ char *more_storage;
+ int i;
+
+ ap_calc_scoreboard_size();
+ ap_scoreboard_image =
+ calloc(1, sizeof(scoreboard) + server_limit * sizeof(worker_score *) +
+ server_limit * lb_limit * sizeof(lb_score *));
+ more_storage = shared_score;
+ ap_scoreboard_image->global = (global_score *)more_storage;
+ more_storage += sizeof(global_score);
+ ap_scoreboard_image->parent = (process_score *)more_storage;
+ more_storage += sizeof(process_score) * server_limit;
+ ap_scoreboard_image->servers =
+ (worker_score **)((char*)ap_scoreboard_image + sizeof(scoreboard));
+ for (i = 0; i < server_limit; i++) {
+ ap_scoreboard_image->servers[i] = (worker_score *)more_storage;
+ more_storage += thread_limit * sizeof(worker_score);
+ }
+ if (lb_limit) {
+ ap_scoreboard_image->balancers = (lb_score *)more_storage;
+ more_storage += lb_limit * sizeof(lb_score);
+ }
+ ap_assert(more_storage == (char*)shared_score + scoreboard_size);
+ ap_scoreboard_image->global->server_limit = server_limit;
+ ap_scoreboard_image->global->thread_limit = thread_limit;
+ ap_scoreboard_image->global->lb_limit = lb_limit;
+}
+
+/**
+ * Create a name-based scoreboard in the given pool using the
+ * given filename.
+ */
+static apr_status_t create_namebased_scoreboard(apr_pool_t *pool,
+ const char *fname)
+{
+#if APR_HAS_SHARED_MEMORY
+ apr_status_t rv;
+
+ /* The shared memory file must not exist before we create the
+ * segment. */
+ apr_shm_remove(fname, pool); /* ignore errors */
+
+ rv = apr_shm_create(&ap_scoreboard_shm, scoreboard_size, fname, pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "unable to create scoreboard \"%s\" "
+ "(name-based shared memory failure)", fname);
+ return rv;
+ }
+#endif /* APR_HAS_SHARED_MEMORY */
+ return APR_SUCCESS;
+}
+
+/* ToDo: This function should be made to handle setting up
+ * a scoreboard shared between processes using any IPC technique,
+ * not just a shared memory segment
+ */
+static apr_status_t open_scoreboard(apr_pool_t *pconf)
+{
+#if APR_HAS_SHARED_MEMORY
+ apr_status_t rv;
+ char *fname = NULL;
+ apr_pool_t *global_pool;
+
+ /* We don't want to have to recreate the scoreboard after
+ * restarts, so we'll create a global pool and never clean it.
+ */
+ rv = apr_pool_create(&global_pool, NULL);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "Fatal error: unable to create global pool "
+ "for use with by the scoreboard");
+ return rv;
+ }
+
+ /* The config says to create a name-based shmem */
+ if (ap_scoreboard_fname) {
+ /* make sure it's an absolute pathname */
+ fname = ap_server_root_relative(pconf, ap_scoreboard_fname);
+ if (!fname) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, APR_EBADPATH, NULL,
+ "Fatal error: Invalid Scoreboard path %s",
+ ap_scoreboard_fname);
+ return APR_EBADPATH;
+ }
+ return create_namebased_scoreboard(global_pool, fname);
+ }
+ else { /* config didn't specify, we get to choose shmem type */
+ rv = apr_shm_create(&ap_scoreboard_shm, scoreboard_size, NULL,
+ global_pool); /* anonymous shared memory */
+ if ((rv != APR_SUCCESS) && (rv != APR_ENOTIMPL)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
+ "Unable to create scoreboard "
+ "(anonymous shared memory failure)");
+ return rv;
+ }
+ /* Make up a filename and do name-based shmem */
+ else if (rv == APR_ENOTIMPL) {
+ /* Make sure it's an absolute pathname */
+ ap_scoreboard_fname = DEFAULT_SCOREBOARD;
+ fname = ap_server_root_relative(pconf, ap_scoreboard_fname);
+
+ return create_namebased_scoreboard(global_pool, fname);
+ }
+ }
+#endif /* APR_HAS_SHARED_MEMORY */
+ return APR_SUCCESS;
+}
+
+/* If detach is non-zero, this is a seperate child process,
+ * if zero, it is a forked child.
+ */
+apr_status_t ap_reopen_scoreboard(apr_pool_t *p, apr_shm_t **shm, int detached)
+{
+#if APR_HAS_SHARED_MEMORY
+ if (!detached) {
+ return APR_SUCCESS;
+ }
+ if (apr_shm_size_get(ap_scoreboard_shm) < scoreboard_size) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
+ "Fatal error: shared scoreboard too small for child!");
+ apr_shm_detach(ap_scoreboard_shm);
+ ap_scoreboard_shm = NULL;
+ return APR_EINVAL;
+ }
+ /* everything will be cleared shortly */
+ if (*shm) {
+ *shm = ap_scoreboard_shm;
+ }
+#endif
+ return APR_SUCCESS;
+}
+
+apr_status_t ap_cleanup_scoreboard(void *d)
+{
+ if (ap_scoreboard_image == NULL) {
+ return APR_SUCCESS;
+ }
+ if (ap_scoreboard_image->global->sb_type == SB_SHARED) {
+ ap_cleanup_shared_mem(NULL);
+ }
+ else {
+ free(ap_scoreboard_image->global);
+ free(ap_scoreboard_image);
+ ap_scoreboard_image = NULL;
+ }
+ return APR_SUCCESS;
+}
+
+/* Create or reinit an existing scoreboard. The MPM can control whether
+ * the scoreboard is shared across multiple processes or not
+ */
+int ap_create_scoreboard(apr_pool_t *p, ap_scoreboard_e sb_type)
+{
+ int running_gen = 0;
+ int i;
+#if APR_HAS_SHARED_MEMORY
+ apr_status_t rv;
+#endif
+
+ if (ap_scoreboard_image) {
+ running_gen = ap_scoreboard_image->global->running_generation;
+ ap_scoreboard_image->global->restart_time = apr_time_now();
+ memset(ap_scoreboard_image->parent, 0,
+ sizeof(process_score) * server_limit);
+ for (i = 0; i < server_limit; i++) {
+ memset(ap_scoreboard_image->servers[i], 0,
+ sizeof(worker_score) * thread_limit);
+ }
+ /* Clean up the lb workers data */
+ if (lb_limit) {
+ memset(ap_scoreboard_image->balancers, 0,
+ sizeof(lb_score) * lb_limit);
+ }
+ return OK;
+ }
+
+ ap_calc_scoreboard_size();
+#if APR_HAS_SHARED_MEMORY
+ if (sb_type == SB_SHARED) {
+ void *sb_shared;
+ rv = open_scoreboard(p);
+ if (rv || !(sb_shared = apr_shm_baseaddr_get(ap_scoreboard_shm))) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ memset(sb_shared, 0, scoreboard_size);
+ ap_init_scoreboard(sb_shared);
+ }
+ else
+#endif
+ {
+ /* A simple malloc will suffice */
+ void *sb_mem = calloc(1, scoreboard_size);
+ if (sb_mem == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
+ "(%d)%s: cannot allocate scoreboard",
+ errno, strerror(errno));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ap_init_scoreboard(sb_mem);
+ }
+
+ ap_scoreboard_image->global->sb_type = sb_type;
+ ap_scoreboard_image->global->running_generation = running_gen;
+ ap_scoreboard_image->global->restart_time = apr_time_now();
+
+ apr_pool_cleanup_register(p, NULL, ap_cleanup_scoreboard, apr_pool_cleanup_null);
+
+ return OK;
+}
+
+/* Routines called to deal with the scoreboard image
+ * --- note that we do *not* need write locks, since update_child_status
+ * only updates a *single* record in place, and only one process writes to
+ * a given scoreboard slot at a time (either the child process owning that
+ * slot, or the parent, noting that the child has died).
+ *
+ * As a final note --- setting the score entry to getpid() is always safe,
+ * since when the parent is writing an entry, it's only noting SERVER_DEAD
+ * anyway.
+ */
+
+AP_DECLARE(int) ap_exists_scoreboard_image(void)
+{
+ return (ap_scoreboard_image ? 1 : 0);
+}
+
+AP_DECLARE(void) ap_increment_counts(ap_sb_handle_t *sb, request_rec *r)
+{
+ worker_score *ws;
+
+ ws = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num];
+
+#ifdef HAVE_TIMES
+ times(&ws->times);
+#endif
+ ws->access_count++;
+ ws->my_access_count++;
+ ws->conn_count++;
+ ws->bytes_served += r->bytes_sent;
+ ws->my_bytes_served += r->bytes_sent;
+ ws->conn_bytes += r->bytes_sent;
+}
+
+int find_child_by_pid(apr_proc_t *pid)
+{
+ int i;
+ int max_daemons_limit;
+
+ ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &max_daemons_limit);
+
+ for (i = 0; i < max_daemons_limit; ++i) {
+ if (ap_scoreboard_image->parent[i].pid == pid->pid) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+AP_DECLARE(void) ap_create_sb_handle(ap_sb_handle_t **new_sbh, apr_pool_t *p,
+ int child_num, int thread_num)
+{
+ *new_sbh = (ap_sb_handle_t *)apr_palloc(p, sizeof(ap_sb_handle_t));
+ (*new_sbh)->child_num = child_num;
+ (*new_sbh)->thread_num = thread_num;
+}
+
+AP_DECLARE(int) ap_update_child_status_from_indexes(int child_num,
+ int thread_num,
+ int status,
+ request_rec *r)
+{
+ int old_status;
+ worker_score *ws;
+ process_score *ps;
+
+ if (child_num < 0) {
+ return -1;
+ }
+
+ ws = &ap_scoreboard_image->servers[child_num][thread_num];
+ old_status = ws->status;
+ ws->status = status;
+
+ ps = &ap_scoreboard_image->parent[child_num];
+
+ if (status == SERVER_READY
+ && old_status == SERVER_STARTING) {
+ ws->thread_num = child_num * thread_limit + thread_num;
+ ps->generation = ap_my_generation;
+ }
+
+ if (ap_extended_status) {
+ ws->last_used = apr_time_now();
+ if (status == SERVER_READY || status == SERVER_DEAD) {
+ /*
+ * Reset individual counters
+ */
+ if (status == SERVER_DEAD) {
+ ws->my_access_count = 0L;
+ ws->my_bytes_served = 0L;
+ }
+ ws->conn_count = 0;
+ ws->conn_bytes = 0;
+ }
+ if (r) {
+ conn_rec *c = r->connection;
+ apr_cpystrn(ws->client, ap_get_remote_host(c, r->per_dir_config,
+ REMOTE_NOLOOKUP, NULL), sizeof(ws->client));
+ if (r->the_request == NULL) {
+ apr_cpystrn(ws->request, "NULL", sizeof(ws->request));
+ } else if (r->parsed_uri.password == NULL) {
+ apr_cpystrn(ws->request, r->the_request, sizeof(ws->request));
+ } else {
+ /* Don't reveal the password in the server-status view */
+ apr_cpystrn(ws->request, apr_pstrcat(r->pool, r->method, " ",
+ apr_uri_unparse(r->pool, &r->parsed_uri,
+ APR_URI_UNP_OMITPASSWORD),
+ r->assbackwards ? NULL : " ", r->protocol, NULL),
+ sizeof(ws->request));
+ }
+ apr_cpystrn(ws->vhost, r->server->server_hostname,
+ sizeof(ws->vhost));
+ }
+ }
+
+ return old_status;
+}
+
+AP_DECLARE(int) ap_update_child_status(ap_sb_handle_t *sbh, int status,
+ request_rec *r)
+{
+ return ap_update_child_status_from_indexes(sbh->child_num, sbh->thread_num,
+ status, r);
+}
+
+void ap_time_process_request(ap_sb_handle_t *sbh, int status)
+{
+ worker_score *ws;
+
+ if (sbh->child_num < 0) {
+ return;
+ }
+
+ ws = &ap_scoreboard_image->servers[sbh->child_num][sbh->thread_num];
+
+ if (status == START_PREQUEST) {
+ ws->start_time = apr_time_now();
+ }
+ else if (status == STOP_PREQUEST) {
+ ws->stop_time = apr_time_now();
+ }
+}
+
+AP_DECLARE(worker_score *) ap_get_scoreboard_worker(int x, int y)
+{
+ if (((x < 0) || (server_limit < x)) ||
+ ((y < 0) || (thread_limit < y))) {
+ return(NULL); /* Out of range */
+ }
+ return &ap_scoreboard_image->servers[x][y];
+}
+
+AP_DECLARE(process_score *) ap_get_scoreboard_process(int x)
+{
+ if ((x < 0) || (server_limit < x)) {
+ return(NULL); /* Out of range */
+ }
+ return &ap_scoreboard_image->parent[x];
+}
+
+AP_DECLARE(global_score *) ap_get_scoreboard_global()
+{
+ return ap_scoreboard_image->global;
+}
+
+AP_DECLARE(lb_score *) ap_get_scoreboard_lb(int lb_num)
+{
+ if (((lb_num < 0) || (lb_limit < lb_num))) {
+ return(NULL); /* Out of range */
+ }
+ return &ap_scoreboard_image->balancers[lb_num];
+}
diff --git a/server/util.c b/server/util.c
new file mode 100644
index 00000000..55ed076b
--- /dev/null
+++ b/server/util.c
@@ -0,0 +1,2149 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * util.c: string utility things
+ *
+ * 3/21/93 Rob McCool
+ * 1995-96 Many changes by the Apache Software Foundation
+ *
+ */
+
+/* Debugging aid:
+ * #define DEBUG to trace all cfg_open*()/cfg_closefile() calls
+ * #define DEBUG_CFG_LINES to trace every line read from the config files
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if APR_HAVE_NETDB_H
+#include <netdb.h> /* for gethostbyname() */
+#endif
+
+#define CORE_PRIVATE
+
+#include "ap_config.h"
+#include "apr_base64.h"
+#include "httpd.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_config.h"
+#include "util_ebcdic.h"
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+/* A bunch of functions in util.c scan strings looking for certain characters.
+ * To make that more efficient we encode a lookup table. The test_char_table
+ * is generated automatically by gen_test_char.c.
+ */
+#include "test_char.h"
+
+/* we assume the folks using this ensure 0 <= c < 256... which means
+ * you need a cast to (unsigned char) first, you can't just plug a
+ * char in here and get it to work, because if char is signed then it
+ * will first be sign extended.
+ */
+#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f))
+
+/* Win32/NetWare/OS2 need to check for both forward and back slashes
+ * in ap_getparents() and ap_escape_url.
+ */
+#ifdef CASE_BLIND_FILESYSTEM
+#define IS_SLASH(s) ((s == '/') || (s == '\\'))
+#else
+#define IS_SLASH(s) (s == '/')
+#endif
+
+
+/*
+ * Examine a field value (such as a media-/content-type) string and return
+ * it sans any parameters; e.g., strip off any ';charset=foo' and the like.
+ */
+AP_DECLARE(char *) ap_field_noparam(apr_pool_t *p, const char *intype)
+{
+ const char *semi;
+
+ if (intype == NULL) return NULL;
+
+ semi = ap_strchr_c(intype, ';');
+ if (semi == NULL) {
+ return apr_pstrdup(p, intype);
+ }
+ else {
+ while ((semi > intype) && apr_isspace(semi[-1])) {
+ semi--;
+ }
+ return apr_pstrndup(p, intype, semi - intype);
+ }
+}
+
+AP_DECLARE(char *) ap_ht_time(apr_pool_t *p, apr_time_t t, const char *fmt,
+ int gmt)
+{
+ apr_size_t retcode;
+ char ts[MAX_STRING_LEN];
+ char tf[MAX_STRING_LEN];
+ apr_time_exp_t xt;
+
+ if (gmt) {
+ const char *f;
+ char *strp;
+
+ apr_time_exp_gmt(&xt, t);
+ /* Convert %Z to "GMT" and %z to "+0000";
+ * on hosts that do not have a time zone string in struct tm,
+ * strftime must assume its argument is local time.
+ */
+ for(strp = tf, f = fmt; strp < tf + sizeof(tf) - 6 && (*strp = *f)
+ ; f++, strp++) {
+ if (*f != '%') continue;
+ switch (f[1]) {
+ case '%':
+ *++strp = *++f;
+ break;
+ case 'Z':
+ *strp++ = 'G';
+ *strp++ = 'M';
+ *strp = 'T';
+ f++;
+ break;
+ case 'z': /* common extension */
+ *strp++ = '+';
+ *strp++ = '0';
+ *strp++ = '0';
+ *strp++ = '0';
+ *strp = '0';
+ f++;
+ break;
+ }
+ }
+ *strp = '\0';
+ fmt = tf;
+ }
+ else {
+ apr_time_exp_lt(&xt, t);
+ }
+
+ /* check return code? */
+ apr_strftime(ts, &retcode, MAX_STRING_LEN, fmt, &xt);
+ ts[MAX_STRING_LEN - 1] = '\0';
+ return apr_pstrdup(p, ts);
+}
+
+/* Roy owes Rob beer. */
+/* Rob owes Roy dinner. */
+
+/* These legacy comments would make a lot more sense if Roy hadn't
+ * replaced the old later_than() routine with util_date.c.
+ *
+ * Well, okay, they still wouldn't make any sense.
+ */
+
+/* Match = 0, NoMatch = 1, Abort = -1
+ * Based loosely on sections of wildmat.c by Rich Salz
+ * Hmmm... shouldn't this really go component by component?
+ */
+AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected)
+{
+ int x, y;
+
+ for (x = 0, y = 0; expected[y]; ++y, ++x) {
+ if ((!str[x]) && (expected[y] != '*'))
+ return -1;
+ if (expected[y] == '*') {
+ while (expected[++y] == '*');
+ if (!expected[y])
+ return 0;
+ while (str[x]) {
+ int ret;
+ if ((ret = ap_strcmp_match(&str[x++], &expected[y])) != 1)
+ return ret;
+ }
+ return -1;
+ }
+ else if ((expected[y] != '?') && (str[x] != expected[y]))
+ return 1;
+ }
+ return (str[x] != '\0');
+}
+
+AP_DECLARE(int) ap_strcasecmp_match(const char *str, const char *expected)
+{
+ int x, y;
+
+ for (x = 0, y = 0; expected[y]; ++y, ++x) {
+ if (!str[x] && expected[y] != '*')
+ return -1;
+ if (expected[y] == '*') {
+ while (expected[++y] == '*');
+ if (!expected[y])
+ return 0;
+ while (str[x]) {
+ int ret;
+ if ((ret = ap_strcasecmp_match(&str[x++], &expected[y])) != 1)
+ return ret;
+ }
+ return -1;
+ }
+ else if (expected[y] != '?'
+ && apr_tolower(str[x]) != apr_tolower(expected[y]))
+ return 1;
+ }
+ return (str[x] != '\0');
+}
+
+/* We actually compare the canonical root to this root, (but we don't
+ * waste time checking the case), since every use of this function in
+ * httpd-2.1 tests if the path is 'proper', meaning we've already passed
+ * it through apr_filepath_merge, or we haven't.
+ */
+AP_DECLARE(int) ap_os_is_path_absolute(apr_pool_t *p, const char *dir)
+{
+ const char *newpath;
+ const char *ourdir = dir;
+ if (apr_filepath_root(&newpath, &dir, 0, p) != APR_SUCCESS
+ || strncmp(newpath, ourdir, strlen(newpath)) != 0) {
+ return 0;
+ }
+ return 1;
+}
+
+AP_DECLARE(int) ap_is_matchexp(const char *str)
+{
+ register int x;
+
+ for (x = 0; str[x]; x++)
+ if ((str[x] == '*') || (str[x] == '?'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Here's a pool-based interface to the POSIX-esque ap_regcomp().
+ * Note that we return ap_regex_t instead of being passed one.
+ * The reason is that if you use an already-used ap_regex_t structure,
+ * the memory that you've already allocated gets forgotten, and
+ * regfree() doesn't clear it. So we don't allow it.
+ */
+
+static apr_status_t regex_cleanup(void *preg)
+{
+ ap_regfree((ap_regex_t *) preg);
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(ap_regex_t *) ap_pregcomp(apr_pool_t *p, const char *pattern,
+ int cflags)
+{
+ ap_regex_t *preg = apr_palloc(p, sizeof *preg);
+
+ if (ap_regcomp(preg, pattern, cflags)) {
+ return NULL;
+ }
+
+ apr_pool_cleanup_register(p, (void *) preg, regex_cleanup,
+ apr_pool_cleanup_null);
+
+ return preg;
+}
+
+AP_DECLARE(void) ap_pregfree(apr_pool_t *p, ap_regex_t *reg)
+{
+ ap_regfree(reg);
+ apr_pool_cleanup_kill(p, (void *) reg, regex_cleanup);
+}
+
+/*
+ * Similar to standard strstr() but we ignore case in this version.
+ * Based on the strstr() implementation further below.
+ */
+AP_DECLARE(char *) ap_strcasestr(const char *s1, const char *s2)
+{
+ char *p1, *p2;
+ if (*s2 == '\0') {
+ /* an empty s2 */
+ return((char *)s1);
+ }
+ while(1) {
+ for ( ; (*s1 != '\0') && (apr_tolower(*s1) != apr_tolower(*s2)); s1++);
+ if (*s1 == '\0') {
+ return(NULL);
+ }
+ /* found first character of s2, see if the rest matches */
+ p1 = (char *)s1;
+ p2 = (char *)s2;
+ for (++p1, ++p2; apr_tolower(*p1) == apr_tolower(*p2); ++p1, ++p2) {
+ if (*p1 == '\0') {
+ /* both strings ended together */
+ return((char *)s1);
+ }
+ }
+ if (*p2 == '\0') {
+ /* second string ended, a match */
+ break;
+ }
+ /* didn't find a match here, try starting at next character in s1 */
+ s1++;
+ }
+ return((char *)s1);
+}
+
+/*
+ * Returns an offsetted pointer in bigstring immediately after
+ * prefix. Returns bigstring if bigstring doesn't start with
+ * prefix or if prefix is longer than bigstring while still matching.
+ * NOTE: pointer returned is relative to bigstring, so we
+ * can use standard pointer comparisons in the calling function
+ * (eg: test if ap_stripprefix(a,b) == a)
+ */
+AP_DECLARE(const char *) ap_stripprefix(const char *bigstring,
+ const char *prefix)
+{
+ const char *p1;
+
+ if (*prefix == '\0')
+ return bigstring;
+
+ p1 = bigstring;
+ while (*p1 && *prefix) {
+ if (*p1++ != *prefix++)
+ return bigstring;
+ }
+ if (*prefix == '\0')
+ return p1;
+
+ /* hit the end of bigstring! */
+ return bigstring;
+}
+
+/* This function substitutes for $0-$9, filling in regular expression
+ * submatches. Pass it the same nmatch and pmatch arguments that you
+ * passed ap_regexec(). pmatch should not be greater than the maximum number
+ * of subexpressions - i.e. one more than the re_nsub member of ap_regex_t.
+ *
+ * input should be the string with the $-expressions, source should be the
+ * string that was matched against.
+ *
+ * It returns the substituted string, or NULL on error.
+ *
+ * Parts of this code are based on Henry Spencer's regsub(), from his
+ * AT&T V8 regexp package.
+ */
+
+AP_DECLARE(char *) ap_pregsub(apr_pool_t *p, const char *input,
+ const char *source, size_t nmatch,
+ ap_regmatch_t pmatch[])
+{
+ const char *src = input;
+ char *dest, *dst;
+ char c;
+ size_t no;
+ int len;
+
+ if (!source)
+ return NULL;
+ if (!nmatch)
+ return apr_pstrdup(p, src);
+
+ /* First pass, find the size */
+
+ len = 0;
+
+ while ((c = *src++) != '\0') {
+ if (c == '&')
+ no = 0;
+ else if (c == '$' && apr_isdigit(*src))
+ no = *src++ - '0';
+ else
+ no = 10;
+
+ if (no > 9) { /* Ordinary character. */
+ if (c == '\\' && (*src == '$' || *src == '&'))
+ c = *src++;
+ len++;
+ }
+ else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
+ len += pmatch[no].rm_eo - pmatch[no].rm_so;
+ }
+
+ }
+
+ dest = dst = apr_pcalloc(p, len + 1);
+
+ /* Now actually fill in the string */
+
+ src = input;
+
+ while ((c = *src++) != '\0') {
+ if (c == '&')
+ no = 0;
+ else if (c == '$' && apr_isdigit(*src))
+ no = *src++ - '0';
+ else
+ no = 10;
+
+ if (no > 9) { /* Ordinary character. */
+ if (c == '\\' && (*src == '$' || *src == '&'))
+ c = *src++;
+ *dst++ = c;
+ }
+ else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
+ len = pmatch[no].rm_eo - pmatch[no].rm_so;
+ memcpy(dst, source + pmatch[no].rm_so, len);
+ dst += len;
+ }
+
+ }
+ *dst = '\0';
+
+ return dest;
+}
+
+/*
+ * Parse .. so we don't compromise security
+ */
+AP_DECLARE(void) ap_getparents(char *name)
+{
+ char *next;
+ int l, w, first_dot;
+
+ /* Four paseses, as per RFC 1808 */
+ /* a) remove ./ path segments */
+ for (next = name; *next && (*next != '.'); next++) {
+ }
+
+ l = w = first_dot = next - name;
+ while (name[l] != '\0') {
+ if (name[l] == '.' && IS_SLASH(name[l + 1])
+ && (l == 0 || IS_SLASH(name[l - 1])))
+ l += 2;
+ else
+ name[w++] = name[l++];
+ }
+
+ /* b) remove trailing . path, segment */
+ if (w == 1 && name[0] == '.')
+ w--;
+ else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2]))
+ w--;
+ name[w] = '\0';
+
+ /* c) remove all xx/../ segments. (including leading ../ and /../) */
+ l = first_dot;
+
+ while (name[l] != '\0') {
+ if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2])
+ && (l == 0 || IS_SLASH(name[l - 1]))) {
+ register int m = l + 3, n;
+
+ l = l - 2;
+ if (l >= 0) {
+ while (l >= 0 && !IS_SLASH(name[l]))
+ l--;
+ l++;
+ }
+ else
+ l = 0;
+ n = l;
+ while ((name[n] = name[m]))
+ (++n, ++m);
+ }
+ else
+ ++l;
+ }
+
+ /* d) remove trailing xx/.. segment. */
+ if (l == 2 && name[0] == '.' && name[1] == '.')
+ name[0] = '\0';
+ else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.'
+ && IS_SLASH(name[l - 3])) {
+ l = l - 4;
+ if (l >= 0) {
+ while (l >= 0 && !IS_SLASH(name[l]))
+ l--;
+ l++;
+ }
+ else
+ l = 0;
+ name[l] = '\0';
+ }
+}
+
+AP_DECLARE(void) ap_no2slash(char *name)
+{
+ char *d, *s;
+
+ s = d = name;
+
+#ifdef HAVE_UNC_PATHS
+ /* Check for UNC names. Leave leading two slashes. */
+ if (s[0] == '/' && s[1] == '/')
+ *d++ = *s++;
+#endif
+
+ while (*s) {
+ if ((*d++ = *s) == '/') {
+ do {
+ ++s;
+ } while (*s == '/');
+ }
+ else {
+ ++s;
+ }
+ }
+ *d = '\0';
+}
+
+
+/*
+ * copy at most n leading directories of s into d
+ * d should be at least as large as s plus 1 extra byte
+ * assumes n > 0
+ * the return value is the ever useful pointer to the trailing \0 of d
+ *
+ * MODIFIED FOR HAVE_DRIVE_LETTERS and NETWARE environments,
+ * so that if n == 0, "/" is returned in d with n == 1
+ * and s == "e:/test.html", "e:/" is returned in d
+ * *** See also directory_walk in modules/http/http_request.c
+
+ * examples:
+ * /a/b, 0 ==> / (true for all platforms)
+ * /a/b, 1 ==> /
+ * /a/b, 2 ==> /a/
+ * /a/b, 3 ==> /a/b/
+ * /a/b, 4 ==> /a/b/
+ *
+ * c:/a/b 0 ==> /
+ * c:/a/b 1 ==> c:/
+ * c:/a/b 2 ==> c:/a/
+ * c:/a/b 3 ==> c:/a/b
+ * c:/a/b 4 ==> c:/a/b
+ */
+AP_DECLARE(char *) ap_make_dirstr_prefix(char *d, const char *s, int n)
+{
+ if (n < 1) {
+ *d = '/';
+ *++d = '\0';
+ return (d);
+ }
+
+ for (;;) {
+ if (*s == '\0' || (*s == '/' && (--n) == 0)) {
+ *d = '/';
+ break;
+ }
+ *d++ = *s++;
+ }
+ *++d = 0;
+ return (d);
+}
+
+
+/*
+ * return the parent directory name including trailing / of the file s
+ */
+AP_DECLARE(char *) ap_make_dirstr_parent(apr_pool_t *p, const char *s)
+{
+ const char *last_slash = ap_strrchr_c(s, '/');
+ char *d;
+ int l;
+
+ if (last_slash == NULL) {
+ return apr_pstrdup(p, "");
+ }
+ l = (last_slash - s) + 1;
+ d = apr_palloc(p, l + 1);
+ memcpy(d, s, l);
+ d[l] = 0;
+ return (d);
+}
+
+
+AP_DECLARE(int) ap_count_dirs(const char *path)
+{
+ register int x, n;
+
+ for (x = 0, n = 0; path[x]; x++)
+ if (path[x] == '/')
+ n++;
+ return n;
+}
+
+AP_DECLARE(char *) ap_getword_nc(apr_pool_t *atrans, char **line, char stop)
+{
+ return ap_getword(atrans, (const char **) line, stop);
+}
+
+AP_DECLARE(char *) ap_getword(apr_pool_t *atrans, const char **line, char stop)
+{
+ const char *pos = *line;
+ int len;
+ char *res;
+
+ while ((*pos != stop) && *pos) {
+ ++pos;
+ }
+
+ len = pos - *line;
+ res = (char *)apr_palloc(atrans, len + 1);
+ memcpy(res, *line, len);
+ res[len] = 0;
+
+ if (stop) {
+ while (*pos == stop) {
+ ++pos;
+ }
+ }
+ *line = pos;
+
+ return res;
+}
+
+AP_DECLARE(char *) ap_getword_white_nc(apr_pool_t *atrans, char **line)
+{
+ return ap_getword_white(atrans, (const char **) line);
+}
+
+AP_DECLARE(char *) ap_getword_white(apr_pool_t *atrans, const char **line)
+{
+ const char *pos = *line;
+ int len;
+ char *res;
+
+ while (!apr_isspace(*pos) && *pos) {
+ ++pos;
+ }
+
+ len = pos - *line;
+ res = (char *)apr_palloc(atrans, len + 1);
+ memcpy(res, *line, len);
+ res[len] = 0;
+
+ while (apr_isspace(*pos)) {
+ ++pos;
+ }
+
+ *line = pos;
+
+ return res;
+}
+
+AP_DECLARE(char *) ap_getword_nulls_nc(apr_pool_t *atrans, char **line,
+ char stop)
+{
+ return ap_getword_nulls(atrans, (const char **) line, stop);
+}
+
+AP_DECLARE(char *) ap_getword_nulls(apr_pool_t *atrans, const char **line,
+ char stop)
+{
+ const char *pos = ap_strchr_c(*line, stop);
+ char *res;
+
+ if (!pos) {
+ res = apr_pstrdup(atrans, *line);
+ *line += strlen(*line);
+ return res;
+ }
+
+ res = apr_pstrndup(atrans, *line, pos - *line);
+
+ ++pos;
+
+ *line = pos;
+
+ return res;
+}
+
+/* Get a word, (new) config-file style --- quoted strings and backslashes
+ * all honored
+ */
+
+static char *substring_conf(apr_pool_t *p, const char *start, int len,
+ char quote)
+{
+ char *result = apr_palloc(p, len + 2);
+ char *resp = result;
+ int i;
+
+ for (i = 0; i < len; ++i) {
+ if (start[i] == '\\' && (start[i + 1] == '\\'
+ || (quote && start[i + 1] == quote)))
+ *resp++ = start[++i];
+ else
+ *resp++ = start[i];
+ }
+
+ *resp++ = '\0';
+#if RESOLVE_ENV_PER_TOKEN
+ return (char *)ap_resolve_env(p,result);
+#else
+ return result;
+#endif
+}
+
+AP_DECLARE(char *) ap_getword_conf_nc(apr_pool_t *p, char **line)
+{
+ return ap_getword_conf(p, (const char **) line);
+}
+
+AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line)
+{
+ const char *str = *line, *strend;
+ char *res;
+ char quote;
+
+ while (*str && apr_isspace(*str))
+ ++str;
+
+ if (!*str) {
+ *line = str;
+ return "";
+ }
+
+ if ((quote = *str) == '"' || quote == '\'') {
+ strend = str + 1;
+ while (*strend && *strend != quote) {
+ if (*strend == '\\' && strend[1] &&
+ (strend[1] == quote || strend[1] == '\\')) {
+ strend += 2;
+ }
+ else {
+ ++strend;
+ }
+ }
+ res = substring_conf(p, str + 1, strend - str - 1, quote);
+
+ if (*strend == quote)
+ ++strend;
+ }
+ else {
+ strend = str;
+ while (*strend && !apr_isspace(*strend))
+ ++strend;
+
+ res = substring_conf(p, str, strend - str, 0);
+ }
+
+ while (*strend && apr_isspace(*strend))
+ ++strend;
+ *line = strend;
+ return res;
+}
+
+/* Check a string for any ${ENV} environment variable
+ * construct and replace each them by the value of
+ * that environment variable, if it exists. If the
+ * environment value does not exist, leave the ${ENV}
+ * construct alone; it means something else.
+ */
+AP_DECLARE(const char *) ap_resolve_env(apr_pool_t *p, const char * word)
+{
+# define SMALL_EXPANSION 5
+ struct sll {
+ struct sll *next;
+ const char *string;
+ apr_size_t len;
+ } *result, *current, sresult[SMALL_EXPANSION];
+ char *res_buf, *cp;
+ const char *s, *e, *ep;
+ unsigned spc;
+ apr_size_t outlen;
+
+ s = ap_strchr_c(word, '$');
+ if (!s) {
+ return word;
+ }
+
+ /* well, actually something to do */
+ ep = word + strlen(word);
+ spc = 0;
+ result = current = &(sresult[spc++]);
+ current->next = NULL;
+ current->string = word;
+ current->len = s - word;
+ outlen = current->len;
+
+ do {
+ /* prepare next entry */
+ if (current->len) {
+ current->next = (spc < SMALL_EXPANSION)
+ ? &(sresult[spc++])
+ : (struct sll *)apr_palloc(p,
+ sizeof(*current->next));
+ current = current->next;
+ current->next = NULL;
+ current->len = 0;
+ }
+
+ if (*s == '$') {
+ if (s[1] == '{' && (e = ap_strchr_c(s, '}'))) {
+ word = getenv(apr_pstrndup(p, s+2, e-s-2));
+ if (word) {
+ current->string = word;
+ current->len = strlen(word);
+ outlen += current->len;
+ }
+ else {
+ current->string = s;
+ current->len = e - s + 1;
+ outlen += current->len;
+ }
+ s = e + 1;
+ }
+ else {
+ current->string = s++;
+ current->len = 1;
+ ++outlen;
+ }
+ }
+ else {
+ word = s;
+ s = ap_strchr_c(s, '$');
+ current->string = word;
+ current->len = s ? s - word : ep - word;
+ outlen += current->len;
+ }
+ } while (s && *s);
+
+ /* assemble result */
+ res_buf = cp = apr_palloc(p, outlen + 1);
+ do {
+ if (result->len) {
+ memcpy(cp, result->string, result->len);
+ cp += result->len;
+ }
+ result = result->next;
+ } while (result);
+ res_buf[outlen] = '\0';
+
+ return res_buf;
+}
+
+AP_DECLARE(int) ap_cfg_closefile(ap_configfile_t *cfp)
+{
+#ifdef DEBUG
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "Done with config file %s", cfp->name);
+#endif
+ return (cfp->close == NULL) ? 0 : cfp->close(cfp->param);
+}
+
+static apr_status_t cfg_close(void *param)
+{
+ apr_file_t *cfp = (apr_file_t *) param;
+ return (apr_file_close(cfp));
+}
+
+static int cfg_getch(void *param)
+{
+ char ch;
+ apr_file_t *cfp = (apr_file_t *) param;
+ if (apr_file_getc(&ch, cfp) == APR_SUCCESS)
+ return ch;
+ return (int)EOF;
+}
+
+static void *cfg_getstr(void *buf, size_t bufsiz, void *param)
+{
+ apr_file_t *cfp = (apr_file_t *) param;
+ apr_status_t rv;
+ rv = apr_file_gets(buf, bufsiz, cfp);
+ if (rv == APR_SUCCESS) {
+ return buf;
+ }
+ return NULL;
+}
+
+/* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */
+AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg,
+ apr_pool_t *p, const char *name)
+{
+ ap_configfile_t *new_cfg;
+ apr_file_t *file = NULL;
+ apr_finfo_t finfo;
+ apr_status_t status;
+#ifdef DEBUG
+ char buf[120];
+#endif
+
+ if (name == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "Internal error: pcfg_openfile() called with NULL filename");
+ return APR_EBADF;
+ }
+
+ status = apr_file_open(&file, name, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, p);
+#ifdef DEBUG
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "Opening config file %s (%s)",
+ name, (status != APR_SUCCESS) ?
+ apr_strerror(status, buf, sizeof(buf)) : "successful");
+#endif
+ if (status != APR_SUCCESS)
+ return status;
+
+ status = apr_file_info_get(&finfo, APR_FINFO_TYPE, file);
+ if (status != APR_SUCCESS)
+ return status;
+
+ if (finfo.filetype != APR_REG &&
+#if defined(WIN32) || defined(OS2) || defined(NETWARE)
+ strcasecmp(apr_filepath_name_get(name), "nul") != 0) {
+#else
+ strcmp(name, "/dev/null") != 0) {
+#endif /* WIN32 || OS2 */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "Access to file %s denied by server: not a regular file",
+ name);
+ apr_file_close(file);
+ return APR_EBADF;
+ }
+
+#ifdef WIN32
+ /* Some twisted character [no pun intended] at MS decided that a
+ * zero width joiner as the lead wide character would be ideal for
+ * describing Unicode text files. This was further convoluted to
+ * another MSism that the same character mapped into utf-8, EF BB BF
+ * would signify utf-8 text files.
+ *
+ * Since MS configuration files are all protecting utf-8 encoded
+ * Unicode path, file and resource names, we already have the correct
+ * WinNT encoding. But at least eat the stupid three bytes up front.
+ */
+ {
+ unsigned char buf[4];
+ apr_size_t len = 3;
+ status = apr_file_read(file, buf, &len);
+ if ((status != APR_SUCCESS) || (len < 3)
+ || memcmp(buf, "\xEF\xBB\xBF", 3) != 0) {
+ apr_off_t zero = 0;
+ apr_file_seek(file, APR_SET, &zero);
+ }
+ }
+#endif
+
+ new_cfg = apr_palloc(p, sizeof(*new_cfg));
+ new_cfg->param = file;
+ new_cfg->name = apr_pstrdup(p, name);
+ new_cfg->getch = (int (*)(void *)) cfg_getch;
+ new_cfg->getstr = (void *(*)(void *, size_t, void *)) cfg_getstr;
+ new_cfg->close = (int (*)(void *)) cfg_close;
+ new_cfg->line_number = 0;
+ *ret_cfg = new_cfg;
+ return APR_SUCCESS;
+}
+
+
+/* Allocate a ap_configfile_t handle with user defined functions and params */
+AP_DECLARE(ap_configfile_t *) ap_pcfg_open_custom(apr_pool_t *p,
+ const char *descr,
+ void *param,
+ int(*getch)(void *param),
+ void *(*getstr) (void *buf, size_t bufsiz, void *param),
+ int(*close_func)(void *param))
+{
+ ap_configfile_t *new_cfg = apr_palloc(p, sizeof(*new_cfg));
+#ifdef DEBUG
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "Opening config handler %s", descr);
+#endif
+ new_cfg->param = param;
+ new_cfg->name = descr;
+ new_cfg->getch = getch;
+ new_cfg->getstr = getstr;
+ new_cfg->close = close_func;
+ new_cfg->line_number = 0;
+ return new_cfg;
+}
+
+/* Read one character from a configfile_t */
+AP_DECLARE(int) ap_cfg_getc(ap_configfile_t *cfp)
+{
+ register int ch = cfp->getch(cfp->param);
+ if (ch == LF)
+ ++cfp->line_number;
+ return ch;
+}
+
+/* Read one line from open ap_configfile_t, strip LF, increase line number */
+/* If custom handler does not define a getstr() function, read char by char */
+AP_DECLARE(int) ap_cfg_getline(char *buf, size_t bufsize, ap_configfile_t *cfp)
+{
+ /* If a "get string" function is defined, use it */
+ if (cfp->getstr != NULL) {
+ char *src, *dst;
+ char *cp;
+ char *cbuf = buf;
+ size_t cbufsize = bufsize;
+
+ while (1) {
+ ++cfp->line_number;
+ if (cfp->getstr(cbuf, cbufsize, cfp->param) == NULL)
+ return 1;
+
+ /*
+ * check for line continuation,
+ * i.e. match [^\\]\\[\r]\n only
+ */
+ cp = cbuf;
+ while (cp < cbuf+cbufsize && *cp != '\0')
+ cp++;
+ if (cp > cbuf && cp[-1] == LF) {
+ cp--;
+ if (cp > cbuf && cp[-1] == CR)
+ cp--;
+ if (cp > cbuf && cp[-1] == '\\') {
+ cp--;
+ if (!(cp > cbuf && cp[-1] == '\\')) {
+ /*
+ * line continuation requested -
+ * then remove backslash and continue
+ */
+ cbufsize -= (cp-cbuf);
+ cbuf = cp;
+ continue;
+ }
+ else {
+ /*
+ * no real continuation because escaped -
+ * then just remove escape character
+ */
+ for ( ; cp < cbuf+cbufsize && *cp != '\0'; cp++)
+ cp[0] = cp[1];
+ }
+ }
+ }
+ break;
+ }
+
+ /*
+ * Leading and trailing white space is eliminated completely
+ */
+ src = buf;
+ while (apr_isspace(*src))
+ ++src;
+ /* blast trailing whitespace */
+ dst = &src[strlen(src)];
+ while (--dst >= src && apr_isspace(*dst))
+ *dst = '\0';
+ /* Zap leading whitespace by shifting */
+ if (src != buf)
+ for (dst = buf; (*dst++ = *src++) != '\0'; )
+ ;
+
+#ifdef DEBUG_CFG_LINES
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "Read config: %s", buf);
+#endif
+ return 0;
+ } else {
+ /* No "get string" function defined; read character by character */
+ register int c;
+ register size_t i = 0;
+
+ buf[0] = '\0';
+ /* skip leading whitespace */
+ do {
+ c = cfp->getch(cfp->param);
+ } while (c == '\t' || c == ' ');
+
+ if (c == EOF)
+ return 1;
+
+ if(bufsize < 2) {
+ /* too small, assume caller is crazy */
+ return 1;
+ }
+
+ while (1) {
+ if ((c == '\t') || (c == ' ')) {
+ buf[i++] = ' ';
+ while ((c == '\t') || (c == ' '))
+ c = cfp->getch(cfp->param);
+ }
+ if (c == CR) {
+ /* silently ignore CR (_assume_ that a LF follows) */
+ c = cfp->getch(cfp->param);
+ }
+ if (c == LF) {
+ /* increase line number and return on LF */
+ ++cfp->line_number;
+ }
+ if (c == EOF || c == 0x4 || c == LF || i >= (bufsize - 2)) {
+ /*
+ * check for line continuation
+ */
+ if (i > 0 && buf[i-1] == '\\') {
+ i--;
+ if (!(i > 0 && buf[i-1] == '\\')) {
+ /* line is continued */
+ c = cfp->getch(cfp->param);
+ continue;
+ }
+ /* else nothing needs be done because
+ * then the backslash is escaped and
+ * we just strip to a single one
+ */
+ }
+ /* blast trailing whitespace */
+ while (i > 0 && apr_isspace(buf[i - 1]))
+ --i;
+ buf[i] = '\0';
+#ifdef DEBUG_CFG_LINES
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "Read config: %s", buf);
+#endif
+ return 0;
+ }
+ buf[i] = c;
+ ++i;
+ c = cfp->getch(cfp->param);
+ }
+ }
+}
+
+/* Size an HTTP header field list item, as separated by a comma.
+ * The return value is a pointer to the beginning of the non-empty list item
+ * within the original string (or NULL if there is none) and the address
+ * of field is shifted to the next non-comma, non-whitespace character.
+ * len is the length of the item excluding any beginning whitespace.
+ */
+AP_DECLARE(const char *) ap_size_list_item(const char **field, int *len)
+{
+ const unsigned char *ptr = (const unsigned char *)*field;
+ const unsigned char *token;
+ int in_qpair, in_qstr, in_com;
+
+ /* Find first non-comma, non-whitespace byte */
+
+ while (*ptr == ',' || apr_isspace(*ptr))
+ ++ptr;
+
+ token = ptr;
+
+ /* Find the end of this item, skipping over dead bits */
+
+ for (in_qpair = in_qstr = in_com = 0;
+ *ptr && (in_qpair || in_qstr || in_com || *ptr != ',');
+ ++ptr) {
+
+ if (in_qpair) {
+ in_qpair = 0;
+ }
+ else {
+ switch (*ptr) {
+ case '\\': in_qpair = 1; /* quoted-pair */
+ break;
+ case '"' : if (!in_com) /* quoted string delim */
+ in_qstr = !in_qstr;
+ break;
+ case '(' : if (!in_qstr) /* comment (may nest) */
+ ++in_com;
+ break;
+ case ')' : if (in_com) /* end comment */
+ --in_com;
+ break;
+ default : break;
+ }
+ }
+ }
+
+ if ((*len = (ptr - token)) == 0) {
+ *field = (const char *)ptr;
+ return NULL;
+ }
+
+ /* Advance field pointer to the next non-comma, non-white byte */
+
+ while (*ptr == ',' || apr_isspace(*ptr))
+ ++ptr;
+
+ *field = (const char *)ptr;
+ return (const char *)token;
+}
+
+/* Retrieve an HTTP header field list item, as separated by a comma,
+ * while stripping insignificant whitespace and lowercasing anything not in
+ * a quoted string or comment. The return value is a new string containing
+ * the converted list item (or NULL if none) and the address pointed to by
+ * field is shifted to the next non-comma, non-whitespace.
+ */
+AP_DECLARE(char *) ap_get_list_item(apr_pool_t *p, const char **field)
+{
+ const char *tok_start;
+ const unsigned char *ptr;
+ unsigned char *pos;
+ char *token;
+ int addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0, tok_len = 0;
+
+ /* Find the beginning and maximum length of the list item so that
+ * we can allocate a buffer for the new string and reset the field.
+ */
+ if ((tok_start = ap_size_list_item(field, &tok_len)) == NULL) {
+ return NULL;
+ }
+ token = apr_palloc(p, tok_len + 1);
+
+ /* Scan the token again, but this time copy only the good bytes.
+ * We skip extra whitespace and any whitespace around a '=', '/',
+ * or ';' and lowercase normal characters not within a comment,
+ * quoted-string or quoted-pair.
+ */
+ for (ptr = (const unsigned char *)tok_start, pos = (unsigned char *)token;
+ *ptr && (in_qpair || in_qstr || in_com || *ptr != ',');
+ ++ptr) {
+
+ if (in_qpair) {
+ in_qpair = 0;
+ *pos++ = *ptr;
+ }
+ else {
+ switch (*ptr) {
+ case '\\': in_qpair = 1;
+ if (addspace == 1)
+ *pos++ = ' ';
+ *pos++ = *ptr;
+ addspace = 0;
+ break;
+ case '"' : if (!in_com)
+ in_qstr = !in_qstr;
+ if (addspace == 1)
+ *pos++ = ' ';
+ *pos++ = *ptr;
+ addspace = 0;
+ break;
+ case '(' : if (!in_qstr)
+ ++in_com;
+ if (addspace == 1)
+ *pos++ = ' ';
+ *pos++ = *ptr;
+ addspace = 0;
+ break;
+ case ')' : if (in_com)
+ --in_com;
+ *pos++ = *ptr;
+ addspace = 0;
+ break;
+ case ' ' :
+ case '\t': if (addspace)
+ break;
+ if (in_com || in_qstr)
+ *pos++ = *ptr;
+ else
+ addspace = 1;
+ break;
+ case '=' :
+ case '/' :
+ case ';' : if (!(in_com || in_qstr))
+ addspace = -1;
+ *pos++ = *ptr;
+ break;
+ default : if (addspace == 1)
+ *pos++ = ' ';
+ *pos++ = (in_com || in_qstr) ? *ptr
+ : apr_tolower(*ptr);
+ addspace = 0;
+ break;
+ }
+ }
+ }
+ *pos = '\0';
+
+ return token;
+}
+
+/* Find an item in canonical form (lowercase, no extra spaces) within
+ * an HTTP field value list. Returns 1 if found, 0 if not found.
+ * This would be much more efficient if we stored header fields as
+ * an array of list items as they are received instead of a plain string.
+ */
+AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line,
+ const char *tok)
+{
+ const unsigned char *pos;
+ const unsigned char *ptr = (const unsigned char *)line;
+ int good = 0, addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0;
+
+ if (!line || !tok)
+ return 0;
+
+ do { /* loop for each item in line's list */
+
+ /* Find first non-comma, non-whitespace byte */
+
+ while (*ptr == ',' || apr_isspace(*ptr))
+ ++ptr;
+
+ if (*ptr)
+ good = 1; /* until proven otherwise for this item */
+ else
+ break; /* no items left and nothing good found */
+
+ /* We skip extra whitespace and any whitespace around a '=', '/',
+ * or ';' and lowercase normal characters not within a comment,
+ * quoted-string or quoted-pair.
+ */
+ for (pos = (const unsigned char *)tok;
+ *ptr && (in_qpair || in_qstr || in_com || *ptr != ',');
+ ++ptr) {
+
+ if (in_qpair) {
+ in_qpair = 0;
+ if (good)
+ good = (*pos++ == *ptr);
+ }
+ else {
+ switch (*ptr) {
+ case '\\': in_qpair = 1;
+ if (addspace == 1)
+ good = good && (*pos++ == ' ');
+ good = good && (*pos++ == *ptr);
+ addspace = 0;
+ break;
+ case '"' : if (!in_com)
+ in_qstr = !in_qstr;
+ if (addspace == 1)
+ good = good && (*pos++ == ' ');
+ good = good && (*pos++ == *ptr);
+ addspace = 0;
+ break;
+ case '(' : if (!in_qstr)
+ ++in_com;
+ if (addspace == 1)
+ good = good && (*pos++ == ' ');
+ good = good && (*pos++ == *ptr);
+ addspace = 0;
+ break;
+ case ')' : if (in_com)
+ --in_com;
+ good = good && (*pos++ == *ptr);
+ addspace = 0;
+ break;
+ case ' ' :
+ case '\t': if (addspace || !good)
+ break;
+ if (in_com || in_qstr)
+ good = (*pos++ == *ptr);
+ else
+ addspace = 1;
+ break;
+ case '=' :
+ case '/' :
+ case ';' : if (!(in_com || in_qstr))
+ addspace = -1;
+ good = good && (*pos++ == *ptr);
+ break;
+ default : if (!good)
+ break;
+ if (addspace == 1)
+ good = (*pos++ == ' ');
+ if (in_com || in_qstr)
+ good = good && (*pos++ == *ptr);
+ else
+ good = good && (*pos++ == apr_tolower(*ptr));
+ addspace = 0;
+ break;
+ }
+ }
+ }
+ if (good && *pos)
+ good = 0; /* not good if only a prefix was matched */
+
+ } while (*ptr && !good);
+
+ return good;
+}
+
+
+/* Retrieve a token, spacing over it and returning a pointer to
+ * the first non-white byte afterwards. Note that these tokens
+ * are delimited by semis and commas; and can also be delimited
+ * by whitespace at the caller's option.
+ */
+
+AP_DECLARE(char *) ap_get_token(apr_pool_t *p, const char **accept_line,
+ int accept_white)
+{
+ const char *ptr = *accept_line;
+ const char *tok_start;
+ char *token;
+ int tok_len;
+
+ /* Find first non-white byte */
+
+ while (*ptr && apr_isspace(*ptr))
+ ++ptr;
+
+ tok_start = ptr;
+
+ /* find token end, skipping over quoted strings.
+ * (comments are already gone).
+ */
+
+ while (*ptr && (accept_white || !apr_isspace(*ptr))
+ && *ptr != ';' && *ptr != ',') {
+ if (*ptr++ == '"')
+ while (*ptr)
+ if (*ptr++ == '"')
+ break;
+ }
+
+ tok_len = ptr - tok_start;
+ token = apr_pstrndup(p, tok_start, tok_len);
+
+ /* Advance accept_line pointer to the next non-white byte */
+
+ while (*ptr && apr_isspace(*ptr))
+ ++ptr;
+
+ *accept_line = ptr;
+ return token;
+}
+
+
+/* find http tokens, see the definition of token from RFC2068 */
+AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok)
+{
+ const unsigned char *start_token;
+ const unsigned char *s;
+
+ if (!line)
+ return 0;
+
+ s = (const unsigned char *)line;
+ for (;;) {
+ /* find start of token, skip all stop characters, note NUL
+ * isn't a token stop, so we don't need to test for it
+ */
+ while (TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
+ ++s;
+ }
+ if (!*s) {
+ return 0;
+ }
+ start_token = s;
+ /* find end of the token */
+ while (*s && !TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
+ ++s;
+ }
+ if (!strncasecmp((const char *)start_token, (const char *)tok,
+ s - start_token)) {
+ return 1;
+ }
+ if (!*s) {
+ return 0;
+ }
+ }
+}
+
+
+AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line,
+ const char *tok)
+{
+ int llen, tlen, lidx;
+
+ if (!line)
+ return 0;
+
+ llen = strlen(line);
+ tlen = strlen(tok);
+ lidx = llen - tlen;
+
+ if (lidx < 0 ||
+ (lidx > 0 && !(apr_isspace(line[lidx - 1]) || line[lidx - 1] == ',')))
+ return 0;
+
+ return (strncasecmp(&line[lidx], tok, tlen) == 0);
+}
+
+AP_DECLARE(char *) ap_escape_shell_cmd(apr_pool_t *p, const char *str)
+{
+ char *cmd;
+ unsigned char *d;
+ const unsigned char *s;
+
+ cmd = apr_palloc(p, 2 * strlen(str) + 1); /* Be safe */
+ d = (unsigned char *)cmd;
+ s = (const unsigned char *)str;
+ for (; *s; ++s) {
+
+#if defined(OS2) || defined(WIN32)
+ /*
+ * Newlines to Win32/OS2 CreateProcess() are ill advised.
+ * Convert them to spaces since they are effectively white
+ * space to most applications
+ */
+ if (*s == '\r' || *s == '\n') {
+ *d++ = ' ';
+ continue;
+ }
+#endif
+
+ if (TEST_CHAR(*s, T_ESCAPE_SHELL_CMD)) {
+ *d++ = '\\';
+ }
+ *d++ = *s;
+ }
+ *d = '\0';
+
+ return cmd;
+}
+
+static char x2c(const char *what)
+{
+ register char digit;
+
+#if !APR_CHARSET_EBCDIC
+ digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10
+ : (what[0] - '0'));
+ digit *= 16;
+ digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10
+ : (what[1] - '0'));
+#else /*APR_CHARSET_EBCDIC*/
+ char xstr[5];
+ xstr[0]='0';
+ xstr[1]='x';
+ xstr[2]=what[0];
+ xstr[3]=what[1];
+ xstr[4]='\0';
+ digit = apr_xlate_conv_byte(ap_hdrs_from_ascii,
+ 0xFF & strtol(xstr, NULL, 16));
+#endif /*APR_CHARSET_EBCDIC*/
+ return (digit);
+}
+
+/*
+ * Unescapes a URL.
+ * Returns 0 on success, non-zero on error
+ * Failure is due to
+ * bad % escape returns HTTP_BAD_REQUEST
+ *
+ * decoding %00 -> \0 (the null character)
+ * decoding %2f -> / (a special character)
+ * returns HTTP_NOT_FOUND
+ */
+AP_DECLARE(int) ap_unescape_url(char *url)
+{
+ register int badesc, badpath;
+ char *x, *y;
+
+ badesc = 0;
+ badpath = 0;
+ /* Initial scan for first '%'. Don't bother writing values before
+ * seeing a '%' */
+ y = strchr(url, '%');
+ if (y == NULL) {
+ return OK;
+ }
+ for (x = y; *y; ++x, ++y) {
+ if (*y != '%')
+ *x = *y;
+ else {
+ if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) {
+ badesc = 1;
+ *x = '%';
+ }
+ else {
+ *x = x2c(y + 1);
+ y += 2;
+ if (IS_SLASH(*x) || *x == '\0')
+ badpath = 1;
+ }
+ }
+ }
+ *x = '\0';
+ if (badesc)
+ return HTTP_BAD_REQUEST;
+ else if (badpath)
+ return HTTP_NOT_FOUND;
+ else
+ return OK;
+}
+
+AP_DECLARE(int) ap_unescape_url_keep2f(char *url)
+{
+ register int badesc, badpath;
+ char *x, *y;
+
+ badesc = 0;
+ badpath = 0;
+ /* Initial scan for first '%'. Don't bother writing values before
+ * seeing a '%' */
+ y = strchr(url, '%');
+ if (y == NULL) {
+ return OK;
+ }
+ for (x = y; *y; ++x, ++y) {
+ if (*y != '%') {
+ *x = *y;
+ }
+ else {
+ if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) {
+ badesc = 1;
+ *x = '%';
+ }
+ else {
+ char decoded;
+ decoded = x2c(y + 1);
+ if (decoded == '\0') {
+ badpath = 1;
+ }
+ else {
+ *x = decoded;
+ y += 2;
+ }
+ }
+ }
+ }
+ *x = '\0';
+ if (badesc) {
+ return HTTP_BAD_REQUEST;
+ }
+ else if (badpath) {
+ return HTTP_NOT_FOUND;
+ }
+ else {
+ return OK;
+ }
+}
+
+AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname,
+ apr_port_t port, const request_rec *r)
+{
+ if (ap_is_default_port(port, r)) {
+ return apr_pstrdup(p, hostname);
+ }
+ else {
+ return apr_psprintf(p, "%s:%u", hostname, port);
+ }
+}
+
+/* c2x takes an unsigned, and expects the caller has guaranteed that
+ * 0 <= what < 256... which usually means that you have to cast to
+ * unsigned char first, because (unsigned)(char)(x) first goes through
+ * signed extension to an int before the unsigned cast.
+ *
+ * The reason for this assumption is to assist gcc code generation --
+ * the unsigned char -> unsigned extension is already done earlier in
+ * both uses of this code, so there's no need to waste time doing it
+ * again.
+ */
+static const char c2x_table[] = "0123456789abcdef";
+
+static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
+ unsigned char *where)
+{
+#if APR_CHARSET_EBCDIC
+ what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
+#endif /*APR_CHARSET_EBCDIC*/
+ *where++ = prefix;
+ *where++ = c2x_table[what >> 4];
+ *where++ = c2x_table[what & 0xf];
+ return where;
+}
+
+/*
+ * escape_path_segment() escapes a path segment, as defined in RFC 1808. This
+ * routine is (should be) OS independent.
+ *
+ * os_escape_path() converts an OS path to a URL, in an OS dependent way. In all
+ * cases if a ':' occurs before the first '/' in the URL, the URL should be
+ * prefixed with "./" (or the ':' escaped). In the case of Unix, this means
+ * leaving '/' alone, but otherwise doing what escape_path_segment() does. For
+ * efficiency reasons, we don't use escape_path_segment(), which is provided for
+ * reference. Again, RFC 1808 is where this stuff is defined.
+ *
+ * If partial is set, os_escape_path() assumes that the path will be appended to
+ * something with a '/' in it (and thus does not prefix "./").
+ */
+
+AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment)
+{
+ char *copy = apr_palloc(p, 3 * strlen(segment) + 1);
+ const unsigned char *s = (const unsigned char *)segment;
+ unsigned char *d = (unsigned char *)copy;
+ unsigned c;
+
+ while ((c = *s)) {
+ if (TEST_CHAR(c, T_ESCAPE_PATH_SEGMENT)) {
+ d = c2x(c, '%', d);
+ }
+ else {
+ *d++ = c;
+ }
+ ++s;
+ }
+ *d = '\0';
+ return copy;
+}
+
+AP_DECLARE(char *) ap_os_escape_path(apr_pool_t *p, const char *path, int partial)
+{
+ char *copy = apr_palloc(p, 3 * strlen(path) + 3);
+ const unsigned char *s = (const unsigned char *)path;
+ unsigned char *d = (unsigned char *)copy;
+ unsigned c;
+
+ if (!partial) {
+ const char *colon = ap_strchr_c(path, ':');
+ const char *slash = ap_strchr_c(path, '/');
+
+ if (colon && (!slash || colon < slash)) {
+ *d++ = '.';
+ *d++ = '/';
+ }
+ }
+ while ((c = *s)) {
+ if (TEST_CHAR(c, T_OS_ESCAPE_PATH)) {
+ d = c2x(c, '%', d);
+ }
+ else {
+ *d++ = c;
+ }
+ ++s;
+ }
+ *d = '\0';
+ return copy;
+}
+
+/* ap_escape_uri is now a macro for os_escape_path */
+
+AP_DECLARE(char *) ap_escape_html(apr_pool_t *p, const char *s)
+{
+ int i, j;
+ char *x;
+
+ /* first, count the number of extra characters */
+ for (i = 0, j = 0; s[i] != '\0'; i++)
+ if (s[i] == '<' || s[i] == '>')
+ j += 3;
+ else if (s[i] == '&')
+ j += 4;
+ else if (s[i] == '"')
+ j += 5;
+
+ if (j == 0)
+ return apr_pstrmemdup(p, s, i);
+
+ x = apr_palloc(p, i + j + 1);
+ for (i = 0, j = 0; s[i] != '\0'; i++, j++)
+ if (s[i] == '<') {
+ memcpy(&x[j], "&lt;", 4);
+ j += 3;
+ }
+ else if (s[i] == '>') {
+ memcpy(&x[j], "&gt;", 4);
+ j += 3;
+ }
+ else if (s[i] == '&') {
+ memcpy(&x[j], "&amp;", 5);
+ j += 4;
+ }
+ else if (s[i] == '"') {
+ memcpy(&x[j], "&quot;", 6);
+ j += 5;
+ }
+ else
+ x[j] = s[i];
+
+ x[j] = '\0';
+ return x;
+}
+
+AP_DECLARE(char *) ap_escape_logitem(apr_pool_t *p, const char *str)
+{
+ char *ret;
+ unsigned char *d;
+ const unsigned char *s;
+
+ if (!str) {
+ return NULL;
+ }
+
+ ret = apr_palloc(p, 4 * strlen(str) + 1); /* Be safe */
+ d = (unsigned char *)ret;
+ s = (const unsigned char *)str;
+ for (; *s; ++s) {
+
+ if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) {
+ *d++ = '\\';
+ switch(*s) {
+ case '\b':
+ *d++ = 'b';
+ break;
+ case '\n':
+ *d++ = 'n';
+ break;
+ case '\r':
+ *d++ = 'r';
+ break;
+ case '\t':
+ *d++ = 't';
+ break;
+ case '\v':
+ *d++ = 'v';
+ break;
+ case '\\':
+ case '"':
+ *d++ = *s;
+ break;
+ default:
+ c2x(*s, 'x', d);
+ d += 3;
+ }
+ }
+ else {
+ *d++ = *s;
+ }
+ }
+ *d = '\0';
+
+ return ret;
+}
+
+AP_DECLARE(apr_size_t) ap_escape_errorlog_item(char *dest, const char *source,
+ apr_size_t buflen)
+{
+ unsigned char *d, *ep;
+ const unsigned char *s;
+
+ if (!source || !buflen) { /* be safe */
+ return 0;
+ }
+
+ d = (unsigned char *)dest;
+ s = (const unsigned char *)source;
+ ep = d + buflen - 1;
+
+ for (; d < ep && *s; ++s) {
+
+ if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) {
+ *d++ = '\\';
+ if (d >= ep) {
+ --d;
+ break;
+ }
+
+ switch(*s) {
+ case '\b':
+ *d++ = 'b';
+ break;
+ case '\n':
+ *d++ = 'n';
+ break;
+ case '\r':
+ *d++ = 'r';
+ break;
+ case '\t':
+ *d++ = 't';
+ break;
+ case '\v':
+ *d++ = 'v';
+ break;
+ case '\\':
+ *d++ = *s;
+ break;
+ case '"': /* no need for this in error log */
+ d[-1] = *s;
+ break;
+ default:
+ if (d >= ep - 2) {
+ ep = --d; /* break the for loop as well */
+ break;
+ }
+ c2x(*s, 'x', d);
+ d += 3;
+ }
+ }
+ else {
+ *d++ = *s;
+ }
+ }
+ *d = '\0';
+
+ return (d - (unsigned char *)dest);
+}
+
+AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path)
+{
+ apr_finfo_t finfo;
+
+ if (apr_stat(&finfo, path, APR_FINFO_TYPE, p) != APR_SUCCESS)
+ return 0; /* in error condition, just return no */
+
+ return (finfo.filetype == APR_DIR);
+}
+
+AP_DECLARE(int) ap_is_rdirectory(apr_pool_t *p, const char *path)
+{
+ apr_finfo_t finfo;
+
+ if (apr_stat(&finfo, path, APR_FINFO_LINK | APR_FINFO_TYPE, p) != APR_SUCCESS)
+ return 0; /* in error condition, just return no */
+
+ return (finfo.filetype == APR_DIR);
+}
+
+AP_DECLARE(char *) ap_make_full_path(apr_pool_t *a, const char *src1,
+ const char *src2)
+{
+ apr_size_t len1, len2;
+ char *path;
+
+ len1 = strlen(src1);
+ len2 = strlen(src2);
+ /* allocate +3 for '/' delimiter, trailing NULL and overallocate
+ * one extra byte to allow the caller to add a trailing '/'
+ */
+ path = (char *)apr_palloc(a, len1 + len2 + 3);
+ if (len1 == 0) {
+ *path = '/';
+ memcpy(path + 1, src2, len2 + 1);
+ }
+ else {
+ char *next;
+ memcpy(path, src1, len1);
+ next = path + len1;
+ if (next[-1] != '/') {
+ *next++ = '/';
+ }
+ memcpy(next, src2, len2 + 1);
+ }
+ return path;
+}
+
+/*
+ * Check for an absoluteURI syntax (see section 3.2 in RFC2068).
+ */
+AP_DECLARE(int) ap_is_url(const char *u)
+{
+ register int x;
+
+ for (x = 0; u[x] != ':'; x++) {
+ if ((!u[x]) ||
+ ((!apr_isalpha(u[x])) && (!apr_isdigit(u[x])) &&
+ (u[x] != '+') && (u[x] != '-') && (u[x] != '.'))) {
+ return 0;
+ }
+ }
+
+ return (x ? 1 : 0); /* If the first character is ':', it's broken, too */
+}
+
+AP_DECLARE(int) ap_ind(const char *s, char c)
+{
+ const char *p = ap_strchr_c(s, c);
+
+ if (p == NULL)
+ return -1;
+ return p - s;
+}
+
+AP_DECLARE(int) ap_rind(const char *s, char c)
+{
+ const char *p = ap_strrchr_c(s, c);
+
+ if (p == NULL)
+ return -1;
+ return p - s;
+}
+
+AP_DECLARE(void) ap_str_tolower(char *str)
+{
+ while (*str) {
+ *str = apr_tolower(*str);
+ ++str;
+ }
+}
+
+/*
+ * We must return a FQDN
+ */
+char *ap_get_local_host(apr_pool_t *a)
+{
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+ char str[MAXHOSTNAMELEN + 1];
+ char *server_hostname = NULL;
+ apr_sockaddr_t *sockaddr;
+ char *hostname;
+
+ if (apr_gethostname(str, sizeof(str) - 1, a) != APR_SUCCESS) {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a,
+ "%s: apr_gethostname() failed to determine ServerName",
+ ap_server_argv0);
+ } else {
+ str[sizeof(str) - 1] = '\0';
+ if (apr_sockaddr_info_get(&sockaddr, str, APR_UNSPEC, 0, 0, a) == APR_SUCCESS) {
+ if ( (apr_getnameinfo(&hostname, sockaddr, 0) == APR_SUCCESS) &&
+ (ap_strchr_c(hostname, '.')) ) {
+ server_hostname = apr_pstrdup(a, hostname);
+ return server_hostname;
+ } else if (ap_strchr_c(str, '.')) {
+ server_hostname = apr_pstrdup(a, str);
+ } else {
+ apr_sockaddr_ip_get(&hostname, sockaddr);
+ server_hostname = apr_pstrdup(a, hostname);
+ }
+ } else {
+ ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a,
+ "%s: apr_sockaddr_info_get() failed for %s",
+ ap_server_argv0, str);
+ }
+ }
+
+ if (!server_hostname)
+ server_hostname = apr_pstrdup(a, "127.0.0.1");
+
+ ap_log_perror(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, a,
+ "%s: Could not reliably determine the server's fully qualified "
+ "domain name, using %s for ServerName",
+ ap_server_argv0, server_hostname);
+
+ return server_hostname;
+}
+
+/* simple 'pool' alloc()ing glue to apr_base64.c
+ */
+AP_DECLARE(char *) ap_pbase64decode(apr_pool_t *p, const char *bufcoded)
+{
+ char *decoded;
+ int l;
+
+ decoded = (char *) apr_palloc(p, 1 + apr_base64_decode_len(bufcoded));
+ l = apr_base64_decode(decoded, bufcoded);
+ decoded[l] = '\0'; /* make binary sequence into string */
+
+ return decoded;
+}
+
+AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string)
+{
+ char *encoded;
+ int l = strlen(string);
+
+ encoded = (char *) apr_palloc(p, 1 + apr_base64_encode_len(l));
+ l = apr_base64_encode(encoded, string, l);
+ encoded[l] = '\0'; /* make binary sequence into string */
+
+ return encoded;
+}
+
+/* we want to downcase the type/subtype for comparison purposes
+ * but nothing else because ;parameter=foo values are case sensitive.
+ * XXX: in truth we want to downcase parameter names... but really,
+ * apache has never handled parameters and such correctly. You
+ * also need to compress spaces and such to be able to compare
+ * properly. -djg
+ */
+AP_DECLARE(void) ap_content_type_tolower(char *str)
+{
+ char *semi;
+
+ semi = strchr(str, ';');
+ if (semi) {
+ *semi = '\0';
+ }
+ while (*str) {
+ *str = apr_tolower(*str);
+ ++str;
+ }
+ if (semi) {
+ *semi = ';';
+ }
+}
+
+/*
+ * Given a string, replace any bare " with \" .
+ */
+AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring)
+{
+ int newlen = 0;
+ const char *inchr = instring;
+ char *outchr, *outstring;
+
+ /*
+ * Look through the input string, jogging the length of the output
+ * string up by an extra byte each time we find an unescaped ".
+ */
+ while (*inchr != '\0') {
+ newlen++;
+ if (*inchr == '"') {
+ newlen++;
+ }
+ /*
+ * If we find a slosh, and it's not the last byte in the string,
+ * it's escaping something - advance past both bytes.
+ */
+ if ((*inchr == '\\') && (inchr[1] != '\0')) {
+ inchr++;
+ newlen++;
+ }
+ inchr++;
+ }
+ outstring = apr_palloc(p, newlen + 1);
+ inchr = instring;
+ outchr = outstring;
+ /*
+ * Now copy the input string to the output string, inserting a slosh
+ * in front of every " that doesn't already have one.
+ */
+ while (*inchr != '\0') {
+ if ((*inchr == '\\') && (inchr[1] != '\0')) {
+ *outchr++ = *inchr++;
+ *outchr++ = *inchr++;
+ }
+ if (*inchr == '"') {
+ *outchr++ = '\\';
+ }
+ if (*inchr != '\0') {
+ *outchr++ = *inchr++;
+ }
+ }
+ *outchr = '\0';
+ return outstring;
+}
+
+/*
+ * Given a string, append the PID deliminated by delim.
+ * Usually used to create a pid-appended filepath name
+ * (eg: /a/b/foo -> /a/b/foo.6726). A function, and not
+ * a macro, to avoid unistd.h dependency
+ */
+AP_DECLARE(char *) ap_append_pid(apr_pool_t *p, const char *string,
+ const char *delim)
+{
+ return apr_psprintf(p, "%s%s%" APR_PID_T_FMT, string,
+ delim, getpid());
+
+}
diff --git a/server/util_cfgtree.c b/server/util_cfgtree.c
new file mode 100644
index 00000000..ac284a7b
--- /dev/null
+++ b/server/util_cfgtree.c
@@ -0,0 +1,47 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CORE_PRIVATE
+#include "util_cfgtree.h"
+#include <stdlib.h>
+
+ap_directive_t *ap_add_node(ap_directive_t **parent, ap_directive_t *current,
+ ap_directive_t *toadd, int child)
+{
+ if (current == NULL) {
+ /* we just started a new parent */
+ if (*parent != NULL) {
+ (*parent)->first_child = toadd;
+ toadd->parent = *parent;
+ }
+ if (child) {
+ /* First item in config file or container is a container */
+ *parent = toadd;
+ return NULL;
+ }
+ return toadd;
+ }
+ current->next = toadd;
+ toadd->parent = *parent;
+ if (child) {
+ /* switch parents, navigate into child */
+ *parent = toadd;
+ return NULL;
+ }
+ return toadd;
+}
+
+
diff --git a/server/util_charset.c b/server/util_charset.c
new file mode 100644
index 00000000..514e0b43
--- /dev/null
+++ b/server/util_charset.c
@@ -0,0 +1,42 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_config.h"
+
+#if APR_CHARSET_EBCDIC
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "util_charset.h"
+
+/* ap_hdrs_to_ascii, ap_hdrs_from_ascii
+ *
+ * These are the translation handles used to translate between the network
+ * format of protocol headers and the local machine format.
+ *
+ * For an EBCDIC machine, these are valid handles which are set up at
+ * initialization to translate between ISO-8859-1 and the code page of
+ * the source code.
+ *
+ * For an ASCII machine, these remain NULL so that when they are stored
+ * in the BUFF via ap_bsetop(BO_RXLATE) it ensures that no translation is
+ * performed.
+ */
+
+apr_xlate_t *ap_hdrs_to_ascii, *ap_hdrs_from_ascii;
+
+#endif /*APR_CHARSET_EBCDIC */
diff --git a/server/util_debug.c b/server/util_debug.c
new file mode 100644
index 00000000..0fefc5c8
--- /dev/null
+++ b/server/util_debug.c
@@ -0,0 +1,128 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+
+/* Possibly get rid of the macros we defined in httpd.h */
+#if defined(strchr)
+#undef strchr
+#endif
+
+#if defined (strrchr)
+#undef strrchr
+#endif
+
+#if defined (strstr)
+#undef strstr
+#endif
+
+
+#if defined(ap_strchr)
+#undef ap_strchr
+AP_DECLARE(char *) ap_strchr(char *s, int c);
+#endif
+
+AP_DECLARE(char *) ap_strchr(char *s, int c)
+{
+ return strchr(s,c);
+}
+
+#if defined(ap_strchr_c)
+#undef ap_strchr_c
+AP_DECLARE(const char *) ap_strchr_c(const char *s, int c);
+#endif
+
+AP_DECLARE(const char *) ap_strchr_c(const char *s, int c)
+{
+ return strchr(s,c);
+}
+
+#if defined(ap_strrchr)
+#undef ap_strrchr
+AP_DECLARE(char *) ap_strrchr(char *s, int c);
+#endif
+
+AP_DECLARE(char *) ap_strrchr(char *s, int c)
+{
+ return strrchr(s,c);
+}
+
+#if defined(ap_strrchr_c)
+#undef ap_strrchr_c
+AP_DECLARE(const char *) ap_strrchr_c(const char *s, int c);
+#endif
+
+AP_DECLARE(const char *) ap_strrchr_c(const char *s, int c)
+{
+ return strrchr(s,c);
+}
+
+#if defined(ap_strstr)
+#undef ap_strstr
+AP_DECLARE(char *) ap_strstr(char *s, const char *c);
+#endif
+
+AP_DECLARE(char *) ap_strstr(char *s, const char *c)
+{
+ return strstr(s,c);
+}
+
+#if defined(ap_strstr_c)
+#undef ap_strstr_c
+AP_DECLARE(const char *) ap_strstr_c(const char *s, const char *c);
+#endif
+
+AP_DECLARE(const char *) ap_strstr_c(const char *s, const char *c)
+{
+ return strstr(s,c);
+}
+
+#if defined(ap_get_module_config)
+#undef ap_get_module_config
+AP_DECLARE(void *) ap_get_module_config(const ap_conf_vector_t *cv,
+ const module *m);
+#endif
+
+AP_DECLARE(void *) ap_get_module_config(const ap_conf_vector_t *cv,
+ const module *m)
+{
+ return ((void **)cv)[m->module_index];
+}
+
+/**
+ * Generic accessors for other modules to set at their own module-specific
+ * data
+ * @param conf_vector The vector in which the modules configuration is stored.
+ * usually r->per_dir_config or s->module_config
+ * @param m The module to set the data for.
+ * @param val The module-specific data to set
+ * @deffunc void ap_set_module_config(ap_conf_vector_t *cv, const module *m, void *val)
+ */
+#if defined(ap_set_module_config)
+#undef ap_set_module_config
+AP_DECLARE(void) ap_set_module_config(ap_conf_vector_t *cv, const module *m,
+ void *val);
+#endif
+
+AP_DECLARE(void) ap_set_module_config(ap_conf_vector_t *cv, const module *m,
+ void *val)
+{
+ ((void **)cv)[m->module_index] = val;
+}
diff --git a/server/util_ebcdic.c b/server/util_ebcdic.c
new file mode 100644
index 00000000..78cf2739
--- /dev/null
+++ b/server/util_ebcdic.c
@@ -0,0 +1,112 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ap_config.h"
+
+#if APR_CHARSET_EBCDIC
+
+#include "apr_strings.h"
+#include "httpd.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "util_ebcdic.h"
+
+apr_status_t ap_init_ebcdic(apr_pool_t *pool)
+{
+ apr_status_t rv;
+ char buf[80];
+
+ rv = apr_xlate_open(&ap_hdrs_to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "apr_xlate_open() failed");
+ return rv;
+ }
+
+ rv = apr_xlate_open(&ap_hdrs_from_ascii, APR_DEFAULT_CHARSET, "ISO-8859-1", pool);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "apr_xlate_open() failed");
+ return rv;
+ }
+
+ rv = apr_MD5InitEBCDIC(ap_hdrs_to_ascii);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "apr_MD5InitEBCDIC() failed");
+ return rv;
+ }
+
+ rv = apr_base64init_ebcdic(ap_hdrs_to_ascii, ap_hdrs_from_ascii);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "apr_base64init_ebcdic() failed");
+ return rv;
+ }
+
+ rv = apr_SHA1InitEBCDIC(ap_hdrs_to_ascii);
+ if (rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "apr_SHA1InitEBCDIC() failed");
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+void ap_xlate_proto_to_ascii(char *buffer, apr_size_t len)
+{
+ apr_size_t inbytes_left, outbytes_left;
+
+ inbytes_left = outbytes_left = len;
+ apr_xlate_conv_buffer(ap_hdrs_to_ascii, buffer, &inbytes_left,
+ buffer, &outbytes_left);
+}
+
+void ap_xlate_proto_from_ascii(char *buffer, apr_size_t len)
+{
+ apr_size_t inbytes_left, outbytes_left;
+
+ inbytes_left = outbytes_left = len;
+ apr_xlate_conv_buffer(ap_hdrs_from_ascii, buffer, &inbytes_left,
+ buffer, &outbytes_left);
+}
+
+int ap_rvputs_proto_in_ascii(request_rec *r, ...)
+{
+ va_list va;
+ const char *s;
+ char *ascii_s;
+ apr_size_t len;
+ apr_size_t written = 0;
+
+ va_start(va, r);
+ while (1) {
+ s = va_arg(va, const char *);
+ if (s == NULL)
+ break;
+ len = strlen(s);
+ ascii_s = apr_pstrndup(r->pool, s, len);
+ ap_xlate_proto_to_ascii(ascii_s, len);
+ if (ap_rputs(ascii_s, r) < 0)
+ return -1;
+ written += len;
+ }
+ va_end(va);
+
+ return written;
+}
+#endif /* APR_CHARSET_EBCDIC */
diff --git a/server/util_filter.c b/server/util_filter.c
new file mode 100644
index 00000000..7d48b52d
--- /dev/null
+++ b/server/util_filter.c
@@ -0,0 +1,622 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apr_lib.h"
+#include "apr_hash.h"
+#include "apr_strings.h"
+
+#include "httpd.h"
+#include "http_log.h"
+#include "util_filter.h"
+
+/* NOTE: Apache's current design doesn't allow a pool to be passed thru,
+ so we depend on a global to hold the correct pool
+*/
+#define FILTER_POOL apr_hook_global_pool
+#include "apr_hooks.h" /* for apr_hook_global_pool */
+
+/*
+** This macro returns true/false if a given filter should be inserted BEFORE
+** another filter. This will happen when one of: 1) there isn't another
+** filter; 2) that filter has a higher filter type (class); 3) that filter
+** corresponds to a different request.
+*/
+#define INSERT_BEFORE(f, before_this) ((before_this) == NULL \
+ || (before_this)->frec->ftype > (f)->frec->ftype \
+ || (before_this)->r != (f)->r)
+
+/* Trie structure to hold the mapping from registered
+ * filter names to filters
+ */
+
+typedef struct filter_trie_node filter_trie_node;
+
+typedef struct {
+ int c;
+ filter_trie_node *child;
+} filter_trie_child_ptr;
+
+/* Each trie node has an array of pointers to its children.
+ * The array is kept in sorted order so that add_any_filter()
+ * can do a binary search
+ */
+struct filter_trie_node {
+ ap_filter_rec_t *frec;
+ filter_trie_child_ptr *children;
+ int nchildren;
+ int size;
+};
+
+#define TRIE_INITIAL_SIZE 4
+
+/* Link a trie node to its parent
+ */
+static void trie_node_link(apr_pool_t *p, filter_trie_node *parent,
+ filter_trie_node *child, int c)
+{
+ int i, j;
+
+ if (parent->nchildren == parent->size) {
+ filter_trie_child_ptr *new;
+ parent->size *= 2;
+ new = (filter_trie_child_ptr *)apr_palloc(p, parent->size *
+ sizeof(filter_trie_child_ptr));
+ memcpy(new, parent->children, parent->nchildren *
+ sizeof(filter_trie_child_ptr));
+ parent->children = new;
+ }
+
+ for (i = 0; i < parent->nchildren; i++) {
+ if (c == parent->children[i].c) {
+ return;
+ }
+ else if (c < parent->children[i].c) {
+ break;
+ }
+ }
+ for (j = parent->nchildren; j > i; j--) {
+ parent->children[j].c = parent->children[j - 1].c;
+ parent->children[j].child = parent->children[j - 1].child;
+ }
+ parent->children[i].c = c;
+ parent->children[i].child = child;
+
+ parent->nchildren++;
+}
+
+/* Allocate a new node for a trie.
+ * If parent is non-NULL, link the new node under the parent node with
+ * key 'c' (or, if an existing child node matches, return that one)
+ */
+static filter_trie_node *trie_node_alloc(apr_pool_t *p,
+ filter_trie_node *parent, char c)
+{
+ filter_trie_node *new_node;
+ if (parent) {
+ int i;
+ for (i = 0; i < parent->nchildren; i++) {
+ if (c == parent->children[i].c) {
+ return parent->children[i].child;
+ }
+ else if (c < parent->children[i].c) {
+ break;
+ }
+ }
+ new_node =
+ (filter_trie_node *)apr_palloc(p, sizeof(filter_trie_node));
+ trie_node_link(p, parent, new_node, c);
+ }
+ else { /* No parent node */
+ new_node = (filter_trie_node *)apr_palloc(p,
+ sizeof(filter_trie_node));
+ }
+
+ new_node->frec = NULL;
+ new_node->nchildren = 0;
+ new_node->size = TRIE_INITIAL_SIZE;
+ new_node->children = (filter_trie_child_ptr *)apr_palloc(p,
+ new_node->size * sizeof(filter_trie_child_ptr));
+ return new_node;
+}
+
+static filter_trie_node *registered_output_filters = NULL;
+static filter_trie_node *registered_input_filters = NULL;
+
+
+static apr_status_t filter_cleanup(void *ctx)
+{
+ registered_output_filters = NULL;
+ registered_input_filters = NULL;
+ return APR_SUCCESS;
+}
+
+static ap_filter_rec_t *get_filter_handle(const char *name,
+ const filter_trie_node *filter_set)
+{
+ if (filter_set) {
+ const char *n;
+ const filter_trie_node *node;
+
+ node = filter_set;
+ for (n = name; *n; n++) {
+ int start, end;
+ start = 0;
+ end = node->nchildren - 1;
+ while (end >= start) {
+ int middle = (end + start) / 2;
+ char ch = node->children[middle].c;
+ if (*n == ch) {
+ node = node->children[middle].child;
+ break;
+ }
+ else if (*n < ch) {
+ end = middle - 1;
+ }
+ else {
+ start = middle + 1;
+ }
+ }
+ if (end < start) {
+ node = NULL;
+ break;
+ }
+ }
+
+ if (node && node->frec) {
+ return node->frec;
+ }
+ }
+ return NULL;
+}
+
+AP_DECLARE(ap_filter_rec_t *)ap_get_output_filter_handle(const char *name)
+{
+ return get_filter_handle(name, registered_output_filters);
+}
+
+AP_DECLARE(ap_filter_rec_t *)ap_get_input_filter_handle(const char *name)
+{
+ return get_filter_handle(name, registered_input_filters);
+}
+
+static ap_filter_rec_t *register_filter(const char *name,
+ ap_filter_func filter_func,
+ ap_init_filter_func filter_init,
+ ap_filter_type ftype,
+ filter_trie_node **reg_filter_set)
+{
+ ap_filter_rec_t *frec;
+ char *normalized_name;
+ const char *n;
+ filter_trie_node *node;
+
+ if (!*reg_filter_set) {
+ *reg_filter_set = trie_node_alloc(FILTER_POOL, NULL, 0);
+ }
+
+ normalized_name = apr_pstrdup(FILTER_POOL, name);
+ ap_str_tolower(normalized_name);
+
+ node = *reg_filter_set;
+ for (n = normalized_name; *n; n++) {
+ filter_trie_node *child = trie_node_alloc(FILTER_POOL, node, *n);
+ if (apr_isalpha(*n)) {
+ trie_node_link(FILTER_POOL, node, child, apr_toupper(*n));
+ }
+ node = child;
+ }
+ if (node->frec) {
+ frec = node->frec;
+ }
+ else {
+ frec = apr_pcalloc(FILTER_POOL, sizeof(*frec));
+ node->frec = frec;
+ frec->name = normalized_name;
+ }
+ frec->filter_func = filter_func;
+ frec->filter_init_func = filter_init;
+ frec->ftype = ftype;
+
+ apr_pool_cleanup_register(FILTER_POOL, NULL, filter_cleanup,
+ apr_pool_cleanup_null);
+ return frec;
+}
+
+AP_DECLARE(ap_filter_rec_t *) ap_register_input_filter(const char *name,
+ ap_in_filter_func filter_func,
+ ap_init_filter_func filter_init,
+ ap_filter_type ftype)
+{
+ ap_filter_func f;
+ f.in_func = filter_func;
+ return register_filter(name, f, filter_init, ftype,
+ &registered_input_filters);
+}
+
+/* Prepare to make this a #define in 2.2 */
+AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter(const char *name,
+ ap_out_filter_func filter_func,
+ ap_init_filter_func filter_init,
+ ap_filter_type ftype)
+{
+ return ap_register_output_filter_protocol(name, filter_func,
+ filter_init, ftype, 0) ;
+}
+AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter_protocol(
+ const char *name,
+ ap_out_filter_func filter_func,
+ ap_init_filter_func filter_init,
+ ap_filter_type ftype,
+ unsigned int proto_flags)
+{
+ ap_filter_rec_t* ret ;
+ ap_filter_func f;
+ f.out_func = filter_func;
+ ret = register_filter(name, f, filter_init, ftype,
+ &registered_output_filters);
+ ret->proto_flags = proto_flags ;
+ return ret ;
+}
+
+static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx,
+ request_rec *r, conn_rec *c,
+ ap_filter_t **r_filters,
+ ap_filter_t **p_filters,
+ ap_filter_t **c_filters)
+{
+ apr_pool_t* p = r ? r->pool : c->pool;
+ ap_filter_t *f = apr_palloc(p, sizeof(*f));
+ ap_filter_t **outf;
+
+ if (frec->ftype < AP_FTYPE_PROTOCOL) {
+ if (r) {
+ outf = r_filters;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "a content filter was added without a request: %s", frec->name);
+ return NULL;
+ }
+ }
+ else if (frec->ftype < AP_FTYPE_CONNECTION) {
+ if (r) {
+ outf = p_filters;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "a protocol filter was added without a request: %s", frec->name);
+ return NULL;
+ }
+ }
+ else {
+ outf = c_filters;
+ }
+
+ f->frec = frec;
+ f->ctx = ctx;
+ f->r = r;
+ f->c = c;
+ f->next = NULL;
+
+ if (INSERT_BEFORE(f, *outf)) {
+ f->next = *outf;
+
+ if (*outf) {
+ ap_filter_t *first = NULL;
+
+ if (r) {
+ /* If we are adding our first non-connection filter,
+ * Then don't try to find the right location, it is
+ * automatically first.
+ */
+ if (*r_filters != *c_filters) {
+ first = *r_filters;
+ while (first && (first->next != (*outf))) {
+ first = first->next;
+ }
+ }
+ }
+ if (first && first != (*outf)) {
+ first->next = f;
+ }
+ }
+ *outf = f;
+ }
+ else {
+ ap_filter_t *fscan = *outf;
+ while (!INSERT_BEFORE(f, fscan->next))
+ fscan = fscan->next;
+
+ f->next = fscan->next;
+ fscan->next = f;
+ }
+
+ if (frec->ftype < AP_FTYPE_CONNECTION && (*r_filters == *c_filters)) {
+ *r_filters = *p_filters;
+ }
+ return f;
+}
+
+static ap_filter_t *add_any_filter(const char *name, void *ctx,
+ request_rec *r, conn_rec *c,
+ const filter_trie_node *reg_filter_set,
+ ap_filter_t **r_filters,
+ ap_filter_t **p_filters,
+ ap_filter_t **c_filters)
+{
+ if (reg_filter_set) {
+ const char *n;
+ const filter_trie_node *node;
+
+ node = reg_filter_set;
+ for (n = name; *n; n++) {
+ int start, end;
+ start = 0;
+ end = node->nchildren - 1;
+ while (end >= start) {
+ int middle = (end + start) / 2;
+ char ch = node->children[middle].c;
+ if (*n == ch) {
+ node = node->children[middle].child;
+ break;
+ }
+ else if (*n < ch) {
+ end = middle - 1;
+ }
+ else {
+ start = middle + 1;
+ }
+ }
+ if (end < start) {
+ node = NULL;
+ break;
+ }
+ }
+
+ if (node && node->frec) {
+ return add_any_filter_handle(node->frec, ctx, r, c, r_filters,
+ p_filters, c_filters);
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+ "an unknown filter was not added: %s", name);
+ return NULL;
+}
+
+AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx,
+ request_rec *r, conn_rec *c)
+{
+ return add_any_filter(name, ctx, r, c, registered_input_filters,
+ r ? &r->input_filters : NULL,
+ r ? &r->proto_input_filters : NULL, &c->input_filters);
+}
+
+AP_DECLARE(ap_filter_t *) ap_add_input_filter_handle(ap_filter_rec_t *f,
+ void *ctx,
+ request_rec *r,
+ conn_rec *c)
+{
+ return add_any_filter_handle(f, ctx, r, c, r ? &r->input_filters : NULL,
+ r ? &r->proto_input_filters : NULL,
+ &c->input_filters);
+}
+
+AP_DECLARE(ap_filter_t *) ap_add_output_filter(const char *name, void *ctx,
+ request_rec *r, conn_rec *c)
+{
+ return add_any_filter(name, ctx, r, c, registered_output_filters,
+ r ? &r->output_filters : NULL,
+ r ? &r->proto_output_filters : NULL, &c->output_filters);
+}
+
+AP_DECLARE(ap_filter_t *) ap_add_output_filter_handle(ap_filter_rec_t *f,
+ void *ctx,
+ request_rec *r,
+ conn_rec *c)
+{
+ return add_any_filter_handle(f, ctx, r, c, r ? &r->output_filters : NULL,
+ r ? &r->proto_output_filters : NULL,
+ &c->output_filters);
+}
+
+static void remove_any_filter(ap_filter_t *f, ap_filter_t **r_filt, ap_filter_t **p_filt,
+ ap_filter_t **c_filt)
+{
+ ap_filter_t **curr = r_filt ? r_filt : c_filt;
+ ap_filter_t *fscan = *curr;
+
+ if (p_filt && *p_filt == f)
+ *p_filt = (*p_filt)->next;
+
+ if (*curr == f) {
+ *curr = (*curr)->next;
+ return;
+ }
+
+ while (fscan->next != f) {
+ if (!(fscan = fscan->next)) {
+ return;
+ }
+ }
+
+ fscan->next = f->next;
+}
+
+AP_DECLARE(void) ap_remove_input_filter(ap_filter_t *f)
+{
+ remove_any_filter(f, f->r ? &f->r->input_filters : NULL,
+ f->r ? &f->r->proto_input_filters : NULL,
+ &f->c->input_filters);
+}
+
+AP_DECLARE(void) ap_remove_output_filter(ap_filter_t *f)
+{
+ remove_any_filter(f, f->r ? &f->r->output_filters : NULL,
+ f->r ? &f->r->proto_output_filters : NULL,
+ &f->c->output_filters);
+}
+
+/*
+ * Read data from the next filter in the filter stack. Data should be
+ * modified in the bucket brigade that is passed in. The core allocates the
+ * bucket brigade, modules that wish to replace large chunks of data or to
+ * save data off to the side should probably create their own temporary
+ * brigade especially for that use.
+ */
+AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *next,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ if (next) {
+ return next->frec->filter_func.in_func(next, bb, mode, block,
+ readbytes);
+ }
+ return AP_NOBODY_READ;
+}
+
+/* Pass the buckets to the next filter in the filter stack. If the
+ * current filter is a handler, we should get NULL passed in instead of
+ * the current filter. At that point, we can just call the first filter in
+ * the stack, or r->output_filters.
+ */
+AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *next,
+ apr_bucket_brigade *bb)
+{
+ if (next) {
+ apr_bucket *e;
+ if ((e = APR_BRIGADE_LAST(bb)) && APR_BUCKET_IS_EOS(e) && next->r) {
+ /* This is only safe because HTTP_HEADER filter is always in
+ * the filter stack. This ensures that there is ALWAYS a
+ * request-based filter that we can attach this to. If the
+ * HTTP_FILTER is removed, and another filter is not put in its
+ * place, then handlers like mod_cgi, which attach their own
+ * EOS bucket to the brigade will be broken, because we will
+ * get two EOS buckets on the same request.
+ */
+ next->r->eos_sent = 1;
+
+ /* remember the eos for internal redirects, too */
+ if (next->r->prev) {
+ request_rec *prev = next->r->prev;
+
+ while (prev) {
+ prev->eos_sent = 1;
+ prev = prev->prev;
+ }
+ }
+ }
+ return next->frec->filter_func.out_func(next, bb);
+ }
+ return AP_NOBODY_WROTE;
+}
+
+AP_DECLARE(apr_status_t) ap_save_brigade(ap_filter_t *f,
+ apr_bucket_brigade **saveto,
+ apr_bucket_brigade **b, apr_pool_t *p)
+{
+ apr_bucket *e;
+ apr_status_t rv, srv = APR_SUCCESS;
+
+ /* If have never stored any data in the filter, then we had better
+ * create an empty bucket brigade so that we can concat.
+ */
+ if (!(*saveto)) {
+ *saveto = apr_brigade_create(p, f->c->bucket_alloc);
+ }
+
+ for (e = APR_BRIGADE_FIRST(*b);
+ e != APR_BRIGADE_SENTINEL(*b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ rv = apr_bucket_setaside(e, p);
+
+ /* If the bucket type does not implement setaside, then
+ * (hopefully) morph it into a bucket type which does, and set
+ * *that* aside... */
+ if (rv == APR_ENOTIMPL) {
+ const char *s;
+ apr_size_t n;
+
+ rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ);
+ if (rv == APR_SUCCESS) {
+ rv = apr_bucket_setaside(e, p);
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ srv = rv;
+ /* Return an error but still save the brigade if
+ * ->setaside() is really not implemented. */
+ if (rv != APR_ENOTIMPL) {
+ return rv;
+ }
+ }
+ }
+ APR_BRIGADE_CONCAT(*saveto, *b);
+ return srv;
+}
+
+AP_DECLARE_NONSTD(apr_status_t) ap_filter_flush(apr_bucket_brigade *bb,
+ void *ctx)
+{
+ ap_filter_t *f = ctx;
+
+ return ap_pass_brigade(f, bb);
+}
+
+AP_DECLARE(apr_status_t) ap_fflush(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+
+ b = apr_bucket_flush_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ return ap_pass_brigade(f, bb);
+}
+
+AP_DECLARE_NONSTD(apr_status_t) ap_fputstrs(ap_filter_t *f,
+ apr_bucket_brigade *bb, ...)
+{
+ va_list args;
+ apr_status_t rv;
+
+ va_start(args, bb);
+ rv = apr_brigade_vputstrs(bb, ap_filter_flush, f, args);
+ va_end(args);
+ return rv;
+}
+
+AP_DECLARE_NONSTD(apr_status_t) ap_fprintf(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ apr_status_t rv;
+
+ va_start(args, fmt);
+ rv = apr_brigade_vprintf(bb, ap_filter_flush, f, fmt, args);
+ va_end(args);
+ return rv;
+}
+AP_DECLARE(void) ap_filter_protocol(ap_filter_t *f, unsigned int flags)
+{
+ f->frec->proto_flags = flags ;
+}
diff --git a/server/util_md5.c b/server/util_md5.c
new file mode 100644
index 00000000..83bfa757
--- /dev/null
+++ b/server/util_md5.c
@@ -0,0 +1,172 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/************************************************************************
+ * NCSA HTTPd Server
+ * Software Development Group
+ * National Center for Supercomputing Applications
+ * University of Illinois at Urbana-Champaign
+ * 605 E. Springfield, Champaign, IL 61820
+ * httpd@ncsa.uiuc.edu
+ *
+ * Copyright (C) 1995, Board of Trustees of the University of Illinois
+ *
+ ************************************************************************
+ *
+ * md5.c: NCSA HTTPd code which uses the md5c.c RSA Code
+ *
+ * Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc.
+ * Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon
+ * University (see Copyright below).
+ * Portions of Content-MD5 code Copyright (C) 1991 Bell Communications
+ * Research, Inc. (Bellcore) (see Copyright below).
+ * Portions extracted from mpack, John G. Myers - jgm+@cmu.edu
+ * Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk)
+ *
+ */
+
+
+
+/* md5.c --Module Interface to MD5. */
+/* Jeff Hostetler, Spyglass, Inc., 1994. */
+
+#include "ap_config.h"
+#include "apr_portable.h"
+#include "apr_strings.h"
+#include "httpd.h"
+#include "util_md5.h"
+#include "util_ebcdic.h"
+
+AP_DECLARE(char *) ap_md5_binary(apr_pool_t *p, const unsigned char *buf, int length)
+{
+ const char *hex = "0123456789abcdef";
+ apr_md5_ctx_t my_md5;
+ unsigned char hash[APR_MD5_DIGESTSIZE];
+ char *r, result[33]; /* (MD5_DIGESTSIZE * 2) + 1 */
+ int i;
+
+ /*
+ * Take the MD5 hash of the string argument.
+ */
+
+ apr_md5_init(&my_md5);
+#if APR_CHARSET_EBCDIC
+ apr_md5_set_xlate(&my_md5, ap_hdrs_to_ascii);
+#endif
+ apr_md5_update(&my_md5, buf, (unsigned int)length);
+ apr_md5_final(hash, &my_md5);
+
+ for (i = 0, r = result; i < APR_MD5_DIGESTSIZE; i++) {
+ *r++ = hex[hash[i] >> 4];
+ *r++ = hex[hash[i] & 0xF];
+ }
+ *r = '\0';
+
+ return apr_pstrndup(p, result, APR_MD5_DIGESTSIZE*2);
+}
+
+AP_DECLARE(char *) ap_md5(apr_pool_t *p, const unsigned char *string)
+{
+ return ap_md5_binary(p, string, (int) strlen((char *)string));
+}
+
+/* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */
+
+/* (C) Copyright 1993,1994 by Carnegie Mellon University
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Carnegie
+ * Mellon University not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. Carnegie Mellon University makes no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore)
+ *
+ * Permission to use, copy, modify, and distribute this material
+ * for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice
+ * appear in all copies, and that the name of Bellcore not be
+ * used in advertising or publicity pertaining to this
+ * material without the specific, prior written permission
+ * of an authorized representative of Bellcore. BELLCORE
+ * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
+ * OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS",
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
+ */
+
+static char basis_64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+AP_DECLARE(char *) ap_md5contextTo64(apr_pool_t *a, apr_md5_ctx_t *context)
+{
+ unsigned char digest[18];
+ char *encodedDigest;
+ int i;
+ char *p;
+
+ encodedDigest = (char *) apr_pcalloc(a, 25 * sizeof(char));
+
+ apr_md5_final(digest, context);
+ digest[sizeof(digest) - 1] = digest[sizeof(digest) - 2] = 0;
+
+ p = encodedDigest;
+ for (i = 0; i < sizeof(digest); i += 3) {
+ *p++ = basis_64[digest[i] >> 2];
+ *p++ = basis_64[((digest[i] & 0x3) << 4) | ((int) (digest[i + 1] & 0xF0) >> 4)];
+ *p++ = basis_64[((digest[i + 1] & 0xF) << 2) | ((int) (digest[i + 2] & 0xC0) >> 6)];
+ *p++ = basis_64[digest[i + 2] & 0x3F];
+ }
+ *p-- = '\0';
+ *p-- = '=';
+ *p-- = '=';
+ return encodedDigest;
+}
+
+AP_DECLARE(char *) ap_md5digest(apr_pool_t *p, apr_file_t *infile)
+{
+ apr_md5_ctx_t context;
+ unsigned char buf[4096]; /* keep this a multiple of 64 */
+ apr_size_t nbytes;
+ apr_off_t offset = 0L;
+
+ apr_md5_init(&context);
+ nbytes = sizeof(buf);
+ while (apr_file_read(infile, buf, &nbytes) == APR_SUCCESS) {
+ apr_md5_update(&context, buf, nbytes);
+ nbytes = sizeof(buf);
+ }
+ apr_file_seek(infile, APR_SET, &offset);
+ return ap_md5contextTo64(p, &context);
+}
+
diff --git a/server/util_pcre.c b/server/util_pcre.c
new file mode 100644
index 00000000..c69c9785
--- /dev/null
+++ b/server/util_pcre.c
@@ -0,0 +1,230 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/*
+This is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language. See
+the file Tech.Notes for some information on the internals.
+
+This module is a wrapper that provides a POSIX API to the underlying PCRE
+functions.
+
+Written by: Philip Hazel <ph10@cam.ac.uk>
+
+ Copyright (c) 1997-2004 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * 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.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+#include "httpd.h"
+#include "apr_strings.h"
+#include "pcre.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#ifndef POSIX_MALLOC_THRESHOLD
+#define POSIX_MALLOC_THRESHOLD (10)
+#endif
+
+/* Table of error strings corresponding to POSIX error codes; must be
+ * kept in synch with include/ap_regex.h's AP_REG_E* definitions. */
+
+static const char *const pstring[] = {
+ "", /* Dummy for value 0 */
+ "internal error", /* AP_REG_ASSERT */
+ "failed to get memory", /* AP_REG_ESPACE */
+ "bad argument", /* AP_REG_INVARG */
+ "match failed" /* AP_REG_NOMATCH */
+};
+
+AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg,
+ char *errbuf, apr_size_t errbuf_size)
+{
+const char *message, *addmessage;
+apr_size_t length, addlength;
+
+message = (errcode >= (int)(sizeof(pstring)/sizeof(char *)))?
+ "unknown error code" : pstring[errcode];
+length = strlen(message) + 1;
+
+addmessage = " at offset ";
+addlength = (preg != NULL && (int)preg->re_erroffset != -1)?
+ strlen(addmessage) + 6 : 0;
+
+if (errbuf_size > 0)
+ {
+ if (addlength > 0 && errbuf_size >= length + addlength)
+ apr_snprintf(errbuf, sizeof errbuf,
+ "%s%s%-6d", message, addmessage, (int)preg->re_erroffset);
+ else
+ {
+ strncpy(errbuf, message, errbuf_size - 1);
+ errbuf[errbuf_size-1] = 0;
+ }
+ }
+
+return length + addlength;
+}
+
+
+
+
+/*************************************************
+* Free store held by a regex *
+*************************************************/
+
+AP_DECLARE(void) ap_regfree(ap_regex_t *preg)
+{
+(pcre_free)(preg->re_pcre);
+}
+
+
+
+
+/*************************************************
+* Compile a regular expression *
+*************************************************/
+
+/*
+Arguments:
+ preg points to a structure for recording the compiled expression
+ pattern the pattern to compile
+ cflags compilation flags
+
+Returns: 0 on success
+ various non-zero codes on failure
+*/
+
+AP_DECLARE(int) ap_regcomp(ap_regex_t *preg, const char *pattern, int cflags)
+{
+const char *errorptr;
+int erroffset;
+int options = 0;
+
+if ((cflags & AP_REG_ICASE) != 0) options |= PCRE_CASELESS;
+if ((cflags & AP_REG_NEWLINE) != 0) options |= PCRE_MULTILINE;
+
+preg->re_pcre = pcre_compile(pattern, options, &errorptr, &erroffset, NULL);
+preg->re_erroffset = erroffset;
+
+if (preg->re_pcre == NULL) return AP_REG_INVARG;
+
+preg->re_nsub = pcre_info((const pcre *)preg->re_pcre, NULL, NULL);
+return 0;
+}
+
+
+
+
+/*************************************************
+* Match a regular expression *
+*************************************************/
+
+/* Unfortunately, PCRE requires 3 ints of working space for each captured
+substring, so we have to get and release working store instead of just using
+the POSIX structures as was done in earlier releases when PCRE needed only 2
+ints. However, if the number of possible capturing brackets is small, use a
+block of store on the stack, to reduce the use of malloc/free. The threshold is
+in a macro that can be changed at configure time. */
+
+AP_DECLARE(int) ap_regexec(const ap_regex_t *preg, const char *string,
+ apr_size_t nmatch, ap_regmatch_t pmatch[],
+ int eflags)
+{
+int rc;
+int options = 0;
+int *ovector = NULL;
+int small_ovector[POSIX_MALLOC_THRESHOLD * 3];
+int allocated_ovector = 0;
+
+if ((eflags & AP_REG_NOTBOL) != 0) options |= PCRE_NOTBOL;
+if ((eflags & AP_REG_NOTEOL) != 0) options |= PCRE_NOTEOL;
+
+((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1); /* Only has meaning after compile */
+
+if (nmatch > 0)
+ {
+ if (nmatch <= POSIX_MALLOC_THRESHOLD)
+ {
+ ovector = &(small_ovector[0]);
+ }
+ else
+ {
+ ovector = (int *)malloc(sizeof(int) * nmatch * 3);
+ if (ovector == NULL) return AP_REG_ESPACE;
+ allocated_ovector = 1;
+ }
+ }
+
+rc = pcre_exec((const pcre *)preg->re_pcre, NULL, string, (int)strlen(string),
+ 0, options, ovector, nmatch * 3);
+
+if (rc == 0) rc = nmatch; /* All captured slots were filled in */
+
+if (rc >= 0)
+ {
+ apr_size_t i;
+ for (i = 0; i < (apr_size_t)rc; i++)
+ {
+ pmatch[i].rm_so = ovector[i*2];
+ pmatch[i].rm_eo = ovector[i*2+1];
+ }
+ if (allocated_ovector) free(ovector);
+ for (; i < nmatch; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1;
+ return 0;
+ }
+
+else
+ {
+ if (allocated_ovector) free(ovector);
+ switch(rc)
+ {
+ case PCRE_ERROR_NOMATCH: return AP_REG_NOMATCH;
+ case PCRE_ERROR_NULL: return AP_REG_INVARG;
+ case PCRE_ERROR_BADOPTION: return AP_REG_INVARG;
+ case PCRE_ERROR_BADMAGIC: return AP_REG_INVARG;
+ case PCRE_ERROR_UNKNOWN_NODE: return AP_REG_ASSERT;
+ case PCRE_ERROR_NOMEMORY: return AP_REG_ESPACE;
+#ifdef PCRE_ERROR_MATCHLIMIT
+ case PCRE_ERROR_MATCHLIMIT: return AP_REG_ESPACE;
+#endif
+#ifdef PCRE_ERROR_BADUTF8
+ case PCRE_ERROR_BADUTF8: return AP_REG_INVARG;
+#endif
+#ifdef PCRE_ERROR_BADUTF8_OFFSET
+ case PCRE_ERROR_BADUTF8_OFFSET: return AP_REG_INVARG;
+#endif
+ default: return AP_REG_ASSERT;
+ }
+ }
+}
+
+/* End of pcreposix.c */
diff --git a/server/util_script.c b/server/util_script.c
new file mode 100644
index 00000000..957413a4
--- /dev/null
+++ b/server/util_script.c
@@ -0,0 +1,721 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_main.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h" /* for sub_req_lookup_uri() */
+#include "util_script.h"
+#include "apr_date.h" /* For apr_date_parse_http() */
+#include "util_ebcdic.h"
+
+#ifdef OS2
+#define INCL_DOS
+#include <os2.h>
+#endif
+
+/*
+ * Various utility functions which are common to a whole lot of
+ * script-type extensions mechanisms, and might as well be gathered
+ * in one place (if only to avoid creating inter-module dependancies
+ * where there don't have to be).
+ */
+
+#define MALFORMED_MESSAGE "malformed header from script. Bad header="
+#define MALFORMED_HEADER_LENGTH_TO_SHOW 30
+
+static char *http2env(apr_pool_t *a, const char *w)
+{
+ char *res = (char *)apr_palloc(a, sizeof("HTTP_") + strlen(w));
+ char *cp = res;
+ char c;
+
+ *cp++ = 'H';
+ *cp++ = 'T';
+ *cp++ = 'T';
+ *cp++ = 'P';
+ *cp++ = '_';
+
+ while ((c = *w++) != 0) {
+ if (!apr_isalnum(c)) {
+ *cp++ = '_';
+ }
+ else {
+ *cp++ = apr_toupper(c);
+ }
+ }
+ *cp = 0;
+
+ return res;
+}
+
+AP_DECLARE(char **) ap_create_environment(apr_pool_t *p, apr_table_t *t)
+{
+ const apr_array_header_t *env_arr = apr_table_elts(t);
+ const apr_table_entry_t *elts = (const apr_table_entry_t *) env_arr->elts;
+ char **env = (char **) apr_palloc(p, (env_arr->nelts + 2) * sizeof(char *));
+ int i, j;
+ char *tz;
+ char *whack;
+
+ j = 0;
+ if (!apr_table_get(t, "TZ")) {
+ tz = getenv("TZ");
+ if (tz != NULL) {
+ env[j++] = apr_pstrcat(p, "TZ=", tz, NULL);
+ }
+ }
+ for (i = 0; i < env_arr->nelts; ++i) {
+ if (!elts[i].key) {
+ continue;
+ }
+ env[j] = apr_pstrcat(p, elts[i].key, "=", elts[i].val, NULL);
+ whack = env[j];
+ if (apr_isdigit(*whack)) {
+ *whack++ = '_';
+ }
+ while (*whack != '=') {
+ if (!apr_isalnum(*whack) && *whack != '_') {
+ *whack = '_';
+ }
+ ++whack;
+ }
+ ++j;
+ }
+
+ env[j] = NULL;
+ return env;
+}
+
+AP_DECLARE(void) ap_add_common_vars(request_rec *r)
+{
+ apr_table_t *e;
+ server_rec *s = r->server;
+ conn_rec *c = r->connection;
+ const char *rem_logname;
+ char *env_path;
+#if defined(WIN32) || defined(OS2) || defined(BEOS)
+ char *env_temp;
+#endif
+ const char *host;
+ const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in);
+ const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
+ int i;
+ apr_port_t rport;
+
+ /* use a temporary apr_table_t which we'll overlap onto
+ * r->subprocess_env later
+ * (exception: if r->subprocess_env is empty at the start,
+ * write directly into it)
+ */
+ if (apr_is_empty_table(r->subprocess_env)) {
+ e = r->subprocess_env;
+ }
+ else {
+ e = apr_table_make(r->pool, 25 + hdrs_arr->nelts);
+ }
+
+ /* First, add environment vars from headers... this is as per
+ * CGI specs, though other sorts of scripting interfaces see
+ * the same vars...
+ */
+
+ for (i = 0; i < hdrs_arr->nelts; ++i) {
+ if (!hdrs[i].key) {
+ continue;
+ }
+
+ /* A few headers are special cased --- Authorization to prevent
+ * rogue scripts from capturing passwords; content-type and -length
+ * for no particular reason.
+ */
+
+ if (!strcasecmp(hdrs[i].key, "Content-type")) {
+ apr_table_addn(e, "CONTENT_TYPE", hdrs[i].val);
+ }
+ else if (!strcasecmp(hdrs[i].key, "Content-length")) {
+ apr_table_addn(e, "CONTENT_LENGTH", hdrs[i].val);
+ }
+ /*
+ * You really don't want to disable this check, since it leaves you
+ * wide open to CGIs stealing passwords and people viewing them
+ * in the environment with "ps -e". But, if you must...
+ */
+#ifndef SECURITY_HOLE_PASS_AUTHORIZATION
+ else if (!strcasecmp(hdrs[i].key, "Authorization")
+ || !strcasecmp(hdrs[i].key, "Proxy-Authorization")) {
+ continue;
+ }
+#endif
+ else {
+ apr_table_addn(e, http2env(r->pool, hdrs[i].key), hdrs[i].val);
+ }
+ }
+
+ if (!(env_path = getenv("PATH"))) {
+ env_path = DEFAULT_PATH;
+ }
+ apr_table_addn(e, "PATH", apr_pstrdup(r->pool, env_path));
+
+#ifdef WIN32
+ if (env_temp = getenv("SystemRoot")) {
+ apr_table_addn(e, "SystemRoot", env_temp);
+ }
+ if (env_temp = getenv("COMSPEC")) {
+ apr_table_addn(e, "COMSPEC", env_temp);
+ }
+ if (env_temp = getenv("PATHEXT")) {
+ apr_table_addn(e, "PATHEXT", env_temp);
+ }
+ if (env_temp = getenv("WINDIR")) {
+ apr_table_addn(e, "WINDIR", env_temp);
+ }
+#endif
+
+#ifdef OS2
+ if ((env_temp = getenv("COMSPEC")) != NULL) {
+ apr_table_addn(e, "COMSPEC", env_temp);
+ }
+ if ((env_temp = getenv("ETC")) != NULL) {
+ apr_table_addn(e, "ETC", env_temp);
+ }
+ if ((env_temp = getenv("DPATH")) != NULL) {
+ apr_table_addn(e, "DPATH", env_temp);
+ }
+ if ((env_temp = getenv("PERLLIB_PREFIX")) != NULL) {
+ apr_table_addn(e, "PERLLIB_PREFIX", env_temp);
+ }
+#endif
+
+#ifdef BEOS
+ if ((env_temp = getenv("LIBRARY_PATH")) != NULL) {
+ apr_table_addn(e, "LIBRARY_PATH", env_temp);
+ }
+#endif
+
+ apr_table_addn(e, "SERVER_SIGNATURE", ap_psignature("", r));
+ apr_table_addn(e, "SERVER_SOFTWARE", ap_get_server_version());
+ apr_table_addn(e, "SERVER_NAME",
+ ap_escape_html(r->pool, ap_get_server_name(r)));
+ apr_table_addn(e, "SERVER_ADDR", r->connection->local_ip); /* Apache */
+ apr_table_addn(e, "SERVER_PORT",
+ apr_psprintf(r->pool, "%u", ap_get_server_port(r)));
+ host = ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST, NULL);
+ if (host) {
+ apr_table_addn(e, "REMOTE_HOST", host);
+ }
+ apr_table_addn(e, "REMOTE_ADDR", c->remote_ip);
+ apr_table_addn(e, "DOCUMENT_ROOT", ap_document_root(r)); /* Apache */
+ apr_table_addn(e, "SERVER_ADMIN", s->server_admin); /* Apache */
+ apr_table_addn(e, "SCRIPT_FILENAME", r->filename); /* Apache */
+
+ rport = c->remote_addr->port;
+ apr_table_addn(e, "REMOTE_PORT", apr_itoa(r->pool, rport));
+
+ if (r->user) {
+ apr_table_addn(e, "REMOTE_USER", r->user);
+ }
+ else if (r->prev) {
+ request_rec *back = r->prev;
+
+ while (back) {
+ if (back->user) {
+ apr_table_addn(e, "REDIRECT_REMOTE_USER", back->user);
+ break;
+ }
+ back = back->prev;
+ }
+ }
+ if (r->ap_auth_type) {
+ apr_table_addn(e, "AUTH_TYPE", r->ap_auth_type);
+ }
+ rem_logname = ap_get_remote_logname(r);
+ if (rem_logname) {
+ apr_table_addn(e, "REMOTE_IDENT", apr_pstrdup(r->pool, rem_logname));
+ }
+
+ /* Apache custom error responses. If we have redirected set two new vars */
+
+ if (r->prev) {
+ if (r->prev->args) {
+ apr_table_addn(e, "REDIRECT_QUERY_STRING", r->prev->args);
+ }
+ if (r->prev->uri) {
+ apr_table_addn(e, "REDIRECT_URL", r->prev->uri);
+ }
+ }
+
+ if (e != r->subprocess_env) {
+ apr_table_overlap(r->subprocess_env, e, APR_OVERLAP_TABLES_SET);
+ }
+}
+
+/* This "cute" little function comes about because the path info on
+ * filenames and URLs aren't always the same. So we take the two,
+ * and find as much of the two that match as possible.
+ */
+
+AP_DECLARE(int) ap_find_path_info(const char *uri, const char *path_info)
+{
+ int lu = strlen(uri);
+ int lp = strlen(path_info);
+
+ while (lu-- && lp-- && uri[lu] == path_info[lp]) {
+ if (path_info[lp] == '/') {
+ while (lu && uri[lu-1] == '/') lu--;
+ }
+ }
+
+ if (lu == -1) {
+ lu = 0;
+ }
+
+ while (uri[lu] != '\0' && uri[lu] != '/') {
+ lu++;
+ }
+ return lu;
+}
+
+/* Obtain the Request-URI from the original request-line, returning
+ * a new string from the request pool containing the URI or "".
+ */
+static char *original_uri(request_rec *r)
+{
+ char *first, *last;
+
+ if (r->the_request == NULL) {
+ return (char *) apr_pcalloc(r->pool, 1);
+ }
+
+ first = r->the_request; /* use the request-line */
+
+ while (*first && !apr_isspace(*first)) {
+ ++first; /* skip over the method */
+ }
+ while (apr_isspace(*first)) {
+ ++first; /* and the space(s) */
+ }
+
+ last = first;
+ while (*last && !apr_isspace(*last)) {
+ ++last; /* end at next whitespace */
+ }
+
+ return apr_pstrmemdup(r->pool, first, last - first);
+}
+
+AP_DECLARE(void) ap_add_cgi_vars(request_rec *r)
+{
+ apr_table_t *e = r->subprocess_env;
+
+ apr_table_setn(e, "GATEWAY_INTERFACE", "CGI/1.1");
+ apr_table_setn(e, "SERVER_PROTOCOL", r->protocol);
+ apr_table_setn(e, "REQUEST_METHOD", r->method);
+ apr_table_setn(e, "QUERY_STRING", r->args ? r->args : "");
+ apr_table_setn(e, "REQUEST_URI", original_uri(r));
+
+ /* Note that the code below special-cases scripts run from includes,
+ * because it "knows" that the sub_request has been hacked to have the
+ * args and path_info of the original request, and not any that may have
+ * come with the script URI in the include command. Ugh.
+ */
+
+ if (!strcmp(r->protocol, "INCLUDED")) {
+ apr_table_setn(e, "SCRIPT_NAME", r->uri);
+ if (r->path_info && *r->path_info) {
+ apr_table_setn(e, "PATH_INFO", r->path_info);
+ }
+ }
+ else if (!r->path_info || !*r->path_info) {
+ apr_table_setn(e, "SCRIPT_NAME", r->uri);
+ }
+ else {
+ int path_info_start = ap_find_path_info(r->uri, r->path_info);
+
+ apr_table_setn(e, "SCRIPT_NAME",
+ apr_pstrndup(r->pool, r->uri, path_info_start));
+
+ apr_table_setn(e, "PATH_INFO", r->path_info);
+ }
+
+ if (r->path_info && r->path_info[0]) {
+ /*
+ * To get PATH_TRANSLATED, treat PATH_INFO as a URI path.
+ * Need to re-escape it for this, since the entire URI was
+ * un-escaped before we determined where the PATH_INFO began.
+ */
+ request_rec *pa_req;
+
+ pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r,
+ NULL);
+
+ if (pa_req->filename) {
+ char *pt = apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info,
+ NULL);
+#ifdef WIN32
+ /* We need to make this a real Windows path name */
+ apr_filepath_merge(&pt, "", pt, APR_FILEPATH_NATIVE, r->pool);
+#endif
+ apr_table_setn(e, "PATH_TRANSLATED", pt);
+ }
+ ap_destroy_sub_req(pa_req);
+ }
+}
+
+
+static int set_cookie_doo_doo(void *v, const char *key, const char *val)
+{
+ apr_table_addn(v, key, val);
+ return 1;
+}
+
+#define HTTP_UNSET (-HTTP_OK)
+
+AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer,
+ int (*getsfunc) (char *, int, void *),
+ void *getsfunc_data)
+{
+ char x[MAX_STRING_LEN];
+ char *w, *l;
+ int p;
+ int cgi_status = HTTP_UNSET;
+ apr_table_t *merge;
+ apr_table_t *cookie_table;
+
+ if (buffer) {
+ *buffer = '\0';
+ }
+ w = buffer ? buffer : x;
+
+ /* temporary place to hold headers to merge in later */
+ merge = apr_table_make(r->pool, 10);
+
+ /* The HTTP specification says that it is legal to merge duplicate
+ * headers into one. Some browsers that support Cookies don't like
+ * merged headers and prefer that each Set-Cookie header is sent
+ * separately. Lets humour those browsers by not merging.
+ * Oh what a pain it is.
+ */
+ cookie_table = apr_table_make(r->pool, 2);
+ apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out, "Set-Cookie", NULL);
+
+ while (1) {
+
+ if ((*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data) == 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
+ "Premature end of script headers: %s",
+ apr_filepath_name_get(r->filename));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Delete terminal (CR?)LF */
+
+ p = strlen(w);
+ /* Indeed, the host's '\n':
+ '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
+ -- whatever the script generates.
+ */
+ if (p > 0 && w[p - 1] == '\n') {
+ if (p > 1 && w[p - 2] == CR) {
+ w[p - 2] = '\0';
+ }
+ else {
+ w[p - 1] = '\0';
+ }
+ }
+
+ /*
+ * If we've finished reading the headers, check to make sure any
+ * HTTP/1.1 conditions are met. If so, we're done; normal processing
+ * will handle the script's output. If not, just return the error.
+ * The appropriate thing to do would be to send the script process a
+ * SIGPIPE to let it know we're ignoring it, close the channel to the
+ * script process, and *then* return the failed-to-meet-condition
+ * error. Otherwise we'd be waiting for the script to finish
+ * blithering before telling the client the output was no good.
+ * However, we don't have the information to do that, so we have to
+ * leave it to an upper layer.
+ */
+ if (w[0] == '\0') {
+ int cond_status = OK;
+
+ /* PR#38070: This fails because it gets confused when a
+ * CGI Status header overrides ap_meets_conditions.
+ *
+ * We can fix that by dropping ap_meets_conditions when
+ * Status has been set. Since this is the only place
+ * cgi_status gets used, let's test it explicitly.
+ *
+ * The alternative would be to ignore CGI Status when
+ * ap_meets_conditions returns anything interesting.
+ * That would be safer wrt HTTP, but would break CGI.
+ */
+ if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) {
+ cond_status = ap_meets_conditions(r);
+ }
+ apr_table_overlap(r->err_headers_out, merge,
+ APR_OVERLAP_TABLES_MERGE);
+ if (!apr_is_empty_table(cookie_table)) {
+ /* the cookies have already been copied to the cookie_table */
+ apr_table_unset(r->err_headers_out, "Set-Cookie");
+ r->err_headers_out = apr_table_overlay(r->pool,
+ r->err_headers_out, cookie_table);
+ }
+ return cond_status;
+ }
+
+ /* if we see a bogus header don't ignore it. Shout and scream */
+
+#if APR_CHARSET_EBCDIC
+ /* Chances are that we received an ASCII header text instead of
+ * the expected EBCDIC header lines. Try to auto-detect:
+ */
+ if (!(l = strchr(w, ':'))) {
+ int maybeASCII = 0, maybeEBCDIC = 0;
+ unsigned char *cp, native;
+ apr_size_t inbytes_left, outbytes_left;
+
+ for (cp = w; *cp != '\0'; ++cp) {
+ native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
+ if (apr_isprint(*cp) && !apr_isprint(native))
+ ++maybeEBCDIC;
+ if (!apr_isprint(*cp) && apr_isprint(native))
+ ++maybeASCII;
+ }
+ if (maybeASCII > maybeEBCDIC) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
+ r->filename);
+ inbytes_left = outbytes_left = cp - w;
+ apr_xlate_conv_buffer(ap_hdrs_from_ascii,
+ w, &inbytes_left, w, &outbytes_left);
+ }
+ }
+#endif /*APR_CHARSET_EBCDIC*/
+ if (!(l = strchr(w, ':'))) {
+ char malformed[(sizeof MALFORMED_MESSAGE) + 1
+ + MALFORMED_HEADER_LENGTH_TO_SHOW];
+
+ strcpy(malformed, MALFORMED_MESSAGE);
+ strncat(malformed, w, MALFORMED_HEADER_LENGTH_TO_SHOW);
+
+ if (!buffer) {
+ /* Soak up all the script output - may save an outright kill */
+ while ((*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data)) {
+ continue;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
+ "%s: %s", malformed,
+ apr_filepath_name_get(r->filename));
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *l++ = '\0';
+ while (*l && apr_isspace(*l)) {
+ ++l;
+ }
+
+ if (!strcasecmp(w, "Content-type")) {
+ char *tmp;
+
+ /* Nuke trailing whitespace */
+
+ char *endp = l + strlen(l) - 1;
+ while (endp > l && apr_isspace(*endp)) {
+ *endp-- = '\0';
+ }
+
+ tmp = apr_pstrdup(r->pool, l);
+ ap_content_type_tolower(tmp);
+ ap_set_content_type(r, tmp);
+ }
+ /*
+ * If the script returned a specific status, that's what
+ * we'll use - otherwise we assume 200 OK.
+ */
+ else if (!strcasecmp(w, "Status")) {
+ r->status = cgi_status = atoi(l);
+ r->status_line = apr_pstrdup(r->pool, l);
+ }
+ else if (!strcasecmp(w, "Location")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!strcasecmp(w, "Content-Length")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!strcasecmp(w, "Content-Range")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ else if (!strcasecmp(w, "Transfer-Encoding")) {
+ apr_table_set(r->headers_out, w, l);
+ }
+ /*
+ * If the script gave us a Last-Modified header, we can't just
+ * pass it on blindly because of restrictions on future values.
+ */
+ else if (!strcasecmp(w, "Last-Modified")) {
+ ap_update_mtime(r, apr_date_parse_http(l));
+ ap_set_last_modified(r);
+ }
+ else if (!strcasecmp(w, "Set-Cookie")) {
+ apr_table_add(cookie_table, w, l);
+ }
+ else {
+ apr_table_add(merge, w, l);
+ }
+ }
+
+ return OK;
+}
+
+static int getsfunc_FILE(char *buf, int len, void *f)
+{
+ return apr_file_gets(buf, len, (apr_file_t *) f) == APR_SUCCESS;
+}
+
+AP_DECLARE(int) ap_scan_script_header_err(request_rec *r, apr_file_t *f,
+ char *buffer)
+{
+ return ap_scan_script_header_err_core(r, buffer, getsfunc_FILE, f);
+}
+
+static int getsfunc_BRIGADE(char *buf, int len, void *arg)
+{
+ apr_bucket_brigade *bb = (apr_bucket_brigade *)arg;
+ const char *dst_end = buf + len - 1; /* leave room for terminating null */
+ char *dst = buf;
+ apr_bucket *e = APR_BRIGADE_FIRST(bb);
+ apr_status_t rv;
+ int done = 0;
+
+ while ((dst < dst_end) && !done && !APR_BUCKET_IS_EOS(e)) {
+ const char *bucket_data;
+ apr_size_t bucket_data_len;
+ const char *src;
+ const char *src_end;
+ apr_bucket * next;
+
+ rv = apr_bucket_read(e, &bucket_data, &bucket_data_len,
+ APR_BLOCK_READ);
+ if (rv != APR_SUCCESS || (bucket_data_len == 0)) {
+ return 0;
+ }
+ src = bucket_data;
+ src_end = bucket_data + bucket_data_len;
+ while ((src < src_end) && (dst < dst_end) && !done) {
+ if (*src == '\n') {
+ done = 1;
+ }
+ else if (*src != '\r') {
+ *dst++ = *src;
+ }
+ src++;
+ }
+
+ if (src < src_end) {
+ apr_bucket_split(e, src - bucket_data);
+ }
+ next = APR_BUCKET_NEXT(e);
+ APR_BUCKET_REMOVE(e);
+ apr_bucket_destroy(e);
+ e = next;
+ }
+ *dst = 0;
+ return 1;
+}
+
+AP_DECLARE(int) ap_scan_script_header_err_brigade(request_rec *r,
+ apr_bucket_brigade *bb,
+ char *buffer)
+{
+ return ap_scan_script_header_err_core(r, buffer, getsfunc_BRIGADE, bb);
+}
+
+struct vastrs {
+ va_list args;
+ int arg;
+ const char *curpos;
+};
+
+static int getsfunc_STRING(char *w, int len, void *pvastrs)
+{
+ struct vastrs *strs = (struct vastrs*) pvastrs;
+ const char *p;
+ int t;
+
+ if (!strs->curpos || !*strs->curpos)
+ return 0;
+ p = ap_strchr_c(strs->curpos, '\n');
+ if (p)
+ ++p;
+ else
+ p = ap_strchr_c(strs->curpos, '\0');
+ t = p - strs->curpos;
+ if (t > len)
+ t = len;
+ strncpy (w, strs->curpos, t);
+ w[t] = '\0';
+ if (!strs->curpos[t]) {
+ ++strs->arg;
+ strs->curpos = va_arg(strs->args, const char *);
+ }
+ else
+ strs->curpos += t;
+ return t;
+}
+
+/* ap_scan_script_header_err_strs() accepts additional const char* args...
+ * each is treated as one or more header lines, and the first non-header
+ * character is returned to **arg, **data. (The first optional arg is
+ * counted as 0.)
+ */
+AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r,
+ char *buffer,
+ const char **termch,
+ int *termarg, ...)
+{
+ struct vastrs strs;
+ int res;
+
+ va_start(strs.args, termarg);
+ strs.arg = 0;
+ strs.curpos = va_arg(strs.args, char*);
+ res = ap_scan_script_header_err_core(r, buffer, getsfunc_STRING, (void *) &strs);
+ if (termch)
+ *termch = strs.curpos;
+ if (termarg)
+ *termarg = strs.arg;
+ va_end(strs.args);
+ return res;
+}
diff --git a/server/util_time.c b/server/util_time.c
new file mode 100644
index 00000000..172cfa52
--- /dev/null
+++ b/server/util_time.c
@@ -0,0 +1,240 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "util_time.h"
+
+/* Cache for exploded values of recent timestamps
+ */
+
+struct exploded_time_cache_element {
+ apr_int64_t t;
+ apr_time_exp_t xt;
+ apr_int64_t t_validate; /* please see comments in cached_explode() */
+};
+
+/* the "+ 1" is for the current second: */
+#define TIME_CACHE_SIZE (AP_TIME_RECENT_THRESHOLD + 1)
+
+/* Note that AP_TIME_RECENT_THRESHOLD is defined to
+ * be a power of two minus one in util_time.h, so that
+ * we can replace a modulo operation with a bitwise AND
+ * when hashing items into a cache of size
+ * AP_TIME_RECENT_THRESHOLD+1
+ */
+#define TIME_CACHE_MASK (AP_TIME_RECENT_THRESHOLD)
+
+static struct exploded_time_cache_element exploded_cache_localtime[TIME_CACHE_SIZE];
+static struct exploded_time_cache_element exploded_cache_gmt[TIME_CACHE_SIZE];
+
+
+static apr_status_t cached_explode(apr_time_exp_t *xt, apr_time_t t,
+ struct exploded_time_cache_element *cache,
+ int use_gmt)
+{
+ apr_int64_t seconds = apr_time_sec(t);
+ struct exploded_time_cache_element *cache_element =
+ &(cache[seconds & TIME_CACHE_MASK]);
+ struct exploded_time_cache_element cache_element_snapshot;
+
+ /* The cache is implemented as a ring buffer. Each second,
+ * it uses a different element in the buffer. The timestamp
+ * in the element indicates whether the element contains the
+ * exploded time for the current second (vs the time
+ * 'now - AP_TIME_RECENT_THRESHOLD' seconds ago). If the
+ * cached value is for the current time, we use it. Otherwise,
+ * we compute the apr_time_exp_t and store it in this
+ * cache element. Note that the timestamp in the cache
+ * element is updated only after the exploded time. Thus
+ * if two threads hit this cache element simultaneously
+ * at the start of a new second, they'll both explode the
+ * time and store it. I.e., the writers will collide, but
+ * they'll be writing the same value.
+ */
+ if (cache_element->t >= seconds) {
+ /* There is an intentional race condition in this design:
+ * in a multithreaded app, one thread might be reading
+ * from this cache_element to resolve a timestamp from
+ * TIME_CACHE_SIZE seconds ago at the same time that
+ * another thread is copying the exploded form of the
+ * current time into the same cache_element. (I.e., the
+ * first thread might hit this element of the ring buffer
+ * just as the element is being recycled.) This can
+ * also happen at the start of a new second, if a
+ * reader accesses the cache_element after a writer
+ * has updated cache_element.t but before the writer
+ * has finished updating the whole cache_element.
+ *
+ * Rather than trying to prevent this race condition
+ * with locks, we allow it to happen and then detect
+ * and correct it. The detection works like this:
+ * Step 1: Take a "snapshot" of the cache element by
+ * copying it into a temporary buffer.
+ * Step 2: Check whether the snapshot contains consistent
+ * data: the timestamps at the start and end of
+ * the cache_element should both match the 'seconds'
+ * value that we computed from the input time.
+ * If these three don't match, then the snapshot
+ * shows the cache_element in the middle of an
+ * update, and its contents are invalid.
+ * Step 3: If the snapshot is valid, use it. Otherwise,
+ * just give up on the cache and explode the
+ * input time.
+ */
+ memcpy(&cache_element_snapshot, cache_element,
+ sizeof(struct exploded_time_cache_element));
+ if ((seconds != cache_element_snapshot.t) ||
+ (seconds != cache_element_snapshot.t_validate)) {
+ /* Invalid snapshot */
+ if (use_gmt) {
+ return apr_time_exp_gmt(xt, t);
+ }
+ else {
+ return apr_time_exp_lt(xt, t);
+ }
+ }
+ else {
+ /* Valid snapshot */
+ memcpy(xt, &(cache_element_snapshot.xt),
+ sizeof(apr_time_exp_t));
+ }
+ }
+ else {
+ apr_status_t r;
+ if (use_gmt) {
+ r = apr_time_exp_gmt(xt, t);
+ }
+ else {
+ r = apr_time_exp_lt(xt, t);
+ }
+ if (r != APR_SUCCESS) {
+ return r;
+ }
+ cache_element->t = seconds;
+ memcpy(&(cache_element->xt), xt, sizeof(apr_time_exp_t));
+ cache_element->t_validate = seconds;
+ }
+ xt->tm_usec = (int)apr_time_usec(t);
+ return APR_SUCCESS;
+}
+
+
+AP_DECLARE(apr_status_t) ap_explode_recent_localtime(apr_time_exp_t * tm,
+ apr_time_t t)
+{
+ return cached_explode(tm, t, exploded_cache_localtime, 0);
+}
+
+AP_DECLARE(apr_status_t) ap_explode_recent_gmt(apr_time_exp_t * tm,
+ apr_time_t t)
+{
+ return cached_explode(tm, t, exploded_cache_gmt, 1);
+}
+
+AP_DECLARE(apr_status_t) ap_recent_ctime(char *date_str, apr_time_t t)
+{
+ /* ### This code is a clone of apr_ctime(), except that it
+ * uses ap_explode_recent_localtime() instead of apr_time_exp_lt().
+ */
+ apr_time_exp_t xt;
+ const char *s;
+ int real_year;
+
+ /* example: "Wed Jun 30 21:49:08 1993" */
+ /* 123456789012345678901234 */
+
+ ap_explode_recent_localtime(&xt, t);
+ s = &apr_day_snames[xt.tm_wday][0];
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = ' ';
+ s = &apr_month_snames[xt.tm_mon][0];
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = ' ';
+ *date_str++ = xt.tm_mday / 10 + '0';
+ *date_str++ = xt.tm_mday % 10 + '0';
+ *date_str++ = ' ';
+ *date_str++ = xt.tm_hour / 10 + '0';
+ *date_str++ = xt.tm_hour % 10 + '0';
+ *date_str++ = ':';
+ *date_str++ = xt.tm_min / 10 + '0';
+ *date_str++ = xt.tm_min % 10 + '0';
+ *date_str++ = ':';
+ *date_str++ = xt.tm_sec / 10 + '0';
+ *date_str++ = xt.tm_sec % 10 + '0';
+ *date_str++ = ' ';
+ real_year = 1900 + xt.tm_year;
+ *date_str++ = real_year / 1000 + '0';
+ *date_str++ = real_year % 1000 / 100 + '0';
+ *date_str++ = real_year % 100 / 10 + '0';
+ *date_str++ = real_year % 10 + '0';
+ *date_str++ = 0;
+
+ return APR_SUCCESS;
+}
+
+AP_DECLARE(apr_status_t) ap_recent_rfc822_date(char *date_str, apr_time_t t)
+{
+ /* ### This code is a clone of apr_rfc822_date(), except that it
+ * uses ap_explode_recent_gmt() instead of apr_time_exp_gmt().
+ */
+ apr_time_exp_t xt;
+ const char *s;
+ int real_year;
+
+ ap_explode_recent_gmt(&xt, t);
+
+ /* example: "Sat, 08 Jan 2000 18:31:41 GMT" */
+ /* 12345678901234567890123456789 */
+
+ s = &apr_day_snames[xt.tm_wday][0];
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = ',';
+ *date_str++ = ' ';
+ *date_str++ = xt.tm_mday / 10 + '0';
+ *date_str++ = xt.tm_mday % 10 + '0';
+ *date_str++ = ' ';
+ s = &apr_month_snames[xt.tm_mon][0];
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = *s++;
+ *date_str++ = ' ';
+ real_year = 1900 + xt.tm_year;
+ /* This routine isn't y10k ready. */
+ *date_str++ = real_year / 1000 + '0';
+ *date_str++ = real_year % 1000 / 100 + '0';
+ *date_str++ = real_year % 100 / 10 + '0';
+ *date_str++ = real_year % 10 + '0';
+ *date_str++ = ' ';
+ *date_str++ = xt.tm_hour / 10 + '0';
+ *date_str++ = xt.tm_hour % 10 + '0';
+ *date_str++ = ':';
+ *date_str++ = xt.tm_min / 10 + '0';
+ *date_str++ = xt.tm_min % 10 + '0';
+ *date_str++ = ':';
+ *date_str++ = xt.tm_sec / 10 + '0';
+ *date_str++ = xt.tm_sec % 10 + '0';
+ *date_str++ = ' ';
+ *date_str++ = 'G';
+ *date_str++ = 'M';
+ *date_str++ = 'T';
+ *date_str++ = 0;
+ return APR_SUCCESS;
+}
diff --git a/server/util_xml.c b/server/util_xml.c
new file mode 100644
index 00000000..dc88b3dd
--- /dev/null
+++ b/server/util_xml.c
@@ -0,0 +1,135 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_xml.h"
+
+#include "httpd.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_core.h"
+
+#include "util_charset.h"
+#include "util_xml.h"
+
+
+/* used for reading input blocks */
+#define READ_BLOCKSIZE 2048
+
+
+AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc)
+{
+ apr_xml_parser *parser;
+ apr_bucket_brigade *brigade;
+ int seen_eos;
+ apr_status_t status;
+ char errbuf[200];
+ apr_size_t total_read = 0;
+ apr_size_t limit_xml_body = ap_get_limit_xml_body(r);
+ int result = HTTP_BAD_REQUEST;
+
+ parser = apr_xml_parser_create(r->pool);
+ brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ seen_eos = 0;
+ total_read = 0;
+
+ do {
+ apr_bucket *bucket;
+
+ /* read the body, stuffing it into the parser */
+ status = ap_get_brigade(r->input_filters, brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ READ_BLOCKSIZE);
+
+ if (status != APR_SUCCESS) {
+ goto read_error;
+ }
+
+ for (bucket = APR_BRIGADE_FIRST(brigade);
+ bucket != APR_BRIGADE_SENTINEL(brigade);
+ bucket = APR_BUCKET_NEXT(bucket))
+ {
+ const char *data;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(bucket)) {
+ seen_eos = 1;
+ break;
+ }
+
+ if (APR_BUCKET_IS_METADATA(bucket)) {
+ continue;
+ }
+
+ status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ goto read_error;
+ }
+
+ total_read += len;
+ if (limit_xml_body && total_read > limit_xml_body) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "XML request body is larger than the configured "
+ "limit of %lu", (unsigned long)limit_xml_body);
+ result = HTTP_REQUEST_ENTITY_TOO_LARGE;
+ goto read_error;
+ }
+
+ status = apr_xml_parser_feed(parser, data, len);
+ if (status) {
+ goto parser_error;
+ }
+ }
+
+ apr_brigade_cleanup(brigade);
+ } while (!seen_eos);
+
+ apr_brigade_destroy(brigade);
+
+ /* tell the parser that we're done */
+ status = apr_xml_parser_done(parser, pdoc);
+ if (status) {
+ /* Some parsers are stupid and return an error on blank documents. */
+ if (!total_read) {
+ *pdoc = NULL;
+ return OK;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "XML parser error (at end). status=%d", status);
+ return HTTP_BAD_REQUEST;
+ }
+
+#if APR_CHARSET_EBCDIC
+ apr_xml_parser_convert_doc(r->pool, *pdoc, ap_hdrs_from_ascii);
+#endif
+ return OK;
+
+ parser_error:
+ (void) apr_xml_parser_geterror(parser, errbuf, sizeof(errbuf));
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "XML Parser Error: %s", errbuf);
+
+ /* FALLTHRU */
+
+ read_error:
+ /* make sure the parser is terminated */
+ (void) apr_xml_parser_done(parser, NULL);
+
+ apr_brigade_destroy(brigade);
+
+ /* Apache will supply a default error, plus the error log above. */
+ return result;
+}
diff --git a/server/vhost.c b/server/vhost.c
new file mode 100644
index 00000000..d7a0a07e
--- /dev/null
+++ b/server/vhost.c
@@ -0,0 +1,1072 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file vhost.c
+ * @brief functions pertaining to virtual host addresses
+ * (configuration and run-time)
+ */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#define CORE_PRIVATE
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_vhost.h"
+#include "http_protocol.h"
+#include "http_core.h"
+
+#if APR_HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+/*
+ * After all the definitions there's an explanation of how it's all put
+ * together.
+ */
+
+/* meta-list of name-vhosts. Each server_rec can be in possibly multiple
+ * lists of name-vhosts.
+ */
+typedef struct name_chain name_chain;
+struct name_chain {
+ name_chain *next;
+ server_addr_rec *sar; /* the record causing it to be in
+ * this chain (needed for port comparisons) */
+ server_rec *server; /* the server to use on a match */
+};
+
+/* meta-list of ip addresses. Each server_rec can be in possibly multiple
+ * hash chains since it can have multiple ips.
+ */
+typedef struct ipaddr_chain ipaddr_chain;
+struct ipaddr_chain {
+ ipaddr_chain *next;
+ server_addr_rec *sar; /* the record causing it to be in
+ * this chain (need for both ip addr and port
+ * comparisons) */
+ server_rec *server; /* the server to use if this matches */
+ name_chain *names; /* if non-NULL then a list of name-vhosts
+ * sharing this address */
+};
+
+/* This defines the size of the hash table used for hashing ip addresses
+ * of virtual hosts. It must be a power of two.
+ */
+#ifndef IPHASH_TABLE_SIZE
+#define IPHASH_TABLE_SIZE 256
+#endif
+
+/* A (n) bucket hash table, each entry has a pointer to a server rec and
+ * a pointer to the other entries in that bucket. Each individual address,
+ * even for virtualhosts with multiple addresses, has an entry in this hash
+ * table. There are extra buckets for _default_, and name-vhost entries.
+ *
+ * Note that after config time this is constant, so it is thread-safe.
+ */
+static ipaddr_chain *iphash_table[IPHASH_TABLE_SIZE];
+
+/* dump out statistics about the hash function */
+/* #define IPHASH_STATISTICS */
+
+/* list of the _default_ servers */
+static ipaddr_chain *default_list;
+
+/* list of the NameVirtualHost addresses */
+static server_addr_rec *name_vhost_list;
+static server_addr_rec **name_vhost_list_tail;
+
+/*
+ * How it's used:
+ *
+ * The ip address determines which chain in iphash_table is interesting, then
+ * a comparison is done down that chain to find the first ipaddr_chain whose
+ * sar matches the address:port pair.
+ *
+ * If that ipaddr_chain has names == NULL then you're done, it's an ip-vhost.
+ *
+ * Otherwise it's a name-vhost list, and the default is the server in the
+ * ipaddr_chain record. We tuck away the ipaddr_chain record in the
+ * conn_rec field vhost_lookup_data. Later on after the headers we get a
+ * second chance, and we use the name_chain to figure out what name-vhost
+ * matches the headers.
+ *
+ * If there was no ip address match in the iphash_table then do a lookup
+ * in the default_list.
+ *
+ * How it's put together ... well you should be able to figure that out
+ * from how it's used. Or something like that.
+ */
+
+
+/* called at the beginning of the config */
+AP_DECLARE(void) ap_init_vhost_config(apr_pool_t *p)
+{
+ memset(iphash_table, 0, sizeof(iphash_table));
+ default_list = NULL;
+ name_vhost_list = NULL;
+ name_vhost_list_tail = &name_vhost_list;
+}
+
+
+/*
+ * Parses a host of the form <address>[:port]
+ * paddr is used to create a list in the order of input
+ * **paddr is the ->next pointer of the last entry (or s->addrs)
+ * *paddr is the variable used to keep track of **paddr between calls
+ * port is the default port to assume
+ */
+static const char *get_addresses(apr_pool_t *p, const char *w_,
+ server_addr_rec ***paddr,
+ apr_port_t default_port)
+{
+ apr_sockaddr_t *my_addr;
+ server_addr_rec *sar;
+ char *w, *host, *scope_id;
+ int wild_port;
+ apr_size_t wlen;
+ apr_port_t port;
+ apr_status_t rv;
+
+ if (*w_ == '\0')
+ return NULL;
+
+ w = apr_pstrdup(p, w_);
+ /* apr_parse_addr_port() doesn't understand ":*" so handle that first. */
+ wlen = strlen(w); /* wlen must be > 0 at this point */
+ wild_port = 0;
+ if (w[wlen - 1] == '*') {
+ if (wlen < 2) {
+ wild_port = 1;
+ }
+ else if (w[wlen - 2] == ':') {
+ w[wlen - 2] = '\0';
+ wild_port = 1;
+ }
+ }
+ rv = apr_parse_addr_port(&host, &scope_id, &port, w, p);
+ /* If the string is "80", apr_parse_addr_port() will be happy and set
+ * host to NULL and port to 80, so watch out for that.
+ */
+ if (rv != APR_SUCCESS) {
+ return "The address or port is invalid";
+ }
+ if (!host) {
+ return "Missing address for VirtualHost";
+ }
+ if (scope_id) {
+ return "Scope ids are not supported";
+ }
+ if (!port && !wild_port) {
+ port = default_port;
+ }
+
+ if (strcmp(host, "*") == 0) {
+ rv = apr_sockaddr_info_get(&my_addr, "0.0.0.0", APR_INET, port, 0, p);
+ if (rv) {
+ return "Could not resolve address '0.0.0.0' -- "
+ "check resolver configuration.";
+ }
+ }
+ else if (strcasecmp(host, "_default_") == 0
+ || strcmp(host, "255.255.255.255") == 0) {
+ rv = apr_sockaddr_info_get(&my_addr, "255.255.255.255", APR_INET, port, 0, p);
+ if (rv) {
+ return "Could not resolve address '255.255.255.255' -- "
+ "check resolver configuration.";
+ }
+ }
+ else {
+ rv = apr_sockaddr_info_get(&my_addr, host, APR_UNSPEC, port, 0, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
+ "Could not resolve host name %s -- ignoring!", host);
+ return NULL;
+ }
+ }
+
+ /* Remember all addresses for the host */
+
+ do {
+ sar = apr_pcalloc(p, sizeof(server_addr_rec));
+ **paddr = sar;
+ *paddr = &sar->next;
+ sar->host_addr = my_addr;
+ sar->host_port = port;
+ sar->virthost = host;
+ my_addr = my_addr->next;
+ } while (my_addr);
+
+ return NULL;
+}
+
+
+/* parse the <VirtualHost> addresses */
+const char *ap_parse_vhost_addrs(apr_pool_t *p,
+ const char *hostname,
+ server_rec *s)
+{
+ server_addr_rec **addrs;
+ const char *err;
+
+ /* start the list of addreses */
+ addrs = &s->addrs;
+ while (hostname[0]) {
+ err = get_addresses(p, ap_getword_conf(p, &hostname), &addrs, s->port);
+ if (err) {
+ *addrs = NULL;
+ return err;
+ }
+ }
+ /* terminate the list */
+ *addrs = NULL;
+ if (s->addrs) {
+ if (s->addrs->host_port) {
+ /* override the default port which is inherited from main_server */
+ s->port = s->addrs->host_port;
+ }
+ }
+ return NULL;
+}
+
+
+const char *ap_set_name_virtual_host(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ /* use whatever port the main server has at this point */
+ return get_addresses(cmd->pool, arg, &name_vhost_list_tail,
+ cmd->server->port);
+}
+
+
+/* hash table statistics, keep this in here for the beta period so
+ * we can find out if the hash function is ok
+ */
+#ifdef IPHASH_STATISTICS
+static int iphash_compare(const void *a, const void *b)
+{
+ return (*(const int *) b - *(const int *) a);
+}
+
+
+static void dump_iphash_statistics(server_rec *main_s)
+{
+ unsigned count[IPHASH_TABLE_SIZE];
+ int i;
+ ipaddr_chain *src;
+ unsigned total;
+ char buf[HUGE_STRING_LEN];
+ char *p;
+
+ total = 0;
+ for (i = 0; i < IPHASH_TABLE_SIZE; ++i) {
+ count[i] = 0;
+ for (src = iphash_table[i]; src; src = src->next) {
+ ++count[i];
+ if (i < IPHASH_TABLE_SIZE) {
+ /* don't count the slop buckets in the total */
+ ++total;
+ }
+ }
+ }
+ qsort(count, IPHASH_TABLE_SIZE, sizeof(count[0]), iphash_compare);
+ p = buf + apr_snprintf(buf, sizeof(buf),
+ "iphash: total hashed = %u, avg chain = %u, "
+ "chain lengths (count x len):",
+ total, total / IPHASH_TABLE_SIZE);
+ total = 1;
+ for (i = 1; i < IPHASH_TABLE_SIZE; ++i) {
+ if (count[i - 1] != count[i]) {
+ p += apr_snprintf(p, sizeof(buf) - (p - buf), " %ux%u",
+ total, count[i - 1]);
+ total = 1;
+ }
+ else {
+ ++total;
+ }
+ }
+ p += apr_snprintf(p, sizeof(buf) - (p - buf), " %ux%u",
+ total, count[IPHASH_TABLE_SIZE - 1]);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, main_s, buf);
+}
+#endif
+
+
+/* This hashing function is designed to get good distribution in the cases
+ * where the server is handling entire "networks" of servers. i.e. a
+ * whack of /24s. This is probably the most common configuration for
+ * ISPs with large virtual servers.
+ *
+ * NOTE: This function is symmetric (i.e. collapses all 4 octets
+ * into one), so machine byte order (big/little endianness) does not matter.
+ *
+ * Hash function provided by David Hankins.
+ */
+static APR_INLINE unsigned hash_inaddr(unsigned key)
+{
+ key ^= (key >> 16);
+ return ((key >> 8) ^ key) % IPHASH_TABLE_SIZE;
+}
+
+static APR_INLINE unsigned hash_addr(struct apr_sockaddr_t *sa)
+{
+ unsigned key;
+
+ /* The key is the last four bytes of the IP address.
+ * For IPv4, this is the entire address, as always.
+ * For IPv6, this is usually part of the MAC address.
+ */
+ key = *(unsigned *)((char *)sa->ipaddr_ptr + sa->ipaddr_len - 4);
+ return hash_inaddr(key);
+}
+
+static ipaddr_chain *new_ipaddr_chain(apr_pool_t *p,
+ server_rec *s, server_addr_rec *sar)
+{
+ ipaddr_chain *new;
+
+ new = apr_palloc(p, sizeof(*new));
+ new->names = NULL;
+ new->server = s;
+ new->sar = sar;
+ new->next = NULL;
+ return new;
+}
+
+
+static name_chain *new_name_chain(apr_pool_t *p,
+ server_rec *s, server_addr_rec *sar)
+{
+ name_chain *new;
+
+ new = apr_palloc(p, sizeof(*new));
+ new->server = s;
+ new->sar = sar;
+ new->next = NULL;
+ return new;
+}
+
+
+static APR_INLINE ipaddr_chain *find_ipaddr(apr_sockaddr_t *sa)
+{
+ unsigned bucket;
+ ipaddr_chain *trav;
+
+ /* scan the hash table for an exact match first */
+ bucket = hash_addr(sa);
+ for (trav = iphash_table[bucket]; trav; trav = trav->next) {
+ server_addr_rec *sar = trav->sar;
+ apr_sockaddr_t *cur = sar->host_addr;
+
+ if (cur->port == 0 || sa->port == 0 || cur->port == sa->port) {
+ if (apr_sockaddr_equal(cur, sa)) {
+ return trav;
+ }
+ }
+ }
+ return NULL;
+}
+
+static ipaddr_chain *find_default_server(apr_port_t port)
+{
+ server_addr_rec *sar;
+ ipaddr_chain *trav;
+
+ for (trav = default_list; trav; trav = trav->next) {
+ sar = trav->sar;
+ if (sar->host_port == 0 || sar->host_port == port) {
+ /* match! */
+ return trav;
+ }
+ }
+ return NULL;
+}
+
+static void dump_a_vhost(apr_file_t *f, ipaddr_chain *ic)
+{
+ name_chain *nc;
+ int len;
+ char buf[MAX_STRING_LEN];
+ apr_sockaddr_t *ha = ic->sar->host_addr;
+
+ if (ha->family == APR_INET &&
+ ha->sa.sin.sin_addr.s_addr == DEFAULT_VHOST_ADDR) {
+ len = apr_snprintf(buf, sizeof(buf), "_default_:%u",
+ ic->sar->host_port);
+ }
+ else if (ha->family == APR_INET &&
+ ha->sa.sin.sin_addr.s_addr == INADDR_ANY) {
+ len = apr_snprintf(buf, sizeof(buf), "*:%u",
+ ic->sar->host_port);
+ }
+ else {
+ len = apr_snprintf(buf, sizeof(buf), "%pI", ha);
+ }
+ if (ic->sar->host_port == 0) {
+ buf[len-1] = '*';
+ }
+ if (ic->names == NULL) {
+ apr_file_printf(f, "%-22s %s (%s:%u)\n", buf,
+ ic->server->server_hostname,
+ ic->server->defn_name, ic->server->defn_line_number);
+ return;
+ }
+ apr_file_printf(f, "%-22s is a NameVirtualHost\n"
+ "%8s default server %s (%s:%u)\n",
+ buf, "", ic->server->server_hostname,
+ ic->server->defn_name, ic->server->defn_line_number);
+ for (nc = ic->names; nc; nc = nc->next) {
+ if (nc->sar->host_port) {
+ apr_file_printf(f, "%8s port %u ", "", nc->sar->host_port);
+ }
+ else {
+ apr_file_printf(f, "%8s port * ", "");
+ }
+ apr_file_printf(f, "namevhost %s (%s:%u)\n",
+ nc->server->server_hostname,
+ nc->server->defn_name, nc->server->defn_line_number);
+ }
+}
+
+static void dump_vhost_config(apr_file_t *f)
+{
+ ipaddr_chain *ic;
+ int i;
+
+ apr_file_printf(f, "VirtualHost configuration:\n");
+ for (i = 0; i < IPHASH_TABLE_SIZE; ++i) {
+ for (ic = iphash_table[i]; ic; ic = ic->next) {
+ dump_a_vhost(f, ic);
+ }
+ }
+ if (default_list) {
+ apr_file_printf(f, "wildcard NameVirtualHosts and _default_ servers:\n");
+ for (ic = default_list; ic; ic = ic->next) {
+ dump_a_vhost(f, ic);
+ }
+ }
+}
+
+/*
+ * Two helper functions for ap_fini_vhost_config()
+ */
+static int add_name_vhost_config(apr_pool_t *p, server_rec *main_s,
+ server_rec *s, server_addr_rec *sar,
+ ipaddr_chain *ic)
+{
+ /* the first time we encounter a NameVirtualHost address
+ * ic->server will be NULL, on subsequent encounters
+ * ic->names will be non-NULL.
+ */
+ if (ic->names || ic->server == NULL) {
+ name_chain *nc = new_name_chain(p, s, sar);
+ nc->next = ic->names;
+ ic->names = nc;
+ ic->server = s;
+ if (sar->host_port != ic->sar->host_port) {
+ /* one of the two is a * port, the other isn't */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_s,
+ "VirtualHost %s:%u -- mixing * "
+ "ports and non-* ports with "
+ "a NameVirtualHost address is not supported,"
+ " proceeding with undefined results",
+ sar->virthost, sar->host_port);
+ }
+ return 1;
+ }
+ else {
+ /* IP-based vhosts are handled by the caller */
+ return 0;
+ }
+}
+
+static void remove_unused_name_vhosts(server_rec *main_s, ipaddr_chain **pic)
+{
+ while (*pic) {
+ ipaddr_chain *ic = *pic;
+
+ if (ic->server == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_s,
+ "NameVirtualHost %s:%u has no VirtualHosts",
+ ic->sar->virthost, ic->sar->host_port);
+ *pic = ic->next;
+ }
+ else {
+ pic = &ic->next;
+ }
+ }
+}
+
+/* compile the tables and such we need to do the run-time vhost lookups */
+AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s)
+{
+ server_addr_rec *sar;
+ int has_default_vhost_addr;
+ server_rec *s;
+ int i;
+ ipaddr_chain **iphash_table_tail[IPHASH_TABLE_SIZE];
+
+ /* terminate the name_vhost list */
+ *name_vhost_list_tail = NULL;
+
+ /* Main host first */
+ s = main_s;
+
+ if (!s->server_hostname) {
+ s->server_hostname = ap_get_local_host(p);
+ }
+
+ /* initialize the tails */
+ for (i = 0; i < IPHASH_TABLE_SIZE; ++i) {
+ iphash_table_tail[i] = &iphash_table[i];
+ }
+
+ /* The first things to go into the hash table are the NameVirtualHosts
+ * Since name_vhost_list is in the same order that the directives
+ * occured in the config file, we'll copy it in that order.
+ */
+ for (sar = name_vhost_list; sar; sar = sar->next) {
+ char inaddr_any[16] = {0}; /* big enough to handle IPv4 or IPv6 */
+ unsigned bucket = hash_addr(sar->host_addr);
+ ipaddr_chain *ic = new_ipaddr_chain(p, NULL, sar);
+
+ if (memcmp(sar->host_addr->ipaddr_ptr, inaddr_any,
+ sar->host_addr->ipaddr_len)) { /* not IN[6]ADDR_ANY */
+ *iphash_table_tail[bucket] = ic;
+ iphash_table_tail[bucket] = &ic->next;
+ }
+ else {
+ /* A wildcard NameVirtualHost goes on the default_list so
+ * that it can catch incoming requests on any address.
+ */
+ ic->next = default_list;
+ default_list = ic;
+ }
+ /* Notice that what we've done is insert an ipaddr_chain with
+ * both server and names NULL. This fact is used to spot name-
+ * based vhosts in add_name_vhost_config().
+ */
+ }
+
+ /* The next things to go into the hash table are the virtual hosts
+ * themselves. They're listed off of main_s->next in the reverse
+ * order they occured in the config file, so we insert them at
+ * the iphash_table_tail but don't advance the tail.
+ */
+
+ for (s = main_s->next; s; s = s->next) {
+ has_default_vhost_addr = 0;
+ for (sar = s->addrs; sar; sar = sar->next) {
+ ipaddr_chain *ic;
+ char inaddr_any[16] = {0}; /* big enough to handle IPv4 or IPv6 */
+
+ if ((sar->host_addr->family == AF_INET &&
+ sar->host_addr->sa.sin.sin_addr.s_addr == DEFAULT_VHOST_ADDR)
+ || !memcmp(sar->host_addr->ipaddr_ptr, inaddr_any, sar->host_addr->ipaddr_len)) {
+ ic = find_default_server(sar->host_port);
+ if (!ic || !add_name_vhost_config(p, main_s, s, sar, ic)) {
+ if (ic && ic->sar->host_port != 0) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, main_s, "_default_ VirtualHost "
+ "overlap on port %u, the first has "
+ "precedence", sar->host_port);
+ }
+ ic = new_ipaddr_chain(p, s, sar);
+ ic->next = default_list;
+ default_list = ic;
+ }
+ has_default_vhost_addr = 1;
+ }
+ else {
+ /* see if it matches something we've already got */
+ ic = find_ipaddr(sar->host_addr);
+
+ if (!ic) {
+ unsigned bucket = hash_addr(sar->host_addr);
+
+ ic = new_ipaddr_chain(p, s, sar);
+ ic->next = *iphash_table_tail[bucket];
+ *iphash_table_tail[bucket] = ic;
+ }
+ else if (!add_name_vhost_config(p, main_s, s, sar, ic)) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, main_s, "VirtualHost %s:%u overlaps "
+ "with VirtualHost %s:%u, the first has "
+ "precedence, perhaps you need a "
+ "NameVirtualHost directive",
+ sar->virthost, sar->host_port,
+ ic->sar->virthost, ic->sar->host_port);
+ ic->sar = sar;
+ ic->server = s;
+ }
+ }
+ }
+
+ /* Ok now we want to set up a server_hostname if the user was
+ * silly enough to forget one.
+ * XXX: This is silly we should just crash and burn.
+ */
+ if (!s->server_hostname) {
+ if (has_default_vhost_addr) {
+ s->server_hostname = main_s->server_hostname;
+ }
+ else if (!s->addrs) {
+ /* what else can we do? at this point this vhost has
+ no configured name, probably because they used
+ DNS in the VirtualHost statement. It's disabled
+ anyhow by the host matching code. -djg */
+ s->server_hostname =
+ apr_pstrdup(p, "bogus_host_without_forward_dns");
+ }
+ else {
+ apr_status_t rv;
+ char *hostname;
+
+ rv = apr_getnameinfo(&hostname, s->addrs->host_addr, 0);
+ if (rv == APR_SUCCESS) {
+ s->server_hostname = apr_pstrdup(p, hostname);
+ }
+ else {
+ /* again, what can we do? They didn't specify a
+ ServerName, and their DNS isn't working. -djg */
+ char *ipaddr_str;
+
+ apr_sockaddr_ip_get(&ipaddr_str, s->addrs->host_addr);
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_s,
+ "Failed to resolve server name "
+ "for %s (check DNS) -- or specify an explicit "
+ "ServerName",
+ ipaddr_str);
+ s->server_hostname =
+ apr_pstrdup(p, "bogus_host_without_reverse_dns");
+ }
+ }
+ }
+ }
+
+ /* now go through and delete any NameVirtualHosts that didn't have any
+ * hosts associated with them. Lamers.
+ */
+ for (i = 0; i < IPHASH_TABLE_SIZE; ++i) {
+ remove_unused_name_vhosts(main_s, &iphash_table[i]);
+ }
+ remove_unused_name_vhosts(main_s, &default_list);
+
+#ifdef IPHASH_STATISTICS
+ dump_iphash_statistics(main_s);
+#endif
+ if (ap_exists_config_define("DUMP_VHOSTS")) {
+ apr_file_t *thefile = NULL;
+ apr_file_open_stderr(&thefile, p);
+ dump_vhost_config(thefile);
+ }
+}
+
+
+/*****************************************************************************
+ * run-time vhost matching functions
+ */
+
+/* Lowercase and remove any trailing dot and/or :port from the hostname,
+ * and check that it is sane.
+ *
+ * In most configurations the exact syntax of the hostname isn't
+ * important so strict sanity checking isn't necessary. However, in
+ * mass hosting setups (using mod_vhost_alias or mod_rewrite) where
+ * the hostname is interpolated into the filename, we need to be sure
+ * that the interpolation doesn't expose parts of the filesystem.
+ * We don't do strict RFC 952 / RFC 1123 syntax checking in order
+ * to support iDNS and people who erroneously use underscores.
+ * Instead we just check for filesystem metacharacters: directory
+ * separators / and \ and sequences of more than one dot.
+ */
+static void fix_hostname(request_rec *r)
+{
+ char *host, *scope_id;
+ char *dst;
+ apr_port_t port;
+ apr_status_t rv;
+
+ /* According to RFC 2616, Host header field CAN be blank. */
+ if (!*r->hostname) {
+ return;
+ }
+
+ rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool);
+ if (rv != APR_SUCCESS || scope_id) {
+ goto bad;
+ }
+
+ if (!host && port) {
+ /* silly looking host ("Host: 123") but that isn't our job
+ * here to judge; apr_parse_addr_port() would think we had a port
+ * but no address
+ */
+ host = apr_itoa(r->pool, (int)port);
+ }
+ else if (port) {
+ /* Don't throw the Host: header's port number away:
+ save it in parsed_uri -- ap_get_server_port() needs it! */
+ /* @@@ XXX there should be a better way to pass the port.
+ * Like r->hostname, there should be a r->portno
+ */
+ r->parsed_uri.port = port;
+ r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
+ }
+
+ /* if the hostname is an IPv6 numeric address string, it was validated
+ * already; otherwise, further validation is needed
+ */
+ if (r->hostname[0] != '[') {
+ for (dst = host; *dst; dst++) {
+ if (apr_islower(*dst)) {
+ /* leave char unchanged */
+ }
+ else if (*dst == '.') {
+ if (*(dst + 1) == '.') {
+ goto bad;
+ }
+ }
+ else if (apr_isupper(*dst)) {
+ *dst = apr_tolower(*dst);
+ }
+ else if (*dst == '/' || *dst == '\\') {
+ goto bad;
+ }
+ }
+ /* strip trailing gubbins */
+ if (dst > host && dst[-1] == '.') {
+ dst[-1] = '\0';
+ }
+ }
+ r->hostname = host;
+ return;
+
+bad:
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Client sent malformed Host header");
+ return;
+}
+
+
+/* return 1 if host matches ServerName or ServerAliases */
+static int matches_aliases(server_rec *s, const char *host)
+{
+ int i;
+ apr_array_header_t *names;
+
+ /* match ServerName */
+ if (!strcasecmp(host, s->server_hostname)) {
+ return 1;
+ }
+
+ /* search all the aliases from ServerAlias directive */
+ names = s->names;
+ if (names) {
+ char **name = (char **) names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if(!name[i]) continue;
+ if (!strcasecmp(host, name[i]))
+ return 1;
+ }
+ }
+ names = s->wild_names;
+ if (names) {
+ char **name = (char **) names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if(!name[i]) continue;
+ if (!ap_strcasecmp_match(host, name[i]))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/* Suppose a request came in on the same socket as this r, and included
+ * a header "Host: host:port", would it map to r->server? It's more
+ * than just that though. When we do the normal matches for each request
+ * we don't even bother considering Host: etc on non-namevirtualhosts,
+ * we just call it a match. But here we require the host:port to match
+ * the ServerName and/or ServerAliases.
+ */
+AP_DECLARE(int) ap_matches_request_vhost(request_rec *r, const char *host,
+ apr_port_t port)
+{
+ server_rec *s;
+ server_addr_rec *sar;
+
+ s = r->server;
+
+ /* search all the <VirtualHost> values */
+ /* XXX: If this is a NameVirtualHost then we may not be doing the Right Thing
+ * consider:
+ *
+ * NameVirtualHost 10.1.1.1
+ * <VirtualHost 10.1.1.1>
+ * ServerName v1
+ * </VirtualHost>
+ * <VirtualHost 10.1.1.1>
+ * ServerName v2
+ * </VirtualHost>
+ *
+ * Suppose r->server is v2, and we're asked to match "10.1.1.1". We'll say
+ * "yup it's v2", when really it isn't... if a request came in for 10.1.1.1
+ * it would really go to v1.
+ */
+ for (sar = s->addrs; sar; sar = sar->next) {
+ if ((sar->host_port == 0 || port == sar->host_port)
+ && !strcasecmp(host, sar->virthost)) {
+ return 1;
+ }
+ }
+
+ /* the Port has to match now, because the rest don't have ports associated
+ * with them. */
+ if (port != s->port) {
+ return 0;
+ }
+
+ return matches_aliases(s, host);
+}
+
+
+static void check_hostalias(request_rec *r)
+{
+ /*
+ * Even if the request has a Host: header containing a port we ignore
+ * that port. We always use the physical port of the socket. There
+ * are a few reasons for this:
+ *
+ * - the default of 80 or 443 for SSL is easier to handle this way
+ * - there is less of a possibility of a security problem
+ * - it simplifies the data structure
+ * - the client may have no idea that a proxy somewhere along the way
+ * translated the request to another ip:port
+ * - except for the addresses from the VirtualHost line, none of the other
+ * names we'll match have ports associated with them
+ */
+ const char *host = r->hostname;
+ apr_port_t port;
+ server_rec *s;
+ server_rec *last_s;
+ name_chain *src;
+
+ last_s = NULL;
+
+ port = r->connection->local_addr->port;
+
+ /* Recall that the name_chain is a list of server_addr_recs, some of
+ * whose ports may not match. Also each server may appear more than
+ * once in the chain -- specifically, it will appear once for each
+ * address from its VirtualHost line which matched. We only want to
+ * do the full ServerName/ServerAlias comparisons once for each
+ * server, fortunately we know that all the VirtualHost addresses for
+ * a single server are adjacent to each other.
+ */
+
+ for (src = r->connection->vhost_lookup_data; src; src = src->next) {
+ server_addr_rec *sar;
+
+ /* We only consider addresses on the name_chain which have a matching
+ * port
+ */
+ sar = src->sar;
+ if (sar->host_port != 0 && port != sar->host_port) {
+ continue;
+ }
+
+ s = src->server;
+
+ /* does it match the virthost from the sar? */
+ if (!strcasecmp(host, sar->virthost)) {
+ goto found;
+ }
+
+ if (s == last_s) {
+ /* we've already done ServerName and ServerAlias checks for this
+ * vhost
+ */
+ continue;
+ }
+ last_s = s;
+
+ if (matches_aliases(s, host)) {
+ goto found;
+ }
+ }
+ return;
+
+found:
+ /* s is the first matching server, we're done */
+ r->server = s;
+}
+
+
+static void check_serverpath(request_rec *r)
+{
+ server_rec *s;
+ server_rec *last_s;
+ name_chain *src;
+ apr_port_t port;
+
+ port = r->connection->local_addr->port;
+
+ /*
+ * This is in conjunction with the ServerPath code in http_core, so we
+ * get the right host attached to a non- Host-sending request.
+ *
+ * See the comment in check_hostalias about how each vhost can be
+ * listed multiple times.
+ */
+
+ last_s = NULL;
+ for (src = r->connection->vhost_lookup_data; src; src = src->next) {
+ /* We only consider addresses on the name_chain which have a matching
+ * port
+ */
+ if (src->sar->host_port != 0 && port != src->sar->host_port) {
+ continue;
+ }
+
+ s = src->server;
+ if (s == last_s) {
+ continue;
+ }
+ last_s = s;
+
+ if (s->path && !strncmp(r->uri, s->path, s->pathlen) &&
+ (s->path[s->pathlen - 1] == '/' ||
+ r->uri[s->pathlen] == '/' ||
+ r->uri[s->pathlen] == '\0')) {
+ r->server = s;
+ return;
+ }
+ }
+}
+
+
+AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r)
+{
+ /* must set this for HTTP/1.1 support */
+ if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) {
+ fix_hostname(r);
+ if (r->status != HTTP_OK)
+ return;
+ }
+ /* check if we tucked away a name_chain */
+ if (r->connection->vhost_lookup_data) {
+ if (r->hostname)
+ check_hostalias(r);
+ else
+ check_serverpath(r);
+ }
+}
+
+/**
+ * For every virtual host on this connection, call func_cb.
+ */
+AP_DECLARE(int) ap_vhost_iterate_given_conn(conn_rec *conn,
+ ap_vhost_iterate_conn_cb func_cb,
+ void* baton)
+{
+ server_rec *s;
+ server_rec *last_s;
+ name_chain *src;
+ apr_port_t port;
+ int rv = 0;
+
+ if (conn->vhost_lookup_data) {
+ last_s = NULL;
+ port = conn->local_addr->port;
+
+ for (src = conn->vhost_lookup_data; src; src = src->next) {
+ server_addr_rec *sar;
+
+ /* We only consider addresses on the name_chain which have a
+ * matching port.
+ */
+ sar = src->sar;
+ if (sar->host_port != 0 && port != sar->host_port) {
+ continue;
+ }
+
+ s = src->server;
+
+ if (s == last_s) {
+ /* we've already done a callback for this vhost. */
+ continue;
+ }
+
+ last_s = s;
+
+ rv = func_cb(baton, conn, s);
+
+ if (rv != 0) {
+ break;
+ }
+ }
+ }
+ else {
+ rv = func_cb(baton, conn, conn->base_server);
+ }
+
+ return rv;
+}
+
+/* Called for a new connection which has a known local_addr. Note that the
+ * new connection is assumed to have conn->server == main server.
+ */
+AP_DECLARE(void) ap_update_vhost_given_ip(conn_rec *conn)
+{
+ ipaddr_chain *trav;
+ apr_port_t port;
+
+ /* scan the hash table for an exact match first */
+ trav = find_ipaddr(conn->local_addr);
+
+ if (trav) {
+ /* save the name_chain for later in case this is a name-vhost */
+ conn->vhost_lookup_data = trav->names;
+ conn->base_server = trav->server;
+ return;
+ }
+
+ /* maybe there's a default server or wildcard name-based vhost
+ * matching this port
+ */
+ port = conn->local_addr->port;
+
+ trav = find_default_server(port);
+ if (trav) {
+ conn->vhost_lookup_data = trav->names;
+ conn->base_server = trav->server;
+ return;
+ }
+
+ /* otherwise we're stuck with just the main server
+ * and no name-based vhosts
+ */
+ conn->vhost_lookup_data = NULL;
+}