diff options
author | Arno Töll <debian@toell.net> | 2012-01-08 22:53:17 +0100 |
---|---|---|
committer | Arno Töll <debian@toell.net> | 2012-01-08 22:53:17 +0100 |
commit | e072a2dd866b7cb9f14319b80326a4e7fd16fcdf (patch) | |
tree | a49dfc56d94a26011fe157835ff6cbe14edbd8a9 /server | |
parent | 0890390c00801651d08d3794e13b31a5dabbf5ef (diff) | |
download | apache2-e072a2dd866b7cb9f14319b80326a4e7fd16fcdf.tar.gz |
Imported Upstream version 2.3.16-beta
Diffstat (limited to 'server')
101 files changed, 20523 insertions, 10770 deletions
diff --git a/server/Makefile.in b/server/Makefile.in index db2caa0d..42d1fe5c 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -7,14 +7,15 @@ 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 \ + connection.c listen.c util_mutex.c mpm_common.c mpm_unix.c \ + util_charset.c util_cookies.c util_debug.c util_xml.c \ + util_filter.c util_pcre.c util_regex.c exports.c \ scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ - eoc_bucket.c core_filters.c + eoc_bucket.c eor_bucket.c core_filters.c \ + util_expr_parse.c util_expr_scan.c util_expr_eval.c +LTLIBRARY_DEPENDENCIES = test_char.h TARGETS = delete-exports $(LTLIBRARY_NAME) $(CORE_IMPLIB_FILE) export_vars.h httpd.exp @@ -30,7 +31,7 @@ test_char.h: gen_test_char util.lo: test_char.h -EXPORT_DIRS = $(top_srcdir)/include $(top_srcdir)/os/$(OS_DIR) $(top_srcdir)/modules/http +EXPORT_DIRS = $(top_srcdir)/include $(top_srcdir)/os/$(OS_DIR) EXPORT_DIRS_APR = $(APR_INCLUDEDIR) $(APU_INCLUDEDIR) # If export_files is a dependency here, but we remove it during this stage, @@ -53,16 +54,13 @@ delete-exports: 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 + ( for dir in $(EXPORT_DIRS); do \ + ls $$dir/*.h ; \ + done; \ + for dir in $(EXPORT_DIRS_APR); do \ + ls $$dir/ap[ru].h $$dir/ap[ru]_*.h 2>/dev/null; \ + done; \ + ) | sort -u > $@ exports.c: export_files $(AWK) -f $(top_srcdir)/build/make_exports.awk `cat $?` > $@ @@ -83,3 +81,22 @@ httpd.exp: exports.c export_vars.h @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' >> $@ + + +# developer stuff +# (we really don't expect end users to use these targets!) +# +util_expr_scan.c util_expr_parse.c util_expr_parse.h: util_expr_scan.l util_expr_parse.y + bison -pap_expr_yy --defines=$(builddir)/util_expr_parse.h \ + -o $(builddir)/util_expr_parse.c $(srcdir)/util_expr_parse.y + flex -Pap_expr_yy -o $(builddir)/util_expr_scan.c $(srcdir)/util_expr_scan.l + set -e ; \ + for f in util_expr_scan.c util_expr_parse.c util_expr_parse.h ; do \ + sed -e "s|\"$(builddir)/|\"|g" < $(builddir)/$$f > \ + $(builddir)/$$f.$$$$ && \ + mv $(builddir)/$$f.$$$$ $(builddir)/$$f ; \ + done + # work around flex bug + # http://sourceforge.net/tracker/?func=detail&aid=3029024&group_id=97492&atid=618177 + perl -0777 -p -i -e 's,\n(void|int) ap_expr_yy[gs]et_column[^\n]+\)\n.*?\n\},,gs' \ + $(builddir)/util_expr_scan.c diff --git a/server/NWGNUmakefile b/server/NWGNUmakefile index 16ce9734..7f96e81e 100644 --- a/server/NWGNUmakefile +++ b/server/NWGNUmakefile @@ -102,7 +102,7 @@ NLM_NAME = genchars # This is used by the link '-desc ' directive. # If left blank, NLM_NAME will be used. # -NLM_DESCRIPTION = Apache $(VERSION_STR) Generate Test Characters +NLM_DESCRIPTION = Generate Test Characters # # This is used by the '-threadname' directive. If left blank, @@ -112,9 +112,9 @@ NLM_THREAD_NAME = genchars # # If this is specified, it will override VERSION value in -# $(AP_WORK)/build/NWGNUenvironment.inc +# $(AP_WORK)\NWGNUNetWare.rul # -NLM_VERSION = +NLM_VERSION = 1,0,0 # # If this is specified, it will override the default of 64K @@ -242,6 +242,16 @@ install :: nlms FORCE # Any specialized rules here # +# Make sure that the build doesn't attempt to regenerate the shipping files. +# This requires a 'touch' utility. Can be downloaded from 'coreutils' at +# http://sourceforge.net/projects/gnuwin32/ +util_expr_parse.h : util_expr_parse.y + touch util_expr_parse.h +util_expr_parse.c : util_expr_parse.y + touch util_expr_parse.c +util_expr_scan.c : util_expr_scan.l + touch util_expr_scan.c + # # Include the 'tail' makefile that has targets that depend on variables defined # in this makefile diff --git a/server/config.c b/server/config.c index 8a742bfb..8c56308b 100644 --- a/server/config.c +++ b/server/config.c @@ -39,8 +39,6 @@ #define APR_WANT_STRFUNC #include "apr_want.h" -#define CORE_PRIVATE - #include "ap_config.h" #include "httpd.h" #include "http_config.h" @@ -51,12 +49,18 @@ #include "http_main.h" #include "http_vhost.h" #include "util_cfgtree.h" -#include "mpm.h" +#include "util_varbuf.h" +#include "mpm_common.h" +#define APLOG_UNSET (APLOG_NO_MODULE - 1) +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX AP_DECLARE_DATA const char *ap_server_argv0 = NULL; - AP_DECLARE_DATA const char *ap_server_root = NULL; +AP_DECLARE_DATA server_rec *ap_server_conf = NULL; +AP_DECLARE_DATA apr_pool_t *ap_pglobal = 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; @@ -67,6 +71,7 @@ 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(check_config) APR_HOOK_LINK(post_config) APR_HOOK_LINK(open_logs) APR_HOOK_LINK(child_init) @@ -84,6 +89,11 @@ AP_IMPLEMENT_HOOK_RUN_ALL(int, pre_config, apr_pool_t *ptemp), (pconf, plog, ptemp), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, check_config, + (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(test_config, (apr_pool_t *pconf, server_rec *s), (pconf, s)) @@ -160,6 +170,20 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(int, handler, (request_rec *r), AP_IMPLEMENT_HOOK_RUN_FIRST(int, quick_handler, (request_rec *r, int lookup), (r, lookup), DECLINED) +/* hooks with no args are implemented last, after disabling APR hook probes */ +#if defined(APR_HOOK_PROBES_ENABLED) +#undef APR_HOOK_PROBES_ENABLED +#undef APR_HOOK_PROBE_ENTRY +#define APR_HOOK_PROBE_ENTRY(ud,ns,name,args) +#undef APR_HOOK_PROBE_RETURN +#define APR_HOOK_PROBE_RETURN(ud,ns,name,rv,args) +#undef APR_HOOK_PROBE_INVOKE +#define APR_HOOK_PROBE_INVOKE(ud,ns,name,src,args) +#undef APR_HOOK_PROBE_COMPLETE +#define APR_HOOK_PROBE_COMPLETE(ud,ns,name,src,rv,args) +#undef APR_HOOK_INT_DCL_UD +#define APR_HOOK_INT_DCL_UD +#endif AP_IMPLEMENT_HOOK_VOID(optional_fn_retrieve, (void), ()) /**************************************************************** @@ -179,15 +203,40 @@ static int total_modules = 0; */ static int dynamic_modules = 0; +/* The maximum possible value for total_modules, i.e. number of static + * modules plus DYNAMIC_MODULE_LIMIT. + */ +static int max_modules = 0; + +/* The number of elements we need to alloc for config vectors. Before loading + * of dynamic modules, we must be liberal and set this to max_modules. After + * loading of dynamic modules, we can trim it down to total_modules. On + * restart, reset to max_modules. + */ +static int conf_vector_length = 0; + +static int reserved_module_slots = 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; +/* a list of the module symbol names with the trailing "_module"removed */ +static char **ap_module_short_names = 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 *); +/* A list of the merge_dir_config functions of all loaded modules, sorted + * by module_index. + * Using this list in ap_merge_per_dir_configs() is faster than following + * the module->next linked list because of better memory locality (resulting + * in better cache usage). + */ +static merger_func *merger_func_cache; + /* maximum nesting level for config directories */ #ifndef AP_MAX_INCLUDE_DIR_DEPTH #define AP_MAX_INCLUDE_DIR_DEPTH (128) @@ -206,15 +255,13 @@ typedef void *(*merger_func)(apr_pool_t *, void *, void *); 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)); + void *conf_vector = apr_pcalloc(p, sizeof(void *) * conf_vector_length); 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)); + void **conf_vector = apr_pcalloc(p, sizeof(void *) * conf_vector_length); module *modp; for (modp = ap_top_module; modp; modp = modp->next) { @@ -231,19 +278,17 @@ 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 **conf_vector = apr_palloc(p, sizeof(void *) * conf_vector_length); 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; + int i; + for (i = 0; i < total_modules; i++) { if (!new_vector[i]) { conf_vector[i] = base_vector[i]; } else { - merger_func df = modp->merge_dir_config; + const merger_func df = merger_func_cache[i]; if (df && base_vector[i]) { conf_vector[i] = (*df)(p, base_vector[i], new_vector[i]); } @@ -257,8 +302,7 @@ AP_CORE_DECLARE(ap_conf_vector_t *) ap_merge_per_dir_configs(apr_pool_t *p, 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)); + void **conf_vector = apr_pcalloc(p, sizeof(void *) * conf_vector_length); module *modp; for (modp = ap_top_module; modp; modp = modp->next) { @@ -324,6 +368,12 @@ static int invoke_filter_init(request_rec *r, ap_filter_t *filters) return OK; } +/* + * TODO: Move this to an appropriate include file and possibly prefix it + * with AP_. + */ +#define DEFAULT_HANDLER_NAME "" + AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r) { const char *handler; @@ -356,18 +406,23 @@ AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r) } 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'; + if (r->content_type) { + handler = r->content_type; + 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; + + /* exclude media type arguments */ + while (p2 > handler && p2[-1] == ' ') + --p2; /* strip trailing spaces */ + + *p2='\0'; + } + } + else { + handler = DEFAULT_HANDLER_NAME; } r->handler = handler; @@ -378,11 +433,11 @@ AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r) r->handler = old_handler; if (result == DECLINED && r->handler && r->filename) { - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(00523) "handler \"%s\" not found for: %s", r->handler, r->filename); } - if ((result != OK) && (result != DONE) && (result != DECLINED) - && (result != AP_FILTER_ERROR) + if ((result != OK) && (result != DONE) && (result != DECLINED) && (result != SUSPENDED) + && (result != AP_FILTER_ERROR) /* ap_die() knows about this specifically */ && !ap_is_HTTP_VALID_RESPONSE(result)) { /* If a module is deliberately returning something else * (request_rec in non-HTTP or proprietary extension?) @@ -392,7 +447,7 @@ AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r) */ ignore = apr_table_get(r->notes, "HTTP_IGNORE_RANGE"); if (!ignore) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00524) "Handler for %s returned invalid result code %d", r->handler, result); result = HTTP_INTERNAL_SERVER_ERROR; @@ -441,19 +496,13 @@ struct ap_mod_list_struct { 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_register(p, &ap_config_hash, ap_pool_cleanup_set_null, apr_pool_cleanup_null); if (add_prelinked) { for (m = ap_prelinked_modules; *m != NULL; m++) { @@ -494,8 +543,11 @@ static void ap_add_module_commands(module *m, apr_pool_t *p) /* 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) +AP_DECLARE(const char *) ap_add_module(module *m, apr_pool_t *p, + const char *sym_name) { + ap_module_symbol_t *sym = ap_prelinked_module_symbols; + /* This could be called from a LoadModule httpd.conf command, * after the file has been linked and the module structure within it * teased out... @@ -508,23 +560,50 @@ AP_DECLARE(const char *) ap_add_module(module *m, apr_pool_t *p) 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) { + 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); } + /* + * If this fails some module forgot to call ap_reserve_module_slots*. + */ + ap_assert(total_modules < conf_vector_length); + + m->module_index = total_modules++; + dynamic_modules++; + + } + else if (!sym_name) { + while (sym->modp != NULL) { + if (sym->modp == m) { + sym_name = sym->name; + break; + } + sym++; + } + } + + if (m->next == NULL) { + m->next = ap_top_module; + ap_top_module = m; + } + + if (sym_name) { + int len = strlen(sym_name); + int slen = strlen("_module"); + if (len > slen && !strcmp(sym_name + len - slen, "_module")) { + len -= slen; + } + + ap_module_short_names[m->module_index] = strdup(sym_name); + ap_module_short_names[m->module_index][len] = '\0'; + merger_func_cache[m->module_index] = m->merge_dir_config; } + /* 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. @@ -587,7 +666,7 @@ AP_DECLARE(void) ap_remove_module(module *m) if (!modp) { /* Uh-oh, this module doesn't exist */ - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00525) "Cannot remove module %s: not found in module list", m->name); return; @@ -597,13 +676,18 @@ AP_DECLARE(void) ap_remove_module(module *m) modp->next = modp->next->next; } + free(ap_module_short_names[m->module_index]); + ap_module_short_names[m->module_index] = NULL; + merger_func_cache[m->module_index] = NULL; + 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) +AP_DECLARE(const char *) ap_add_loaded_module(module *mod, apr_pool_t *p, + const char *short_name) { module **m; const char *error; @@ -611,7 +695,7 @@ AP_DECLARE(const char *) ap_add_loaded_module(module *mod, apr_pool_t *p) /* * Add module pointer to top of chained module list */ - error = ap_add_module(mod, p); + error = ap_add_module(mod, p, short_name); if (error) { return error; } @@ -679,15 +763,23 @@ AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process) for (m = ap_preloaded_modules; *m != NULL; m++) (*m)->module_index = total_modules++; + max_modules = total_modules + DYNAMIC_MODULE_LIMIT + 1; + conf_vector_length = max_modules; + /* - * Initialise list of loaded modules + * Initialise list of loaded modules and short names */ ap_loaded_modules = (module **)apr_palloc(process->pool, - sizeof(module *) * (total_modules + DYNAMIC_MODULE_LIMIT + 1)); + sizeof(module *) * conf_vector_length); + if (!ap_module_short_names) + ap_module_short_names = ap_calloc(sizeof(char *), conf_vector_length); + + if (!merger_func_cache) + merger_func_cache = ap_calloc(sizeof(merger_func), conf_vector_length); - if (ap_loaded_modules == NULL) { + if (ap_loaded_modules == NULL || ap_module_short_names == NULL + || merger_func_cache == NULL) return "Ouch! Out of memory in ap_setup_prelinked_modules()!"; - } for (m = ap_preloaded_modules, m2 = ap_loaded_modules; *m != NULL; ) *m2++ = *m++; @@ -698,7 +790,7 @@ AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process) * Initialize chain of linked (=activate) modules */ for (m = ap_prelinked_modules; *m != NULL; m++) { - error = ap_add_module(*m, process->pconf); + error = ap_add_module(*m, process->pconf, NULL); if (error) { return error; } @@ -714,6 +806,13 @@ AP_DECLARE(const char *) ap_find_module_name(module *m) return m->name; } +AP_DECLARE(const char *) ap_find_module_short_name(int module_index) +{ + if (module_index < 0 || module_index >= conf_vector_length) + return NULL; + return ap_module_short_names[module_index]; +} + AP_DECLARE(module *) ap_find_linked_module(const char *name) { module *modp; @@ -740,10 +839,16 @@ AP_DECLARE(module *) ap_find_linked_module(const char *name) static const char *invoke_cmd(const command_rec *cmd, cmd_parms *parms, void *mconfig, const char *args) { + int override_list_ok = 0; char *w, *w2, *w3; const char *errmsg = NULL; - if ((parms->override & cmd->req_override) == 0) + /** Have we been provided a list of acceptable directives? */ + if(parms->override_list != NULL) + if(apr_table_get(parms->override_list, cmd->name) != NULL) + override_list_ok = 1; + + if ((parms->override & cmd->req_override) == 0 && !override_list_ok) return apr_pstrcat(parms->pool, cmd->name, " not allowed here", NULL); parms->info = cmd->cmd_data; @@ -1082,6 +1187,9 @@ static const char *ap_build_config_sub(apr_pool_t *p, apr_pool_t *temp_pool, return retval; } +#define VARBUF_INIT_LEN 200 +#define VARBUF_MAX_LEN (16*1024*1024) + AP_DECLARE(const char *) ap_build_cont_config(apr_pool_t *p, apr_pool_t *temp_pool, cmd_parms *parms, @@ -1089,25 +1197,26 @@ AP_DECLARE(const char *) ap_build_cont_config(apr_pool_t *p, 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) + apr_status_t rc; + struct ap_varbuf vb; + apr_size_t max_len = VARBUF_MAX_LEN; + if (p == temp_pool) + max_len = HUGE_STRING_LEN; /* lower limit for .htaccess */ + + bracket = apr_pstrcat(temp_pool, orig_directive + 1, ">", NULL); + ap_varbuf_init(temp_pool, &vb, VARBUF_INIT_LEN); + + while ((rc = ap_varbuf_cfg_getline(&vb, parms->config_file, max_len)) + == APR_SUCCESS) { + if (!memcmp(vb.buf, "</", 2) + && (strcasecmp(vb.buf + 2, bracket) == 0) && (*curr_parent == NULL)) { break; } - retval = ap_build_config_sub(p, temp_pool, l, parms, current, + retval = ap_build_config_sub(p, temp_pool, vb.buf, parms, current, curr_parent, &sub_tree); if (retval != NULL) return retval; @@ -1120,6 +1229,9 @@ AP_DECLARE(const char *) ap_build_cont_config(apr_pool_t *p, sub_tree = *current; } } + ap_varbuf_free(&vb); + if (rc != APR_EOF && rc != APR_SUCCESS) + return ap_pcfg_strerror(temp_pool, parms->config_file, rc); *current = sub_tree; return NULL; @@ -1131,7 +1243,7 @@ static const char *ap_walk_config_sub(const ap_directive_t *current, { const command_rec *cmd; ap_mod_list *ml; - char *dir = apr_pstrdup(parms->pool, current->directive); + char *dir = apr_pstrdup(parms->temp_pool, current->directive); ap_str_tolower(dir); @@ -1211,17 +1323,41 @@ AP_DECLARE(const char *) ap_build_config(cmd_parms *parms, { ap_directive_t *current = *conftree; ap_directive_t *curr_parent = NULL; - char *l = apr_palloc (temp_pool, MAX_STRING_LEN); const char *errmsg; + ap_directive_t **last_ptr = NULL; + apr_status_t rc; + struct ap_varbuf vb; + apr_size_t max_len = VARBUF_MAX_LEN; + if (p == temp_pool) + max_len = HUGE_STRING_LEN; /* lower limit for .htaccess */ + + ap_varbuf_init(temp_pool, &vb, VARBUF_INIT_LEN); if (current != NULL) { + /* If we have to traverse the whole tree again for every included + * config file, the required time grows as O(n^2) with the number of + * files. This can be a significant delay for large configurations. + * Therefore we cache a pointer to the last node. + */ + last_ptr = &(current->last); + + if(last_ptr && *last_ptr) { + current = *last_ptr; + } + while (current->next) { current = current->next; } + + if(last_ptr) { + /* update cached pointer to last node */ + *last_ptr = current; + } } - while (!(ap_cfg_getline(l, MAX_STRING_LEN, parms->config_file))) { - errmsg = ap_build_config_sub(p, temp_pool, l, parms, + while ((rc = ap_varbuf_cfg_getline(&vb, parms->config_file, max_len)) + == APR_SUCCESS) { + errmsg = ap_build_config_sub(p, temp_pool, vb.buf, parms, ¤t, &curr_parent, conftree); if (errmsg != NULL) return errmsg; @@ -1234,6 +1370,9 @@ AP_DECLARE(const char *) ap_build_config(cmd_parms *parms, *conftree = current; } } + ap_varbuf_free(&vb); + if (rc != APR_EOF && rc != APR_SUCCESS) + return ap_pcfg_strerror(temp_pool, parms->config_file, rc); if (curr_parent != NULL) { errmsg = ""; @@ -1314,6 +1453,18 @@ AP_DECLARE_NONSTD(const char *) ap_set_flag_slot(cmd_parms *cmd, return NULL; } +AP_DECLARE_NONSTD(const char *) ap_set_flag_slot_char(cmd_parms *cmd, + void *struct_ptr_v, int arg) +{ + int offset = (int)(long)cmd->info; + char *struct_ptr = (char *)struct_ptr_v; + + *(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) { @@ -1343,13 +1494,35 @@ AP_DECLARE_NONSTD(const char *) ap_set_deprecated(cmd_parms *cmd, return cmd->cmd->errmsg; } +AP_DECLARE(void) ap_reset_module_loglevels(struct ap_logconf *l, int val) +{ + if (l->module_levels) + memset(l->module_levels, val, conf_vector_length); +} + +AP_DECLARE(void) ap_set_module_loglevel(apr_pool_t *pool, struct ap_logconf *l, + int index, int level) +{ + if (!l->module_levels) { + l->module_levels = apr_palloc(pool, conf_vector_length); + if (l->level == APLOG_UNSET) { + ap_reset_module_loglevels(l, APLOG_UNSET); + } + else { + ap_reset_module_loglevels(l, APLOG_NO_MODULE); + } + } + + l->module_levels[index] = level; +} + /***************************************************************** * * Reading whole config files... */ static cmd_parms default_parms = -{NULL, 0, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; +{NULL, 0, 0, NULL, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) { @@ -1369,18 +1542,25 @@ AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) AP_DECLARE(const char *) ap_soak_end_container(cmd_parms *cmd, char *directive) { - char l[MAX_STRING_LEN]; + struct ap_varbuf vb; const char *args; char *cmd_name; + apr_status_t rc; + apr_size_t max_len = VARBUF_MAX_LEN; + if (cmd->pool == cmd->temp_pool) + max_len = HUGE_STRING_LEN; /* lower limit for .htaccess */ + + ap_varbuf_init(cmd->temp_pool, &vb, VARBUF_INIT_LEN); - while(!(ap_cfg_getline(l, MAX_STRING_LEN, cmd->config_file))) { + while((rc = ap_varbuf_cfg_getline(&vb, cmd->config_file, max_len)) + == APR_SUCCESS) { #if RESOLVE_ENV_PER_TOKEN - args = l; + args = vb.buf; #else - args = ap_resolve_env(cmd->temp_pool, l); + args = ap_resolve_env(cmd->temp_pool, vb.buf); #endif - cmd_name = ap_getword_conf(cmd->pool, &args); + cmd_name = ap_getword_conf(cmd->temp_pool, &args); if (cmd_name[0] == '<') { if (cmd_name[1] == '/') { cmd_name[strlen(cmd_name) - 1] = '\0'; @@ -1391,6 +1571,7 @@ AP_DECLARE(const char *) ap_soak_end_container(cmd_parms *cmd, char *directive) cmd_name, ">", NULL); } + ap_varbuf_free(&vb); return NULL; /* found end of container */ } else { @@ -1406,6 +1587,8 @@ AP_DECLARE(const char *) ap_soak_end_container(cmd_parms *cmd, char *directive) } } } + if (rc != APR_EOF && rc != APR_SUCCESS) + return ap_pcfg_strerror(cmd->temp_pool, cmd->config_file, rc); return apr_pstrcat(cmd->pool, "Expected </", directive + 1, "> before end of configuration", @@ -1420,7 +1603,7 @@ static const char *execute_now(char *cmd_line, const char *args, { const command_rec *cmd; ap_mod_list *ml; - char *dir = apr_pstrdup(parms->pool, cmd_line); + char *dir = apr_pstrdup(parms->temp_pool, cmd_line); ap_str_tolower(dir); @@ -1465,31 +1648,31 @@ typedef struct { /* arr_elts_getstr() returns the next line from the string array. */ -static void *arr_elts_getstr(void *buf, size_t bufsiz, void *param) +static apr_status_t arr_elts_getstr(void *buf, size_t bufsiz, void *param) { arr_elts_param_t *arr_param = (arr_elts_param_t *)param; + char *elt; /* End of array reached? */ if (++arr_param->curr_idx > arr_param->array->nelts) - return NULL; + return APR_EOF; /* return the line */ - apr_cpystrn(buf, - ((char **)arr_param->array->elts)[arr_param->curr_idx - 1], - bufsiz); - - return buf; + elt = ((char **)arr_param->array->elts)[arr_param->curr_idx - 1]; + if (apr_cpystrn(buf, elt, bufsiz) - (char *)buf >= bufsiz - 1) + return APR_ENOSPC; + return APR_SUCCESS; } /* arr_elts_close(): dummy close routine (makes sure no more lines can be read) */ -static int arr_elts_close(void *param) +static apr_status_t 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; + return APR_SUCCESS; } static const char *process_command_config(server_rec *s, @@ -1532,7 +1715,7 @@ static const char *process_command_config(server_rec *s, } typedef struct { - char *fname; + const char *fname; } fnames; static int fname_alphasort(const void *fn1, const void *fn2) @@ -1543,25 +1726,65 @@ static int fname_alphasort(const void *fn1, const void *fn2) return strcmp(f1->fname,f2->fname); } +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) +{ + ap_configfile_t *cfp; + cmd_parms parms; + apr_status_t rv; + const char *error; + + 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_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) { + if (parms.err_directive) + return apr_psprintf(p, "Syntax error on line %d of %s: %s", + parms.err_directive->line_num, + parms.err_directive->filename, error); + else + return error; + } + + return NULL; +} + 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) + unsigned depth, + int optional) { - cmd_parms parms; - ap_configfile_t *cfp; const char *error; apr_status_t rv; - if (ap_is_directory(p, fname)) { + if (ap_is_directory(ptemp, 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); + char *path = apr_pstrdup(ptemp, fname); if (++depth > AP_MAX_INCLUDE_DIR_DEPTH) { return apr_psprintf(p, "Directory %s exceeds the maximum include " @@ -1575,20 +1798,20 @@ static const char *process_resource_config_nofnmatch(server_rec *s, * entries here and store 'em away. Recall we need full pathnames * for this. */ - rv = apr_dir_open(&dirp, path, p); + rv = apr_dir_open(&dirp, path, ptemp); 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)); + candidates = apr_array_make(ptemp, 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); + fnew->fname = ap_make_full_path(ptemp, path, dirent.name); } } @@ -1605,7 +1828,7 @@ static const char *process_resource_config_nofnmatch(server_rec *s, fnew = &((fnames *) candidates->elts)[current]; error = process_resource_config_nofnmatch(s, fnew->fname, conftree, p, ptemp, - depth); + depth, optional); if (error) { return error; } @@ -1615,39 +1838,124 @@ static const char *process_resource_config_nofnmatch(server_rec *s, 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_SYM_OWNER | OPT_MULTI; + return ap_process_resource_config(s, fname, conftree, p, ptemp); +} - rv = ap_pcfg_openfile(&cfp, p, fname); +static const char *process_resource_config_fnmatch(server_rec *s, + const char *path, + const char *fname, + ap_directive_t **conftree, + apr_pool_t *p, + apr_pool_t *ptemp, + unsigned depth, + int optional) +{ + const char *rest; + apr_status_t rv; + apr_dir_t *dirp; + apr_finfo_t dirent; + apr_array_header_t *candidates = NULL; + fnames *fnew; + int current; + + /* find the first part of the filename */ + rest = ap_strchr_c(fname, '/'); + if (rest) { + fname = apr_pstrndup(ptemp, fname, rest - fname); + rest++; + } + + /* optimisation - if the filename isn't a wildcard, process it directly */ + if (!apr_fnmatch_test(fname)) { + path = ap_make_full_path(ptemp, path, fname); + if (!rest) { + return process_resource_config_nofnmatch(s, path, + conftree, p, + ptemp, 0, optional); + } + else { + return process_resource_config_fnmatch(s, path, rest, + conftree, p, + ptemp, 0, optional); + } + } + + /* + * 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, ptemp); 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)); + return apr_psprintf(p, "Could not open config directory %s: %s", + path, apr_strerror(rv, errmsg, sizeof errmsg)); + } + + candidates = apr_array_make(ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..") + && (apr_fnmatch(fname, dirent.name, + APR_FNM_PERIOD) == APR_SUCCESS)) { + const char *full_path = ap_make_full_path(ptemp, path, dirent.name); + /* If matching internal to path, and we happen to match something + * other than a directory, skip it + */ + if (rest && (rv == APR_SUCCESS) && (dirent.filetype != APR_DIR)) { + continue; + } + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = full_path; + } } - parms.config_file = cfp; - error = ap_build_config(&parms, p, ptemp, conftree); - ap_cfg_closefile(cfp); + apr_dir_close(dirp); + if (candidates->nelts != 0) { + const char *error; - if (error) { - return apr_psprintf(p, "Syntax error on line %d of %s: %s", - parms.err_directive->line_num, - parms.err_directive->filename, 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]; + if (!rest) { + error = process_resource_config_nofnmatch(s, fnew->fname, + conftree, p, + ptemp, 0, optional); + } + else { + error = process_resource_config_fnmatch(s, fnew->fname, rest, + conftree, p, + ptemp, 0, optional); + } + if (error) { + return error; + } + } + } + else { + + if (!optional) { + return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing " + "(use IncludeOptional if required)", fname, path); + } } return NULL; } -AP_DECLARE(const char *) ap_process_resource_config(server_rec *s, +AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, const char *fname, ap_directive_t **conftree, apr_pool_t *p, - apr_pool_t *ptemp) + apr_pool_t *ptemp, + int optional) { /* XXX: lstat() won't work on the wildcard pattern... */ @@ -1655,95 +1963,35 @@ AP_DECLARE(const char *) ap_process_resource_config(server_rec *s, /* 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)))) { + && !(strcmp(fname, ap_server_root_relative(ptemp, SERVER_CONFIG_FILE)))) { apr_finfo_t finfo; - if (apr_stat(&finfo, fname, APR_FINFO_LINK | APR_FINFO_TYPE, p) != APR_SUCCESS) + if (apr_stat(&finfo, fname, APR_FINFO_LINK | APR_FINFO_TYPE, ptemp) != APR_SUCCESS) return NULL; } if (!apr_fnmatch_test(fname)) { - return process_resource_config_nofnmatch(s, fname, conftree, p, ptemp, - 0); + return ap_process_resource_config(s, fname, conftree, p, ptemp); } 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, '/'); + apr_status_t status; + const char *rootpath, *filepath = fname; - AP_DEBUG_ASSERT(pattern != NULL); /* path must be absolute. */ + /* locate the start of the directories proper */ + status = apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, ptemp); - *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)); + /* we allow APR_SUCCESS and APR_EINCOMPLETE */ + if (APR_ERELATIVE == status) { + return apr_pstrcat(p, "Include must have an absolute path, ", fname, NULL); } - - 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); - } + else if (APR_EBADPATH == status) { + return apr_pstrcat(p, "Include has a bad path, ", fname, NULL); } - 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; - } - } - } + /* walk the filepath */ + return process_resource_config_fnmatch(s, rootpath, filepath, conftree, p, ptemp, + 0, optional); } - - return NULL; } AP_DECLARE(int) ap_process_config_tree(server_rec *s, @@ -1764,12 +2012,12 @@ AP_DECLARE(int) ap_process_config_tree(server_rec *s, 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); + if (parms.err_directive) + ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, p, APLOGNO(00526) + "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; } @@ -1778,7 +2026,7 @@ AP_DECLARE(int) ap_process_config_tree(server_rec *s, AP_CORE_DECLARE(int) ap_parse_htaccess(ap_conf_vector_t **result, request_rec *r, int override, - int override_opts, + int override_opts, apr_table_t *override_list, const char *d, const char *access_name) { ap_configfile_t *f = NULL; @@ -1800,6 +2048,7 @@ AP_CORE_DECLARE(int) ap_parse_htaccess(ap_conf_vector_t **result, parms = default_parms; parms.override = override; parms.override_opts = override_opts; + parms.override_list = override_list; parms.pool = r->pool; parms.temp_pool = r->pool; parms.server = r->server; @@ -1840,10 +2089,11 @@ AP_CORE_DECLARE(int) ap_parse_htaccess(ap_conf_vector_t **result, else { if (!APR_STATUS_IS_ENOENT(status) && !APR_STATUS_IS_ENOTDIR(status)) { - ap_log_rerror(APLOG_MARK, APLOG_CRIT, status, r, + ap_log_rerror(APLOG_MARK, APLOG_CRIT, status, r, APLOGNO(00529) "%s pcfg_openfile: unable to check htaccess file, " - "ensure it is readable", - filename); + "ensure it is readable and that '%s' " + "is executable", + filename, d); apr_table_setn(r->notes, "error-notes", "Server unable to read htaccess file, denying " "access to be safe"); @@ -1884,7 +2134,8 @@ AP_CORE_DECLARE(const char *) ap_init_virtual_host(apr_pool_t *p, s->keep_alive = -1; s->keep_alive_max = -1; s->error_log = main_server->error_log; - s->loglevel = main_server->loglevel; + s->log.level = APLOG_UNSET; + s->log.module_levels = NULL; /* useful default, otherwise we get a port of 0 on redirects */ s->port = main_server->port; s->next = NULL; @@ -1905,10 +2156,52 @@ AP_CORE_DECLARE(const char *) ap_init_virtual_host(apr_pool_t *p, return ap_parse_vhost_addrs(p, hostname, s); } +AP_DECLARE(struct ap_logconf *) ap_new_log_config(apr_pool_t *p, + const struct ap_logconf *old) +{ + struct ap_logconf *l = apr_pcalloc(p, sizeof(struct ap_logconf)); + if (old) { + l->level = old->level; + if (old->module_levels) { + l->module_levels = + apr_pmemdup(p, old->module_levels, conf_vector_length); + } + } + else { + l->level = APLOG_UNSET; + } + return l; +} + +AP_DECLARE(void) ap_merge_log_config(const struct ap_logconf *old_conf, + struct ap_logconf *new_conf) +{ + if (new_conf->level != APLOG_UNSET) { + /* Setting the main loglevel resets all per-module log levels. + * I.e. if new->level has been set, we must ignore old->module_levels. + */ + return; + } + + new_conf->level = old_conf->level; + if (new_conf->module_levels == NULL) { + new_conf->module_levels = old_conf->module_levels; + } + else if (old_conf->module_levels != NULL) { + int i; + for (i = 0; i < conf_vector_length; i++) { + if (new_conf->module_levels[i] == APLOG_UNSET) + new_conf->module_levels[i] = old_conf->module_levels[i]; + } + } +} AP_DECLARE(void) ap_fixup_virtual_hosts(apr_pool_t *p, server_rec *main_server) { server_rec *virt; + core_dir_config *dconf = + ap_get_core_module_config(main_server->lookup_defaults); + dconf->log = &main_server->log; for (virt = main_server->next; virt; virt = virt->next) { merge_server_configs(p, main_server->module_config, @@ -1933,6 +2226,11 @@ AP_DECLARE(void) ap_fixup_virtual_hosts(apr_pool_t *p, server_rec *main_server) if (virt->keep_alive_max == -1) virt->keep_alive_max = main_server->keep_alive_max; + ap_merge_log_config(&main_server->log, &virt->log); + + dconf = ap_get_core_module_config(virt->lookup_defaults); + dconf->log = &virt->log; + /* XXX: this is really something that should be dealt with by a * post-config api phase */ @@ -1965,7 +2263,8 @@ static server_rec *init_server_config(process_rec *process, apr_pool_t *p) s->server_hostname = NULL; s->server_scheme = NULL; s->error_fname = DEFAULT_ERRORLOG; - s->loglevel = DEFAULT_LOGLEVEL; + s->log.level = DEFAULT_LOGLEVEL; + s->log.module_levels = NULL; s->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE; s->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE; s->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS; @@ -1978,8 +2277,13 @@ static server_rec *init_server_config(process_rec *process, apr_pool_t *p) /* 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 */ + NULL, APR_UNSPEC, 0, 0, p); + if (rv != APR_SUCCESS) { + /* should we test here for rv being an EAIERR? */ + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, rv, NULL, APLOGNO(00530) + "initialisation: bug or getaddrinfo fail"); + return NULL; + } s->addrs->host_port = 0; /* matches any port */ s->addrs->virthost = ""; /* must be non-NULL */ @@ -1992,6 +2296,38 @@ static server_rec *init_server_config(process_rec *process, apr_pool_t *p) } +static apr_status_t reset_conf_vector_length(void *dummy) +{ + reserved_module_slots = 0; + conf_vector_length = max_modules; + return APR_SUCCESS; +} + +static int conf_vector_length_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + /* + * We have loaded all modules that are loaded by EXEC_ON_READ directives. + * From now on we reduce the size of the config vectors to what we need, + * plus what has been reserved (e.g. by mod_perl) for additional modules + * loaded later on. + * If max_modules is too small, ap_add_module() will abort. + */ + if (total_modules + reserved_module_slots < max_modules) { + conf_vector_length = total_modules + reserved_module_slots; + } + apr_pool_cleanup_register(pconf, NULL, reset_conf_vector_length, + apr_pool_cleanup_null); + return OK; +} + + +AP_CORE_DECLARE(void) ap_register_config_hooks(apr_pool_t *p) +{ + ap_hook_pre_config(conf_vector_length_pre_config, NULL, NULL, + APR_HOOK_REALLY_LAST); +} + AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp, const char *filename, ap_directive_t **conftree) @@ -1999,6 +2335,9 @@ AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp, const char *confname, *error; apr_pool_t *p = process->pconf; server_rec *s = init_server_config(process, p); + if (s == NULL) { + return s; + } init_config_globals(p); @@ -2018,7 +2357,7 @@ AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp, if (!confname) { ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, - APR_EBADPATH, NULL, "Invalid config file path %s", + APR_EBADPATH, NULL, APLOGNO(00532) "Invalid config file path %s", filename); return NULL; } @@ -2030,6 +2369,13 @@ AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp, return NULL; } + error = ap_check_mpm(); + if (error) { + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, 0, NULL, APLOGNO(00534) + "%s: Configuration error: %s", ap_server_argv0, error); + return NULL; + } + error = process_command_config(s, ap_server_post_read_config, conftree, p, ptemp); @@ -2092,10 +2438,10 @@ static void show_overrides(const command_rec *pc, module *pm) printf("anywhere"); } else if (pc->req_override & RSRC_CONF) { - printf("only outside <Directory>, <Files> or <Location>"); + printf("only outside <Directory>, <Files>, <Location>, or <If>"); } else { - printf("only inside <Directory>, <Files> or <Location>"); + printf("only inside <Directory>, <Files>, <Location>, or <If>"); } /* Warn if the directive is allowed inside <Directory> or .htaccess @@ -2184,7 +2530,42 @@ AP_DECLARE(void) ap_show_modules(void) printf(" %s\n", ap_loaded_modules[n]->name); } -AP_DECLARE(const char *) ap_show_mpm(void) +AP_DECLARE(void *) ap_retained_data_get(const char *key) +{ + void *retained; + + apr_pool_userdata_get((void *)&retained, key, ap_pglobal); + return retained; +} + +AP_DECLARE(void *) ap_retained_data_create(const char *key, apr_size_t size) +{ + void *retained; + + retained = apr_pcalloc(ap_pglobal, size); + apr_pool_userdata_set((const void *)retained, key, apr_pool_cleanup_null, ap_pglobal); + return retained; +} + +static int count_directives_sub(const char *directive, ap_directive_t *current) +{ + int count = 0; + while (current != NULL) { + if (current->first_child != NULL) + count += count_directives_sub(directive, current->first_child); + if (strcasecmp(current->directive, directive) == 0) + count++; + current = current->next; + } + return count; +} + +AP_DECLARE(void) ap_reserve_module_slots(int count) +{ + reserved_module_slots += count; +} + +AP_DECLARE(void) ap_reserve_module_slots_directive(const char *directive) { - return MPM_NAME; + ap_reserve_module_slots(count_directives_sub(directive, ap_conftree)); } diff --git a/server/config.m4 b/server/config.m4 index 85fa4d17..dde51ed3 100644 --- a/server/config.m4 +++ b/server/config.m4 @@ -13,3 +13,7 @@ AC_CHECK_FUNCS(syslog) dnl Obsolete scoreboard code uses this. AC_CHECK_HEADERS(sys/times.h) AC_CHECK_FUNCS(times) + +dnl Add expr header files to INCLUDES +# util_expr needs header files in server source dir +APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/server]) diff --git a/server/connection.c b/server/connection.c index 3db5a2e6..6e4495f8 100644 --- a/server/connection.c +++ b/server/connection.c @@ -17,14 +17,12 @@ #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" @@ -95,23 +93,23 @@ AP_CORE_DECLARE(void) ap_flush_conn(conn_rec *c) * all the response data has been sent to the client. */ #define SECONDS_TO_LINGER 2 -AP_DECLARE(void) ap_lingering_close(conn_rec *c) + +AP_DECLARE(int) ap_start_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); + apr_socket_t *csd = ap_get_conn_socket(c); if (!csd) { - return; + return 1; } - ap_update_child_status(c->sbh, SERVER_CLOSING, NULL); + if (c->sbh) { + ap_update_child_status(c->sbh, SERVER_CLOSING, NULL); + } #ifdef NO_LINGCLOSE ap_flush_conn(c); /* just close it */ apr_socket_close(csd); - return; + return 1; #endif /* Close the connection, being careful to send out whatever is still @@ -124,7 +122,7 @@ AP_DECLARE(void) ap_lingering_close(conn_rec *c) if (c->aborted) { apr_socket_close(csd); - return; + return 1; } /* Shut down the socket for write, which will send a FIN @@ -133,6 +131,20 @@ AP_DECLARE(void) ap_lingering_close(conn_rec *c) if (apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS || c->aborted) { apr_socket_close(csd); + return 1; + } + + return 0; +} + +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_conn_socket(c); + + if (ap_start_lingering_close(c)) { return; } @@ -158,9 +170,9 @@ AP_DECLARE(void) ap_lingering_close(conn_rec *c) * First time through; * calculate now + 30 seconds (MAX_SECS_TO_LINGER). * - * If some module requested a shortened waiting period, only wait - * for 2s (SECONDS_TO_LINGER). This is useful for mitigating - * certain DoS attacks. + * If some module requested a shortened waiting period, only wait for + * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain + * DoS attacks. */ if (apr_table_get(c->notes, "short-lingering-close")) { timeup = apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER); diff --git a/server/core.c b/server/core.c index ca727139..c1f472cf 100644 --- a/server/core.c +++ b/server/core.c @@ -20,14 +20,13 @@ #include "apr_fnmatch.h" #include "apr_hash.h" #include "apr_thread_proc.h" /* for RLIMIT stuff */ -#include "apr_hooks.h" +#include "apr_random.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" @@ -42,7 +41,8 @@ #include "apr_buckets.h" #include "util_filter.h" #include "util_ebcdic.h" -#include "mpm.h" +#include "util_mutex.h" +#include "util_time.h" #include "mpm_common.h" #include "scoreboard.h" #include "mod_core.h" @@ -51,6 +51,13 @@ #include "mod_so.h" /* for ap_find_loaded_module_symbol */ +#if defined(RLIMIT_CPU) || defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS) || defined (RLIMIT_NPROC) +#include "unixd.h" +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + /* LimitRequestBody handling */ #define AP_LIMIT_REQ_BODY_UNSET ((apr_off_t) -1) #define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 0) @@ -67,7 +74,11 @@ #endif /* valid in core-conf, but not in runtime r->used_path_info */ -#define AP_ACCEPT_PATHINFO_UNSET 3 +#define AP_ACCEPT_PATHINFO_UNSET 3 + +#define AP_CONTENT_MD5_OFF 0 +#define AP_CONTENT_MD5_ON 1 +#define AP_CONTENT_MD5_UNSET 2 APR_HOOK_STRUCT( APR_HOOK_LINK(get_mgmt_items) @@ -81,66 +92,68 @@ AP_IMPLEMENT_HOOK_RUN_ALL(int, get_mgmt_items, * 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. + * The core module also defines handlers, etc., to handle just enough + * to allow a server with the core module ONLY to actually serve documents. * * This file could almost be mod_core.c, except for the stuff which affects * the http_conf_globals. */ +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + /* 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; +/* Provide ap_document_root_check storage and default value = true */ +AP_DECLARE_DATA int ap_document_root_check = 1; + /* magic pointer for ErrorDocument xxx "default" */ static char errordocument_default; -/* Default ap_document_root_check to default value: true */ -AP_DECLARE_DATA int ap_document_root_check = 1; +static apr_array_header_t *saved_server_config_defines = NULL; +static apr_table_t *server_config_defined_vars = NULL; + +AP_DECLARE_DATA int ap_main_state = AP_SQ_MS_INITIAL_STARTUP; +AP_DECLARE_DATA int ap_run_mode = AP_SQ_RM_UNKNOWN; +AP_DECLARE_DATA int ap_config_generation = 0; 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 = dir ? OPT_UNSET : OPT_UNSET|OPT_SYM_LINKS; conf->opts_add = conf->opts_remove = OPT_NONE; - conf->override = dir ? OR_UNSET : OR_UNSET|OR_ALL; + conf->override = OR_UNSET|OR_NONE; conf->override_opts = OPT_UNSET | OPT_ALL | OPT_SYM_OWNER | OPT_MULTI; - conf->content_md5 = 2; + conf->content_md5 = AP_CONTENT_MD5_UNSET; conf->accept_path_info = AP_ACCEPT_PATHINFO_UNSET; 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 + /* + * left as NULL (we use apr_pcalloc): + * conf->limit_cpu = NULL; + * conf->limit_mem = NULL; + * conf->limit_nproc = NULL; + * conf->sec_file = NULL; + * conf->sec_if = NULL; + */ 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; @@ -148,11 +161,12 @@ static void *create_core_dir_config(apr_pool_t *a, char *dir) conf->add_default_charset_name = DEFAULT_ADD_DEFAULT_CHARSET_NAME; /* Overriding all negotiation + * Set NULL by apr_pcalloc: + * conf->mime_type = NULL; + * conf->handler = NULL; + * conf->output_filters = NULL; + * conf->input_filters = NULL; */ - conf->mime_type = NULL; - conf->handler = NULL; - conf->output_filters = NULL; - conf->input_filters = NULL; /* * Flag for use of inodes in ETags. @@ -165,66 +179,14 @@ static void *create_core_dir_config(apr_pool_t *a, char *dir) conf->enable_sendfile = ENABLE_SENDFILE_UNSET; conf->allow_encoded_slashes = 0; conf->decode_encoded_slashes = 0; - + conf->max_ranges = AP_MAXRANGES_UNSET; + conf->max_overlaps = AP_MAXRANGES_UNSET; + conf->max_reversals = AP_MAXRANGES_UNSET; 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; @@ -241,6 +203,7 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) conf->d_is_fnmatch = new->d_is_fnmatch; conf->d_components = new->d_components; conf->r = new->r; + conf->condition = new->condition; if (new->opts & OPT_UNSET) { /* there was no explicit setting of new->opts, so we merge @@ -278,27 +241,15 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) 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->override_list == NULL) { + conf->override_list = new->override_list; } 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 + /* If we merge, the merge-result must have its own array */ conf->response_code_strings = apr_pmemdup(a, base->response_code_strings, @@ -317,11 +268,11 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) conf->hostname_lookups = new->hostname_lookups; } - if ((new->content_md5 & 2) == 0) { + if (new->content_md5 == AP_CONTENT_MD5_UNSET) { conf->content_md5 = new->content_md5; } - if (new->accept_path_info != 3) { + if (new->accept_path_info != AP_ACCEPT_PATHINFO_UNSET) { conf->accept_path_info = new->accept_path_info; } @@ -364,22 +315,23 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) conf->sec_file = new->sec_file; } else if (new->sec_file) { - /* If we merge, the merge-result must have it's own array + /* If we merge, the merge-result must have its 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 (!conf->sec_if) { + conf->sec_if = new->sec_if; } + else if (new->sec_if) { + /* If we merge, the merge-result must have its own array + */ + conf->sec_if = apr_array_append(a, base->sec_if, new->sec_if); + } + /* Otherwise we simply use the base->sec_if array + */ if (new->server_signature != srv_sig_unset) { conf->server_signature = new->server_signature; @@ -408,21 +360,6 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) 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. */ @@ -455,11 +392,35 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) conf->allow_encoded_slashes = new->allow_encoded_slashes; conf->decode_encoded_slashes = new->decode_encoded_slashes; + if (new->log) { + if (!conf->log) { + conf->log = new->log; + } + else { + conf->log = ap_new_log_config(a, new->log); + ap_merge_log_config(base->log, conf->log); + } + } + conf->max_ranges = new->max_ranges != AP_MAXRANGES_UNSET ? new->max_ranges : base->max_ranges; + conf->max_overlaps = new->max_overlaps != AP_MAXRANGES_UNSET ? new->max_overlaps : base->max_overlaps; + conf->max_reversals = new->max_reversals != AP_MAXRANGES_UNSET ? new->max_reversals : base->max_reversals; return (void*)conf; } +#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 +#endif + static void *create_core_server_config(apr_pool_t *a, server_rec *s) { core_server_config *conf; @@ -467,40 +428,44 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s) conf = (core_server_config *)apr_pcalloc(a, sizeof(core_server_config)); -#ifdef GPROF - conf->gprof_dir = NULL; + /* global-default / global-only settings */ + + if (!is_virtual) { + conf->ap_document_root = DOCUMENT_LOCATION; + conf->access_name = DEFAULT_ACCESS_FNAME; + + /* A mapping only makes sense in the global context */ + conf->accf_map = apr_table_make(a, 5); +#if APR_HAS_SO_ACCEPTFILTER + apr_table_setn(conf->accf_map, "http", ACCEPT_FILTER_NAME); + apr_table_setn(conf->accf_map, "https", "dataready"); +#else + apr_table_setn(conf->accf_map, "http", "data"); + apr_table_setn(conf->accf_map, "https", "data"); #endif + } + /* pcalloc'ed - we have NULL's/0's + else ** is_virtual ** { + conf->ap_document_root = NULL; + conf->access_name = NULL; + conf->accf_map = NULL; + } + */ + + /* initialization, no special case for global context */ - 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 */ + /* pcalloc'ed - we have NULL's/0's + conf->gprof_dir = NULL; + + ** recursion stopper; 0 == unset + conf->redirect_limit = 0; 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; @@ -511,36 +476,44 @@ 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_pmemdup(p, virt, sizeof(core_server_config)); + core_server_config *conf = (core_server_config *) + apr_pmemdup(p, base, 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 (virt->ap_document_root) + conf->ap_document_root = virt->ap_document_root; - if (!conf->protocol) { - conf->protocol = base->protocol; - } + if (virt->access_name) + conf->access_name = virt->access_name; + /* XXX optimize to keep base->sec_ pointers if virt->sec_ array is empty */ 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; + if (virt->redirect_limit) + conf->redirect_limit = virt->redirect_limit; + + if (virt->subreq_limit) + conf->subreq_limit = virt->subreq_limit; + + if (virt->trace_enable != AP_TRACE_UNSET) + conf->trace_enable = virt->trace_enable; + + /* no action for virt->accf_map, not allowed per-vhost */ + + if (virt->protocol) + conf->protocol = virt->protocol; + + if (virt->gprof_dir) + conf->gprof_dir = virt->gprof_dir; - conf->subreq_limit = virt->subreq_limit - ? virt->subreq_limit - : base->subreq_limit; + if (virt->error_log_format) + conf->error_log_format = virt->error_log_format; - conf->trace_enable = (virt->trace_enable != AP_TRACE_UNSET) - ? virt->trace_enable - : base->trace_enable; + if (virt->error_log_conn) + conf->error_log_conn = virt->error_log_conn; + + if (virt->error_log_req) + conf->error_log_req = virt->error_log_req; return conf; } @@ -551,8 +524,7 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) 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); + core_server_config *sconf = ap_get_core_module_config(s->module_config); void **new_space = (void **)apr_array_push(sconf->sec_dir); *new_space = dir_config; @@ -560,20 +532,56 @@ AP_CORE_DECLARE(void) ap_add_per_dir_conf(server_rec *s, void *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); + core_server_config *sconf = ap_get_core_module_config(s->module_config); 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) +AP_CORE_DECLARE(void) ap_add_file_conf(apr_pool_t *p, core_dir_config *conf, + void *url_config) { - void **new_space = (void **)apr_array_push(conf->sec_file); + void **new_space; + + if (!conf->sec_file) + conf->sec_file = apr_array_make(p, 2, sizeof(ap_conf_vector_t *)); + new_space = (void **)apr_array_push(conf->sec_file); *new_space = url_config; } +AP_CORE_DECLARE(const char *) ap_add_if_conf(apr_pool_t *p, + core_dir_config *conf, + void *if_config) +{ + void **new_space; + core_dir_config *new = ap_get_module_config(if_config, &core_module); + + if (!conf->sec_if) { + conf->sec_if = apr_array_make(p, 2, sizeof(ap_conf_vector_t *)); + } + if (new->condition_ifelse & AP_CONDITION_ELSE) { + int have_if = 0; + if (conf->sec_if->nelts > 0) { + core_dir_config *last; + ap_conf_vector_t *lastelt = APR_ARRAY_IDX(conf->sec_if, + conf->sec_if->nelts - 1, + ap_conf_vector_t *); + last = ap_get_module_config(lastelt, &core_module); + if (last->condition_ifelse & AP_CONDITION_IF) + have_if = 1; + } + if (!have_if) + return "<Else> or <ElseIf> section without previous <If> or " + "<ElseIf> section in same scope"; + } + + new_space = (void **)apr_array_push(conf->sec_if); + *new_space = if_config; + return NULL; +} + + /* 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 @@ -591,8 +599,8 @@ static int reorder_sorter(const void *va, const void *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); + core_a = ap_get_core_module_config(a->elt); + core_b = ap_get_core_module_config(b->elt); /* a regex always sorts after a non-regex */ @@ -628,7 +636,7 @@ void ap_core_reorder_directories(apr_pool_t *p, server_rec *s) int i; apr_pool_t *tmp; - sconf = ap_get_module_config(s->module_config, &core_module); + sconf = ap_get_core_module_config(s->module_config); sec_dir = sconf->sec_dir; nelts = sec_dir->nelts; elts = (ap_conf_vector_t **)sec_dir->elts; @@ -671,89 +679,103 @@ void ap_core_reorder_directories(apr_pool_t *p, server_rec *s) 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); + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); - /* Per comment in http_core.h - the OPT_INC_WITH_EXEC bit is - * inverted, such that the exposed semantics match that of - * OPT_INCNOEXEC; i.e., the bit is only enabled if exec= is *not* - * permitted. */ - if (conf->opts & OPT_INCLUDES) { - return conf->opts ^ OPT_INC_WITH_EXEC; - } - else { - return conf->opts; - } + 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); + conf = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); return conf->override; } +/* + * Optional function coming from mod_authn_core, used for + * retrieving the type of autorization + */ +static APR_OPTIONAL_FN_TYPE(authn_ap_auth_type) *authn_ap_auth_type; + 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; + if (authn_ap_auth_type) { + return authn_ap_auth_type(r); + } + return NULL; } +/* + * Optional function coming from mod_authn_core, used for + * retrieving the authorization realm + */ +static APR_OPTIONAL_FN_TYPE(authn_ap_auth_name) *authn_ap_auth_name; + 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; + if (authn_ap_auth_name) { + return authn_ap_auth_name(r); + } + return NULL; } -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); +/* + * Optional function coming from mod_access_compat, used to determine how + access control interacts with authentication/authorization + */ +static APR_OPTIONAL_FN_TYPE(access_compat_ap_satisfies) *access_compat_ap_satisfies; - return conf->ap_default_type - ? conf->ap_default_type - : DEFAULT_CONTENT_TYPE; +AP_DECLARE(int) ap_satisfies(request_rec *r) +{ + if (access_compat_ap_satisfies) { + return access_compat_ap_satisfies(r); + } + return SATISFY_NOSPEC; } 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; + core_server_config *sconf; + core_request_config *rconf = ap_get_core_module_config(r->request_config); + if (rconf->document_root) + return rconf->document_root; + sconf = ap_get_core_module_config(r->server->module_config); + return sconf->ap_document_root; } -AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r) +AP_DECLARE(const char *) ap_context_prefix(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; + core_request_config *conf = ap_get_core_module_config(r->request_config); + if (conf->context_prefix) + return conf->context_prefix; + else + return ""; } -AP_DECLARE(int) ap_satisfies(request_rec *r) +AP_DECLARE(const char *) ap_context_document_root(request_rec *r) { - core_dir_config *conf; + core_request_config *conf = ap_get_core_module_config(r->request_config); + if (conf->context_document_root) + return conf->context_document_root; + else + return ap_document_root(r); +} - conf = (core_dir_config *)ap_get_module_config(r->per_dir_config, - &core_module); +AP_DECLARE(void) ap_set_document_root(request_rec *r, const char *document_root) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + conf->document_root = document_root; +} - return conf->satisfy[r->method_number]; +AP_DECLARE(void) ap_set_context_info(request_rec *r, const char *context_prefix, + const char *context_document_root) +{ + core_request_config *conf = ap_get_core_module_config(r->request_config); + if (context_prefix) + conf->context_prefix = context_prefix; + if (context_document_root) + conf->context_document_root = context_document_root; } /* Should probably just get rid of this... the only code that cares is @@ -764,19 +786,16 @@ AP_DECLARE(int) ap_satisfies(request_rec *r) char *ap_response_code_string(request_rec *r, int error_index) { core_dir_config *dirconf; - core_request_config *reqconf; + core_request_config *reqconf = ap_get_core_module_config(r->request_config); /* 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); + dirconf = ap_get_core_module_config(r->per_dir_config); if (dirconf->response_code_strings == NULL) { return NULL; @@ -810,7 +829,7 @@ static APR_INLINE void do_double_reverse (conn_rec *conn) 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)) { + if (apr_sockaddr_equal(sa, conn->client_addr)) { conn->double_reverse = 1; return; } @@ -835,9 +854,8 @@ AP_DECLARE(const char *) ap_get_remote_host(conn_rec *conn, void *dir_config, /* 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; + hostname_lookups = ((core_dir_config *)ap_get_core_module_config(dir_config)) + ->hostname_lookups; if (hostname_lookups == HOSTNAME_LOOKUP_UNSET) { hostname_lookups = HOSTNAME_LOOKUP_OFF; @@ -853,7 +871,7 @@ AP_DECLARE(const char *) ap_get_remote_host(conn_rec *conn, void *dir_config, && (type == REMOTE_DOUBLE_REV || hostname_lookups != HOSTNAME_LOOKUP_OFF)) { - if (apr_getnameinfo(&conn->remote_host, conn->remote_addr, 0) + if (apr_getnameinfo(&conn->remote_host, conn->client_addr, 0) == APR_SUCCESS) { ap_str_tolower(conn->remote_host); @@ -892,7 +910,7 @@ AP_DECLARE(const char *) ap_get_remote_host(conn_rec *conn, void *dir_config, } else { *str_is_ip = 1; - return conn->remote_ip; + return conn->client_ip; } } } @@ -932,8 +950,7 @@ AP_DECLARE(const char *) ap_get_server_name(request_rec *r) core_dir_config *d; const char *retval; - d = (core_dir_config *)ap_get_module_config(r->per_dir_config, - &core_module); + d = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); switch (d->use_canonical_name) { case USE_CANONICAL_NAME_ON: @@ -956,7 +973,7 @@ AP_DECLARE(const char *) ap_get_server_name(request_rec *r) retval = r->hostname ? r->hostname : r->server->server_hostname; break; default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00109) "ap_get_server_name: Invalid UCN Option somehow"); retval = "localhost"; break; @@ -969,7 +986,7 @@ AP_DECLARE(const char *) ap_get_server_name(request_rec *r) * 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) +AP_DECLARE(const char *) ap_get_server_name_for_url(request_rec *r) { const char *plain_server_name = ap_get_server_name(r); @@ -985,7 +1002,7 @@ 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); + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); switch (d->use_canonical_name) { case USE_CANONICAL_NAME_OFF: @@ -1019,7 +1036,7 @@ AP_DECLARE(apr_port_t) ap_get_server_port(const request_rec *r) ap_default_port(r); break; default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00110) "ap_get_server_port: Invalid UCN Option somehow"); port = ap_default_port(r); break; @@ -1032,7 +1049,7 @@ 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); + const char *host = ap_get_server_name_for_url(r); if (ap_is_default_port(port, r)) { return apr_pstrcat(p, ap_http_scheme(r), "://", host, uri, NULL); @@ -1044,7 +1061,7 @@ AP_DECLARE(char *) ap_construct_url(apr_pool_t *p, const char *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); + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); if (d->limit_req_body == AP_LIMIT_REQ_BODY_UNSET) { return AP_DEFAULT_LIMIT_REQ_BODY; @@ -1089,9 +1106,16 @@ AP_DECLARE(const char *) ap_check_cmd_context(cmd_parms *cmd, " cannot occur within <VirtualHost> section", NULL); } - if ((forbidden & NOT_IN_LIMIT) && cmd->limited != -1) { + if ((forbidden & (NOT_IN_LIMIT | NOT_IN_DIR_LOC_FILE)) + && cmd->limited != -1) { return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, - " cannot occur within <Limit> section", NULL); + " cannot occur within <Limit> or <LimitExcept> " + "section", NULL); + } + + if ((forbidden & NOT_IN_HTACCESS) && (cmd->pool == cmd->temp_pool)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, + " cannot occur within htaccess files", NULL); } if ((forbidden & NOT_IN_DIR_LOC_FILE) == NOT_IN_DIR_LOC_FILE) { @@ -1129,10 +1153,9 @@ 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); + core_server_config *conf = ap_get_core_module_config(sconf); - const char *err = ap_check_cmd_context(cmd, - NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; } @@ -1141,19 +1164,205 @@ static const char *set_access_name(cmd_parms *cmd, void *dummy, return NULL; } +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, '}'))) { + char *name = apr_pstrndup(p, s+2, e-s-2); + word = NULL; + if (server_config_defined_vars) + word = apr_table_get(server_config_defined_vars, name); + if (!word) + word = getenv(name); + if (word) { + current->string = word; + current->len = strlen(word); + outlen += current->len; + } + else { + if (ap_strchr(name, ':') == 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(00111) + "Config variable ${%s} is not defined", + name); + 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; +} + +static int reset_config_defines(void *dummy) +{ + ap_server_config_defines = saved_server_config_defines; + server_config_defined_vars = NULL; + return OK; +} + +/* + * Make sure we can revert the effects of Define/UnDefine when restarting. + * This function must be called once per loading of the config, before + * ap_server_config_defines is changed. This may be during reading of the + * config, which is even before the pre_config hook is run (due to + * EXEC_ON_READ for Define/UnDefine). + */ +static void init_config_defines(apr_pool_t *pconf) +{ + saved_server_config_defines = ap_server_config_defines; + /* Use apr_array_copy instead of apr_array_copy_hdr because it does not + * protect from the way unset_define removes entries. + */ + ap_server_config_defines = apr_array_copy(pconf, ap_server_config_defines); +} + +static const char *set_define(cmd_parms *cmd, void *dummy, + const char *name, const char *value) +{ + const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS); + if (err) + return err; + if (ap_strchr_c(name, ':') != NULL) + return "Variable name must not contain ':'"; + + if (!saved_server_config_defines) + init_config_defines(cmd->pool); + if (!ap_exists_config_define(name)) { + char **newv = (char **)apr_array_push(ap_server_config_defines); + *newv = apr_pstrdup(cmd->pool, name); + } + if (value) { + if (!server_config_defined_vars) + server_config_defined_vars = apr_table_make(cmd->pool, 5); + apr_table_setn(server_config_defined_vars, name, value); + } + + return NULL; +} + +static const char *unset_define(cmd_parms *cmd, void *dummy, + const char *name) +{ + int i; + char **defines; + const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS); + if (err) + return err; + if (ap_strchr_c(name, ':') != NULL) + return "Variable name must not contain ':'"; + + if (!saved_server_config_defines) + init_config_defines(cmd->pool); + + defines = (char **)ap_server_config_defines->elts; + for (i = 0; i < ap_server_config_defines->nelts; i++) { + if (strcmp(defines[i], name) == 0) { + defines[i] = apr_array_pop(ap_server_config_defines); + break; + } + } + + if (server_config_defined_vars) { + apr_table_unset(server_config_defined_vars, name); + } + + return NULL; +} + +static const char *generate_error(cmd_parms *cmd, void *dummy, + const char *arg) +{ + if (!arg || !*arg) { + return "The Error directive was used with no message."; + } + + if (*arg == '"' || *arg == '\'') { /* strip off quotes */ + apr_size_t len = strlen(arg); + char last = *(arg + len - 1); + + if (*arg == last) { + return apr_pstrndup(cmd->pool, arg + 1, len - 2); + } + } + + return arg; +} + #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); + core_server_config *conf = ap_get_core_module_config(sconf); - const char *err = ap_check_cmd_context(cmd, - NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; } - conf->gprof_dir = apr_pstrdup(cmd->pool, arg); + conf->gprof_dir = arg; return NULL; } #endif /*GPROF*/ @@ -1163,11 +1372,6 @@ static const char *set_add_default_charset(cmd_parms *cmd, { 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; } @@ -1187,10 +1391,9 @@ 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); + core_server_config *conf = ap_get_core_module_config(sconf); - const char *err = ap_check_cmd_context(cmd, - NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; } @@ -1213,7 +1416,7 @@ static const char *set_document_root(cmd_parms *cmd, void *dummy, || !ap_is_directory(cmd->pool, arg)) { if (cmd->server->is_virtual) { ap_log_perror(APLOG_MARK, APLOG_STARTUP, 0, - cmd->pool, + cmd->pool, APLOGNO(00112) "Warning: DocumentRoot [%s] does not exist", arg); conf->ap_document_root = arg; @@ -1228,8 +1431,7 @@ static const char *set_document_root(cmd_parms *cmd, void *dummy, 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); + core_request_config *conf = ap_get_core_module_config(r->request_config); int idx; if (conf->response_code_strings == NULL) { @@ -1252,11 +1454,6 @@ static const char *set_error_document(cmd_parms *cmd, void *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 */ @@ -1284,7 +1481,7 @@ static const char *set_error_document(cmd_parms *cmd, void *conf_, /* 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, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, cmd->server, APLOGNO(00113) "cannot use a full URL in a 401 ErrorDocument " "directive --- ignoring!"); } @@ -1382,22 +1579,20 @@ 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; - } + const char *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, + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00114) "Useless use of AllowOverride in line %d of %s.", cmd->directive->line_num, cmd->directive->filename); } + if ((err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS)) != NULL) + return err; d->override = OR_NONE; while (l[0]) { - w = ap_getword_conf(cmd->pool, &l); + w = ap_getword_conf(cmd->temp_pool, &l); k = w; v = strchr(k, '='); @@ -1413,7 +1608,7 @@ static const char *set_override(cmd_parms *cmd, void *d_, const char *l) if (v) set_allow_opts(cmd, &(d->override_opts), v); else - d->override_opts = OPT_ALL; + d->override_opts = OPT_SYM_LINKS; } else if (!strcasecmp(w, "FileInfo")) { d->override |= OR_FILEINFO; @@ -1440,23 +1635,68 @@ static const char *set_override(cmd_parms *cmd, void *d_, const char *l) return NULL; } +static const char *set_override_list(cmd_parms *cmd, void *d_, int argc, char *const argv[]) +{ + core_dir_config *d = d_; + int i; + const char *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, APLOGNO(00115) + "Useless use of AllowOverrideList in line %d of %s.", + cmd->directive->line_num, cmd->directive->filename); + } + if ((err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS)) != NULL) + return err; + + d->override_list = apr_table_make(cmd->pool, 1); + + for (i=0;i<argc;i++){ + if (!strcasecmp(argv[i], "None")) { + return NULL; + } + else { + const command_rec *result = NULL; + module *mod = ap_top_module; + result = ap_find_command_in_modules(argv[i], &mod); + if (result) + apr_table_set(d->override_list, argv[i], "1"); + else + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00116) + "Discarding unrecognized directive `%s' in AllowOverrideList.", + argv[i]); + } + } + + 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; + int merge = 0; + int all_none = 0; char action; while (l[0]) { - char *w = ap_getword_conf(cmd->pool, &l); + char *w = ap_getword_conf(cmd->temp_pool, &l); action = '\0'; if (*w == '+' || *w == '-') { action = *(w++); + if (!merge && !first && !all_none) { + return "Either all Options must start with + or -, or no Option may."; + } + merge = 1; } else if (first) { - d->opts = OPT_NONE; - first = 0; + d->opts = OPT_NONE; + } + else if (merge) { + return "Either all Options must start with + or -, or no Option may."; } if (!strcasecmp(w, "Indexes")) { @@ -1484,10 +1724,24 @@ static const char *set_options(cmd_parms *cmd, void *d_, const char *l) opt = OPT_MULTI|OPT_EXECCGI; } else if (!strcasecmp(w, "None")) { + if (!first) { + return "'Options None' must be the first Option given."; + } + else if (merge) { /* Only works since None may not follow any other option. */ + return "You may not use 'Options +None' or 'Options -None'."; + } opt = OPT_NONE; + all_none = 1; } else if (!strcasecmp(w, "All")) { + if (!first) { + return "'Options All' must be the first option given."; + } + else if (merge) { /* Only works since All may not follow any other option. */ + return "You may not use 'Options +All' or 'Options -All'."; + } opt = OPT_ALL; + all_none = 1; } else { return apr_pstrcat(cmd->pool, "Illegal option ", w, NULL); @@ -1510,6 +1764,20 @@ static const char *set_options(cmd_parms *cmd, void *d_, const char *l) else { d->opts |= opt; } + + first = 0; + } + + return NULL; +} + +static const char *set_default_type(cmd_parms *cmd, void *d_, + const char *arg) +{ + if ((strcasecmp(arg, "off") != 0) && (strcasecmp(arg, "none") != 0)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00117) + "Ignoring deprecated use of DefaultType in line %d of %s.", + cmd->directive->line_num, cmd->directive->filename); } return NULL; @@ -1541,7 +1809,7 @@ static const char *set_etag_bits(cmd_parms *cmd, void *mconfig, action = '*'; bit = ETAG_UNSET; valid = 1; - token = ap_getword_conf(cmd->pool, &args); + token = ap_getword_conf(cmd->temp_pool, &args); if ((*token == '+') || (*token == '-')) { action = *token; token++; @@ -1652,11 +1920,6 @@ 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; @@ -1675,11 +1938,6 @@ 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; @@ -1694,46 +1952,6 @@ static const char *set_enable_sendfile(cmd_parms *cmd, void *d_, 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. @@ -1761,25 +1979,21 @@ AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, const char *limited_methods; void *tog = cmd->cmd->cmd_data; apr_int64_t limited = 0; + apr_int64_t old_limited = cmd->limited; 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); + limited_methods = apr_pstrndup(cmd->temp_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); + char *method = ap_getword_conf(cmd->temp_pool, &limited_methods); int methnum; /* check for builtin or module registered method number */ @@ -1792,7 +2006,8 @@ AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, /* 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); + methnum = ap_method_register(cmd->pool, + apr_pstrdup(cmd->pool, method)); } limited |= (AP_METHOD_BIT << methnum); @@ -1801,11 +2016,23 @@ AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, /* Killing two features with one function, * if (tog == NULL) <Limit>, else <LimitExcept> */ - cmd->limited = tog ? ~limited : limited; + limited = tog ? ~limited : limited; + + if (!(old_limited & limited)) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "> directive excludes all methods", NULL); + } + else if ((old_limited & limited) == old_limited) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + "> directive specifies methods already excluded", + NULL); + } + + cmd->limited &= limited; errmsg = ap_walk_config(cmd->directive->first_child, cmd, cmd->context); - cmd->limited = -1; + cmd->limited = old_limited; return errmsg; } @@ -1833,8 +2060,7 @@ static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg) 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; } @@ -1843,7 +2069,7 @@ static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg) return unclosed_directive(cmd); } - arg = apr_pstrndup(cmd->pool, arg, endp - arg); + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); if (!arg[0]) { return missing_container_arg(cmd); @@ -1930,8 +2156,7 @@ static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg) 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; } @@ -1940,7 +2165,7 @@ static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg) return unclosed_directive(cmd); } - arg = apr_pstrndup(cmd->pool, arg, endp - arg); + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); if (!arg[0]) { return missing_container_arg(cmd); @@ -1997,9 +2222,9 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg) 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); + const char *err = ap_check_cmd_context(cmd, + NOT_IN_LOCATION | NOT_IN_LIMIT); if (err != NULL) { return err; @@ -2009,7 +2234,7 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg) return unclosed_directive(cmd); } - arg = apr_pstrndup(cmd->pool, arg, endp - arg); + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); if (!arg[0]) { return missing_container_arg(cmd); @@ -2057,7 +2282,7 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg) conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0; conf->r = r; - ap_add_file_conf(c, new_file_conf); + ap_add_file_conf(cmd->pool, (core_dir_config *)mconfig, new_file_conf); if (*arg != '\0') { return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name, @@ -2070,34 +2295,99 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg) return NULL; } -static const char *start_ifmod(cmd_parms *cmd, void *mconfig, const char *arg) +#define COND_IF ((void *)1) +#define COND_ELSE ((void *)2) +#define COND_ELSEIF ((void *)3) + +static const char *ifsection(cmd_parms *cmd, void *mconfig, const char *arg) { + const char *errmsg; const char *endp = ap_strrchr_c(arg, '>'); - int not = (arg[0] == '!'); - module *found; + int old_overrides = cmd->override; + char *old_path = cmd->path; + core_dir_config *conf; + const command_rec *thiscmd = cmd->cmd; + ap_conf_vector_t *new_if_conf = ap_create_per_dir_config(cmd->pool); + const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT); + const char *condition; + const char *expr_err; + + if (err != NULL) { + return err; + } if (endp == NULL) { return unclosed_directive(cmd); } - arg = apr_pstrndup(cmd->pool, arg, endp - arg); + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); - if (not) { - arg++; + + /* Only if not an .htaccess file */ + if (!old_path) { + cmd->override = OR_ALL|ACCESS_CONF; } - if (!arg[0]) { - return missing_container_arg(cmd); + /* initialize our config and fetch it */ + conf = ap_set_config_vectors(cmd->server, new_if_conf, cmd->path, + &core_module, cmd->pool); + + if (cmd->cmd->cmd_data == COND_IF) + conf->condition_ifelse = AP_CONDITION_IF; + else if (cmd->cmd->cmd_data == COND_ELSEIF) + conf->condition_ifelse = AP_CONDITION_ELSEIF; + else if (cmd->cmd->cmd_data == COND_ELSE) + conf->condition_ifelse = AP_CONDITION_ELSE; + else + ap_assert(0); + + if (conf->condition_ifelse == AP_CONDITION_ELSE) { + if (arg[0]) + return "<Else> does not take an argument"; + } + else { + if (!arg[0]) + return missing_container_arg(cmd); + condition = ap_getword_conf(cmd->pool, &arg); + conf->condition = ap_expr_parse_cmd(cmd, condition, 0, &expr_err, NULL); + if (expr_err) + return apr_psprintf(cmd->pool, "Cannot parse condition clause: %s", + expr_err); } - found = ap_find_linked_module(arg); + errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_if_conf); + if (errmsg != NULL) + return errmsg; + + conf->d = cmd->path; + conf->d_is_fnmatch = 0; + conf->r = NULL; + + errmsg = ap_add_if_conf(cmd->pool, (core_dir_config *)mconfig, new_if_conf); + if (errmsg != NULL) + return errmsg; + + if (*arg != '\0') { + return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name, + "> arguments not supported.", NULL); + } + + cmd->path = old_path; + cmd->override = old_overrides; + + return NULL; +} + +static module *find_module(server_rec *s, const char *name) +{ + module *found = ap_find_linked_module(name); /* search prelinked stuff */ if (!found) { ap_module_symbol_t *current = ap_prelinked_module_symbols; for (; current->name; ++current) { - if (!strcmp(current->name, arg)) { + if (!strcmp(current->name, name)) { found = current->modp; break; } @@ -2110,10 +2400,52 @@ static const char *start_ifmod(cmd_parms *cmd, void *mconfig, const char *arg) APR_RETRIEVE_OPTIONAL_FN(ap_find_loaded_module_symbol); if (check_symbol) { - found = check_symbol(cmd->server, arg); + /* + * There are two phases where calling ap_find_loaded_module_symbol + * is problematic: + * + * During reading of the config, ap_server_conf is invalid but s + * points to the main server config, if passed from cmd->server + * of an EXEC_ON_READ directive. + * + * During config parsing, s may be a virtual host that would cause + * a segfault in mod_so if passed to ap_find_loaded_module_symbol, + * because mod_so's server config for vhosts is initialized later. + * But ap_server_conf is already set at this time. + * + * Therefore we use s if it is not virtual and ap_server_conf if + * s is virtual. + */ + found = check_symbol(s->is_virtual ? ap_server_conf : s, name); } } + return found; +} + + +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->temp_pool, arg, endp - arg); + + if (not) { + arg++; + } + + if (!arg[0]) { + return missing_container_arg(cmd); + } + + found = find_module(cmd->server, arg); + if ((!not && found) || (not && !found)) { ap_directive_t *parent = NULL; ap_directive_t *current = NULL; @@ -2156,7 +2488,7 @@ static const char *start_ifdefine(cmd_parms *cmd, void *dummy, const char *arg) return unclosed_directive(cmd); } - arg = apr_pstrndup(cmd->pool, arg, endp - arg); + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); if (arg[0] == '!') { not = 1; @@ -2203,7 +2535,7 @@ static const char *virtualhost_section(cmd_parms *cmd, void *dummy, return unclosed_directive(cmd); } - arg = apr_pstrndup(cmd->pool, arg, endp - arg); + arg = apr_pstrndup(cmd->temp_pool, arg, endp - arg); if (!arg[0]) { return missing_container_arg(cmd); @@ -2266,8 +2598,8 @@ 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); + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); char* proto; char* accf; if (err != NULL) { @@ -2278,31 +2610,29 @@ static const char *set_accf_map(cmd_parms *cmd, void *dummy, ap_str_tolower(proto); accf = apr_pstrdup(cmd->pool, iaccf); ap_str_tolower(accf); - apr_table_set(conf->accf_map, proto, accf); + apr_table_setn(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); + core_server_config *conf = ap_get_core_module_config(s->module_config); 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); + core_server_config *conf = ap_get_core_module_config(s->module_config); 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); char* proto; if (err != NULL) { @@ -2325,7 +2655,7 @@ static const char *set_server_string_slot(cmd_parms *cmd, void *dummy, char *struct_ptr = (char *)cmd->server; const char *err = ap_check_cmd_context(cmd, - NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); + NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; } @@ -2344,7 +2674,7 @@ static const char *set_server_string_slot(cmd_parms *cmd, void *dummy, 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 *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); const char *portstr, *part; char *scheme; int port; @@ -2353,12 +2683,16 @@ static const char *server_hostname_port(cmd_parms *cmd, void *dummy, const char return err; } + if (apr_fnmatch_test(arg)) + return apr_pstrcat(cmd->temp_pool, "Invalid ServerName \"", arg, + "\" use ServerAlias to set multiple server names.", NULL); + part = ap_strstr_c(arg, "://"); if (part) { - scheme = apr_pstrmemdup(cmd->pool, arg, part - arg); + scheme = apr_pstrndup(cmd->pool, arg, part - arg); ap_str_tolower(scheme); - cmd->server->server_scheme = scheme; + cmd->server->server_scheme = (const char *)scheme; part += 3; } else { part = arg; @@ -2366,8 +2700,8 @@ static const char *server_hostname_port(cmd_parms *cmd, void *dummy, const char portstr = ap_strchr_c(part, ':'); if (portstr) { - cmd->server->server_hostname = apr_pstrmemdup(cmd->pool, part, - portstr - part); + cmd->server->server_hostname = apr_pstrndup(cmd->pool, part, + portstr - part); portstr++; port = atoi(portstr); if (port <= 0 || port >= 65536) { /* 65536 == 1<<16 */ @@ -2389,11 +2723,6 @@ 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; @@ -2422,7 +2751,7 @@ static const char *set_server_root(cmd_parms *cmd, void *dummy, 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)) { + || !ap_is_directory(cmd->temp_pool, ap_server_root)) { return "ServerRoot must be a valid directory"; } @@ -2431,7 +2760,7 @@ static const char *set_server_root(cmd_parms *cmd, void *dummy, 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; @@ -2444,15 +2773,10 @@ static const char *set_timeout(cmd_parms *cmd, void *dummy, const char *arg) static const char *set_allow2f(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 (0 == strcasecmp(arg, "on")) { d->allow_encoded_slashes = 1; - d->decode_encoded_slashes = 1; + d->decode_encoded_slashes = 1; /* for compatibility with 2.0 & 2.2 */ } else if (0 == strcasecmp(arg, "off")) { d->allow_encoded_slashes = 0; d->decode_encoded_slashes = 0; @@ -2471,11 +2795,6 @@ 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; @@ -2496,7 +2815,7 @@ static const char *set_hostname_lookups(cmd_parms *cmd, void *d_, 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); if (err != NULL) { return err; @@ -2510,13 +2829,8 @@ static const char *set_serverpath(cmd_parms *cmd, void *dummy, 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; + d->content_md5 = arg ? AP_CONTENT_MD5_ON : AP_CONTENT_MD5_OFF; return NULL; } @@ -2544,11 +2858,6 @@ 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; @@ -2570,11 +2879,6 @@ 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; @@ -2589,13 +2893,13 @@ static const char *set_use_canonical_phys_port(cmd_parms *cmd, void *d_, return NULL; } - static const char *include_config (cmd_parms *cmd, void *dummy, const char *name) { ap_directive_t *conftree = NULL; - const char* conffile, *error; + const char *conffile, *error; unsigned *recursion; + int optional = cmd->cmd->cmd_data ? 1 : 0; void *data; apr_pool_userdata_get(&data, "ap_include_sentinel", cmd->pool); @@ -2610,8 +2914,8 @@ static const char *include_config (cmd_parms *cmd, void *dummy, 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.", + return apr_psprintf(cmd->pool, "Exceeded maximum include depth of %u, " + "There appears to be a recursion.", AP_MAX_INCLUDE_DEPTH); } @@ -2622,8 +2926,9 @@ static const char *include_config (cmd_parms *cmd, void *dummy, name, NULL); } - error = ap_process_resource_config(cmd->server, conffile, - &conftree, cmd->pool, cmd->temp_pool); + error = ap_process_fnmatch_configs(cmd->server, conffile, &conftree, + cmd->pool, cmd->temp_pool, + optional); if (error) { *recursion = 0; return error; @@ -2639,50 +2944,67 @@ static const char *include_config (cmd_parms *cmd, void *dummy, return NULL; } -static const char *set_loglevel(cmd_parms *cmd, void *dummy, const char *arg) +static const char *set_loglevel(cmd_parms *cmd, void *config_, const char *arg_) { - char *str; + char *level_str; + int level; + module *module; + char *arg = apr_pstrdup(cmd->temp_pool, arg_); + struct ap_logconf *log; + const char *err; + + if (cmd->path) { + core_dir_config *dconf = config_; + if (!dconf->log) { + dconf->log = ap_new_log_config(cmd->pool, NULL); + } + log = dconf->log; + } + else { + log = &cmd->server->log; + } - const char *err = ap_check_cmd_context(cmd, - NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT); - if (err != NULL) { - return err; + if (arg == NULL) + return "LogLevel requires level keyword or module loglevel specifier"; + + level_str = ap_strrchr(arg, ':'); + + if (level_str == NULL) { + err = ap_parse_log_level(arg, &log->level); + if (err != NULL) + return err; + ap_reset_module_loglevels(log, APLOG_NO_MODULE); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, cmd->server, + "Setting LogLevel for all modules to %s", arg); + return NULL; } - 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"; - } + *level_str++ = '\0'; + if (!*level_str) { + return apr_psprintf(cmd->temp_pool, "Module specifier '%s' must be " + "followed by a log level keyword", arg); } - else { - return "LogLevel requires level keyword"; + + err = ap_parse_log_level(level_str, &level); + if (err != NULL) + return apr_psprintf(cmd->temp_pool, "%s:%s: %s", arg, level_str, err); + + if ((module = find_module(cmd->server, arg)) == NULL) { + char *name = apr_psprintf(cmd->temp_pool, "%s_module", arg); + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, cmd->server, + "Cannot find module '%s', trying '%s'", arg, name); + module = find_module(cmd->server, name); + } + + if (module == NULL) { + return apr_psprintf(cmd->temp_pool, "Cannot find module %s", arg); } + ap_set_module_loglevel(cmd->pool, log, module->module_index, level); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, cmd->server, + "Setting LogLevel for module %s to %s", module->name, + level_str); + return NULL; } @@ -2691,8 +3013,7 @@ 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); + conf = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); if ((conf->server_signature == srv_sig_off) || (conf->server_signature == srv_sig_unset)) { return ""; @@ -2720,19 +3041,6 @@ AP_DECLARE(const char *) ap_psignature(const char *prefix, request_rec *r) } /* - * 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 @@ -2742,14 +3050,14 @@ static const char *set_authname(cmd_parms *cmd, void *mconfig, static char *server_banner = NULL; static int banner_locked = 0; -static char *server_description = NULL; +static const char *server_description = NULL; 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_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; @@ -2782,14 +3090,6 @@ AP_DECLARE(const char *) ap_get_server_banner(void) return server_banner ? server_banner : AP_SERVER_BASEVERSION; } -/* ap_get_server_version() is deprecated. ap_get_server_banner() - * provides the same semantics. - */ -AP_DECLARE(const char *) ap_get_server_version(void) -{ - return ap_get_server_banner(); -} - AP_DECLARE(void) ap_add_version_component(apr_pool_t *pconf, const char *component) { if (! banner_locked) { @@ -2849,7 +3149,7 @@ static void set_banner(apr_pool_t *pconf) } static const char *set_serv_tokens(cmd_parms *cmd, void *dummy, - const char *arg) + const char *arg1, const char *arg2) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); @@ -2857,19 +3157,19 @@ static const char *set_serv_tokens(cmd_parms *cmd, void *dummy, return err; } - if (!strcasecmp(arg, "OS")) { + if (!strcasecmp(arg1, "OS")) { ap_server_tokens = SrvTk_OS; } - else if (!strcasecmp(arg, "Min") || !strcasecmp(arg, "Minimal")) { + else if (!strcasecmp(arg1, "Min") || !strcasecmp(arg1, "Minimal")) { ap_server_tokens = SrvTk_MINIMAL; } - else if (!strcasecmp(arg, "Major")) { + else if (!strcasecmp(arg1, "Major")) { ap_server_tokens = SrvTk_MAJOR; } - else if (!strcasecmp(arg, "Minor") ) { + else if (!strcasecmp(arg1, "Minor") ) { ap_server_tokens = SrvTk_MINOR; } - else if (!strcasecmp(arg, "Prod") || !strcasecmp(arg, "ProductOnly")) { + else if (!strcasecmp(arg1, "Prod") || !strcasecmp(arg1, "ProductOnly")) { ap_server_tokens = SrvTk_PRODUCT_ONLY; } else { @@ -2882,8 +3182,7 @@ static const char *set_serv_tokens(cmd_parms *cmd, void *dummy, 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); int lim; if (err != NULL) { @@ -2903,8 +3202,7 @@ static const char *set_limit_req_line(cmd_parms *cmd, void *dummy, 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); int lim; if (err != NULL) { @@ -2925,8 +3223,7 @@ static const char *set_limit_req_fieldsize(cmd_parms *cmd, void *dummy, 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); + const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE); int lim; if (err != NULL) { @@ -2948,13 +3245,8 @@ 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."; } @@ -2969,11 +3261,6 @@ 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) @@ -2987,31 +3274,84 @@ static const char *set_max_ranges(cmd_parms *cmd, void *conf_, const char *arg) core_dir_config *conf = conf_; int val = 0; - if (!strcasecmp(arg, "none")) { + if (!strcasecmp(arg, "none")) { val = AP_MAXRANGES_NORANGES; } - else if (!strcasecmp(arg, "default")) { + else if (!strcasecmp(arg, "default")) { val = AP_MAXRANGES_DEFAULT; } - else if (!strcasecmp(arg, "unlimited")) { + else if (!strcasecmp(arg, "unlimited")) { val = AP_MAXRANGES_UNLIMITED; } - else { + else { val = atoi(arg); if (val <= 0) - return "MaxRanges requires 'none', 'default', 'unlimited' or " + return "MaxRanges requires 'none', 'default', 'unlimited' or " "a positive integer"; } conf->max_ranges = val; - + return NULL; } + +static const char *set_max_overlaps(cmd_parms *cmd, void *conf_, const char *arg) +{ + core_dir_config *conf = conf_; + int val = 0; + + if (!strcasecmp(arg, "none")) { + val = AP_MAXRANGES_NORANGES; + } + else if (!strcasecmp(arg, "default")) { + val = AP_MAXRANGES_DEFAULT; + } + else if (!strcasecmp(arg, "unlimited")) { + val = AP_MAXRANGES_UNLIMITED; + } + else { + val = atoi(arg); + if (val <= 0) + return "MaxRangeOverlaps requires 'none', 'default', 'unlimited' or " + "a positive integer"; + } + + conf->max_overlaps = val; + + return NULL; +} + +static const char *set_max_reversals(cmd_parms *cmd, void *conf_, const char *arg) +{ + core_dir_config *conf = conf_; + int val = 0; + + if (!strcasecmp(arg, "none")) { + val = AP_MAXRANGES_NORANGES; + } + else if (!strcasecmp(arg, "default")) { + val = AP_MAXRANGES_DEFAULT; + } + else if (!strcasecmp(arg, "unlimited")) { + val = AP_MAXRANGES_UNLIMITED; + } + else { + val = atoi(arg); + if (val <= 0) + return "MaxRangeReversals requires 'none', 'default', 'unlimited' or " + "a positive integer"; + } + + conf->max_reversals = val; + + 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); + conf = ap_get_core_module_config(r->per_dir_config); if (conf->limit_xml_body == AP_LIMIT_UNSET) return AP_DEFAULT_LIMIT_XML_BODY; @@ -3022,7 +3362,7 @@ AP_DECLARE(size_t) ap_get_limit_xml_body(const request_rec *r) 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, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, cmd->server, APLOGNO(00118) "%s not supported on this platform", cmd->cmd->name); return NULL; @@ -3035,7 +3375,7 @@ static const char *set_limit_cpu(cmd_parms *cmd, void *conf_, { core_dir_config *conf = conf_; - unixd_set_rlimit(cmd, &conf->limit_cpu, arg, arg2, RLIMIT_CPU); + ap_unixd_set_rlimit(cmd, &conf->limit_cpu, arg, arg2, RLIMIT_CPU); return NULL; } #endif @@ -3047,11 +3387,11 @@ static const char *set_limit_mem(cmd_parms *cmd, void *conf_, core_dir_config *conf = conf_; #if defined(RLIMIT_AS) - unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2 ,RLIMIT_AS); + ap_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); + ap_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); + ap_unixd_set_rlimit(cmd, &conf->limit_mem, arg, arg2, RLIMIT_VMEM); #endif return NULL; @@ -3064,7 +3404,7 @@ static const char *set_limit_nproc(cmd_parms *cmd, void *conf_, { core_dir_config *conf = conf_; - unixd_set_rlimit(cmd, &conf->limit_nproc, arg, arg2, RLIMIT_NPROC); + ap_unixd_set_rlimit(cmd, &conf->limit_nproc, arg, arg2, RLIMIT_NPROC); return NULL; } #endif @@ -3072,15 +3412,15 @@ static const char *set_limit_nproc(cmd_parms *cmd, void *conf_, 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); + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); 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, + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00119) "Limiting internal redirects to very low numbers may " "cause normal requests to fail."); } @@ -3094,7 +3434,7 @@ static const char *set_recursion_limit(cmd_parms *cmd, void *dummy, return "The recursion limit must be greater than zero."; } if (limit < 4) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00120) "Limiting the subrequest depth to a very low level may" " cause normal requests to fail."); } @@ -3109,20 +3449,20 @@ static void log_backtrace(const request_rec *r) { const request_rec *top = r; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00121) "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, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00122) "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, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00123) "subrequested from r->uri = %s", top->uri ? top->uri : "(unexpectedly NULL)"); } @@ -3134,8 +3474,8 @@ static void log_backtrace(const request_rec *r) */ 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); + core_server_config *conf = + ap_get_core_module_config(r->server->module_config); const request_rec *top = r; int redirects = 0, subreqs = 0; int rlimit = conf->redirect_limit @@ -3150,7 +3490,7 @@ AP_DECLARE(int) ap_is_recursion_limit_exceeded(const request_rec *r) if (top->prev) { if (++redirects >= rlimit) { /* uuh, too much. */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00124) "Request exceeded the limit of %d internal " "redirects due to probable configuration error. " "Use 'LimitInternalRecursion' to increase the " @@ -3170,9 +3510,9 @@ AP_DECLARE(int) ap_is_recursion_limit_exceeded(const request_rec *r) if (!top->prev && top->main) { if (++subreqs >= slimit) { /* uuh, too much. */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00125) "Request exceeded the limit of %d subrequest " - "nesting levels due to probable confguration " + "nesting levels due to probable configuration " "error. Use 'LimitInternalRecursion' to increase " "the limit if necessary. Use 'LogLevel debug' to " "get a backtrace.", slimit); @@ -3192,109 +3532,280 @@ AP_DECLARE(int) ap_is_recursion_limit_exceeded(const request_rec *r) return 0; } -static const char *add_ct_output_filters(cmd_parms *cmd, void *conf_, - const char *arg, const char *arg2) +static const char *set_trace_enable(cmd_parms *cmd, void *dummy, + const char *arg1) { - core_dir_config *conf = conf_; - ap_filter_rec_t *old, *new = NULL; - const char *filter_name; + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); - if (!conf->ct_output_filters) { - conf->ct_output_filters = apr_hash_make(cmd->pool); - old = NULL; + 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 { - 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; - } - } + return "TraceEnable must be one of 'on', 'off', or 'extended'"; } - 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; + return NULL; +} + +static apr_hash_t *errorlog_hash; + +static int log_constant_item(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + char *end = apr_cpystrn(buf, arg, buflen); + return end - buf; +} + +static char *parse_errorlog_misc_string(apr_pool_t *p, + ap_errorlog_format_item *it, + const char **sa) +{ + const char *s; + char scratch[MAX_STRING_LEN]; + char *d = scratch; + /* + * non-leading white space terminates this string to allow the next field + * separator to be inserted + */ + int at_start = 1; - /* We found something, so let's append it. */ - if (old) { - old->next = new; + it->func = log_constant_item; + s = *sa; + + while (*s && *s != '%' && (*s != ' ' || at_start) && d < scratch + MAX_STRING_LEN) { + if (*s != '\\') { + if (*s != ' ') { + at_start = 0; + } + *d++ = *s++; } else { - apr_hash_set(conf->ct_output_filters, arg2, - APR_HASH_KEY_STRING, new); + s++; + switch (*s) { + case 'r': + *d++ = '\r'; + s++; + break; + case 'n': + *d++ = '\n'; + s++; + break; + case 't': + *d++ = '\t'; + s++; + break; + case '\0': + /* handle end of string */ + *d++ = '\\'; + break; + default: + /* copy next char verbatim */ + *d++ = *s++; + break; + } } - old = new; - } - - if (!new) { - return "invalid filter name"; } + *d = '\0'; + it->arg = apr_pstrdup(p, scratch); + *sa = s; 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) + +static char *parse_errorlog_item(apr_pool_t *p, ap_errorlog_format_item *it, + const char **sa) { - core_dir_config *conf; - const char *ctype; + const char *s = *sa; + ap_errorlog_handler *handler; + int i; - conf = (core_dir_config *)ap_get_module_config(r->per_dir_config, - &core_module); + if (*s != '%') { + if (*s == ' ') { + it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP; + } + return parse_errorlog_misc_string(p, it, sa); + } - /* 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; + ++s; + + if (*s == ' ') { + /* percent-space (% ) is a field separator */ + it->flags |= AP_ERRORLOG_FLAG_FIELD_SEP; + *sa = ++s; + /* recurse */ + return parse_errorlog_item(p, it, sa); + } + else if (*s == '%') { + it->arg = "%"; + it->func = log_constant_item; + *sa = ++s; + return NULL; } - /* 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; + while (*s) { + switch (*s) { + case '{': + ++s; + it->arg = ap_getword(p, &s, '}'); + break; + case '+': + ++s; + it->flags |= AP_ERRORLOG_FLAG_REQUIRED; + break; + case '-': + ++s; + it->flags |= AP_ERRORLOG_FLAG_NULL_AS_HYPHEN; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + i = *s - '0'; + while (apr_isdigit(*++s)) + i = i * 10 + (*s) - '0'; + it->min_loglevel = i; + break; + case 'M': + it->func = NULL; + it->flags |= AP_ERRORLOG_FLAG_MESSAGE; + *sa = ++s; + return NULL; + default: + handler = (ap_errorlog_handler *)apr_hash_get(errorlog_hash, s, 1); + if (!handler) { + char dummy[2]; + + dummy[0] = *s; + dummy[1] = '\0'; + return apr_pstrcat(p, "Unrecognized error log format directive %", + dummy, NULL); + } + it->func = handler->func; + *sa = ++s; + return NULL; } } - return; + return "Ran off end of error log format parsing args to some directive"; } -static const char *set_trace_enable(cmd_parms *cmd, void *dummy, - const char *arg1) +static apr_array_header_t *parse_errorlog_string(apr_pool_t *p, + const char *s, + const char **err, + int is_main_fmt) { - core_server_config *conf = ap_get_module_config(cmd->server->module_config, - &core_module); + apr_array_header_t *a = apr_array_make(p, 30, + sizeof(ap_errorlog_format_item)); + char *res; + int seen_msg_fmt = 0; + + while (s && *s) { + ap_errorlog_format_item *item = + (ap_errorlog_format_item *)apr_array_push(a); + memset(item, 0, sizeof(*item)); + res = parse_errorlog_item(p, item, &s); + if (res) { + *err = res; + return NULL; + } + if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) { + if (!is_main_fmt) { + *err = "%M cannot be used in once-per-request or " + "once-per-connection formats"; + return NULL; + } + seen_msg_fmt = 1; + } + if (is_main_fmt && item->flags & AP_ERRORLOG_FLAG_REQUIRED) { + *err = "The '+' flag cannot be used in the main error log format"; + return NULL; + } + if (!is_main_fmt && item->min_loglevel) { + *err = "The loglevel cannot be used as a condition in " + "once-per-request or once-per-connection formats"; + return NULL; + } + if (item->min_loglevel > APLOG_TRACE8) { + *err = "The specified loglevel modifier is out of range"; + return NULL; + } + } - if (strcasecmp(arg1, "on") == 0) { - conf->trace_enable = AP_TRACE_ENABLE; + if (is_main_fmt && !seen_msg_fmt) { + *err = "main ErrorLogFormat must contain message format string '%M'"; + return NULL; } - else if (strcasecmp(arg1, "off") == 0) { - conf->trace_enable = AP_TRACE_DISABLE; + + return a; +} + +static const char *set_errorlog_format(cmd_parms *cmd, void *dummy, + const char *arg1, const char *arg2) +{ + const char *err_string = NULL; + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + + if (!arg2) { + conf->error_log_format = parse_errorlog_string(cmd->pool, arg1, + &err_string, 1); + } + else if (!strcasecmp(arg1, "connection")) { + if (!conf->error_log_conn) { + conf->error_log_conn = apr_array_make(cmd->pool, 5, + sizeof(apr_array_header_t *)); + } + + if (*arg2) { + apr_array_header_t **e; + e = (apr_array_header_t **) apr_array_push(conf->error_log_conn); + *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); + } } - else if (strcasecmp(arg1, "extended") == 0) { - conf->trace_enable = AP_TRACE_EXTENDED; + else if (!strcasecmp(arg1, "request")) { + if (!conf->error_log_req) { + conf->error_log_req = apr_array_make(cmd->pool, 5, + sizeof(apr_array_header_t *)); + } + + if (*arg2) { + apr_array_header_t **e; + e = (apr_array_header_t **) apr_array_push(conf->error_log_req); + *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); + } } else { - return "TraceEnable must be one of 'on', 'off', or 'extended'"; + err_string = "ErrorLogFormat type must be one of request, connection"; } - return NULL; + return err_string; +} + +AP_DECLARE(void) ap_register_errorlog_handler(apr_pool_t *p, char *tag, + ap_errorlog_handler_fn_t *handler, + int flags) +{ + ap_errorlog_handler *log_struct = apr_palloc(p, sizeof(*log_struct)); + log_struct->func = handler; + log_struct->flags = flags; + + apr_hash_set(errorlog_hash, tag, 1, (const void *)log_struct); } + /* Note --- ErrorDocument will now work from .htaccess files. * The AllowOverride of Fileinfo allows webmasters to turn it off */ @@ -3314,16 +3825,17 @@ AP_INIT_RAW_ARGS("<VirtualHost", virtualhost_section, NULL, RSRC_CONF, "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, +AP_INIT_RAW_ARGS("<Limit", ap_limit_section, NULL, OR_LIMIT | OR_AUTHCFG, "Container for authentication directives when accessed using specified HTTP " "methods"), -AP_INIT_RAW_ARGS("<LimitExcept", ap_limit_section, (void*)1, OR_ALL, +AP_INIT_RAW_ARGS("<LimitExcept", ap_limit_section, (void*)1, + OR_LIMIT | OR_AUTHCFG, "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"), + "Container for directives based on existence 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"), + "Container for directives based on existence of command line defines"), AP_INIT_RAW_ARGS("<DirectoryMatch", dirsection, (void*)1, RSRC_CONF, "Container for directives affecting resources located in the " "specified directories"), @@ -3332,15 +3844,6 @@ AP_INIT_RAW_ARGS("<LocationMatch", urlsection, (void*)1, RSRC_CONF, "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"), @@ -3349,6 +3852,18 @@ 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"), +AP_INIT_TAKE12("Define", set_define, NULL, EXEC_ON_READ|ACCESS_CONF|RSRC_CONF, + "Define a variable, optionally to a value. Same as passing -D to the command line."), +AP_INIT_TAKE1("UnDefine", unset_define, NULL, EXEC_ON_READ|ACCESS_CONF|RSRC_CONF, + "Undefine the existence of a variable. Undo a Define."), +AP_INIT_RAW_ARGS("Error", generate_error, NULL, OR_ALL, + "Generate error message from within configuration"), +AP_INIT_RAW_ARGS("<If", ifsection, COND_IF, OR_ALL, + "Container for directives to be conditionally applied"), +AP_INIT_RAW_ARGS("<ElseIf", ifsection, COND_ELSEIF, OR_ALL, + "Container for directives to be conditionally applied"), +AP_INIT_RAW_ARGS("<Else", ifsection, COND_ELSE, OR_ALL, + "Container for directives to be conditionally applied"), /* Old resource config file commands */ @@ -3361,11 +3876,13 @@ AP_INIT_TAKE2("ErrorDocument", set_error_document, NULL, OR_FILEINFO, 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_TAKE_ARGV("AllowOverrideList", set_override_list, NULL, ACCESS_CONF, + "Controls what individual 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_TAKE1("DefaultType", set_default_type, NULL, OR_FILEINFO, + "the default media type for otherwise untyped files (DEPRECATED)"), 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, @@ -3397,6 +3914,8 @@ AP_INIT_TAKE1("ServerRoot", set_server_root, NULL, RSRC_CONF | EXEC_ON_READ, 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_TAKE12("ErrorLogFormat", set_errorlog_format, NULL, RSRC_CONF, + "Format string for the ErrorLog"), 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, @@ -3415,13 +3934,19 @@ AP_INIT_TAKE1("UseCanonicalPhysicalPort", set_use_canonical_phys_port, NULL, /* 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, + "Name(s) of the config file(s) to be included; fails if the wildcard does " + "not match at least one file"), +AP_INIT_TAKE1("IncludeOptional", include_config, (void*)1, + (RSRC_CONF | ACCESS_CONF | EXEC_ON_READ), + "Name or pattern of the config file(s) to be included; ignored if the file " + "does not exist or the pattern does not match any files"), +AP_INIT_ITERATE("LogLevel", set_loglevel, NULL, RSRC_CONF|ACCESS_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_TAKE12("ServerTokens", set_serv_tokens, NULL, RSRC_CONF, + "Determine tokens displayed in the Server: header - Min(imal), " + "Major, Minor, Prod, 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, @@ -3435,10 +3960,18 @@ AP_INIT_TAKE1("LimitRequestBody", set_limit_req_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"), +AP_INIT_RAW_ARGS("Mutex", ap_set_mutex, NULL, RSRC_CONF, + "mutex (or \"default\") and mechanism"), AP_INIT_TAKE1("MaxRanges", set_max_ranges, NULL, RSRC_CONF|ACCESS_CONF, "Maximum number of Ranges in a request before returning the entire " "resource, or 0 for unlimited"), +AP_INIT_TAKE1("MaxRangeOverlaps", set_max_overlaps, NULL, RSRC_CONF|ACCESS_CONF, + "Maximum number of overlaps in Ranges in a request before returning the entire " + "resource, or 0 for unlimited"), +AP_INIT_TAKE1("MaxRangeReversals", set_max_reversals, NULL, RSRC_CONF|ACCESS_CONF, + "Maximum number of reversals in Ranges in a request before returning the entire " + "resource, or 0 for unlimited"), /* System Resource Controls */ #ifdef RLIMIT_CPU AP_INIT_TAKE12("RLimitCPU", set_limit_cpu, @@ -3481,59 +4014,43 @@ AP_INIT_TAKE1("SetOutputFilter", ap_set_string_slot, 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_TAKE1("AllowEncodedSlashes", set_allow2f, NULL, RSRC_CONF, "Allow URLs containing '/' encoded as '%2F'"), +/* scoreboard.c directives */ +AP_INIT_TAKE1("ScoreBoardFile", ap_set_scoreboard, NULL, RSRC_CONF, + "A file for Apache to maintain runtime process management information"), +AP_INIT_FLAG("ExtendedStatus", ap_set_extended_status, NULL, RSRC_CONF, + "\"On\" to track extended status information, \"Off\" to disable"), +AP_INIT_FLAG("SeeRequestTail", ap_set_reqtail, NULL, RSRC_CONF, + "For extended status, \"On\" to see the last 63 chars of " + "the request line, \"Off\" (default) to see the first 63"), + /* * 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. + * pay attention to. + * XXX These are not for all platforms, and even some Unix MPMs might not want + * some directives. */ -#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 + "Maximum number of connections a particular child serves before " + "dying. (DEPRECATED, use MaxConnectionsPerChild)"), +AP_INIT_TAKE1("MaxConnectionsPerChild", ap_mpm_set_max_requests, NULL, RSRC_CONF, + "Maximum number of connections a particular child serves before dying."), 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"), -#ifdef SUEXEC_BIN -AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF, - "Enable or disable suEXEC support"), -#endif { NULL } }; @@ -3544,9 +4061,8 @@ AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF, 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; + char *path; /* XXX this seems too specific, this should probably become * some general-case test @@ -3555,7 +4071,7 @@ AP_DECLARE_NONSTD(int) ap_core_translate(request_rec *r) return HTTP_FORBIDDEN; } if (!r->uri || ((r->uri[0] != '/') && strcmp(r->uri, "*"))) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00126) "Invalid URI in request %s", r->the_request); return HTTP_BAD_REQUEST; } @@ -3566,46 +4082,31 @@ AP_DECLARE_NONSTD(int) ap_core_translate(request_rec *r) || 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; + path = r->uri + r->server->pathlen; } 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; + path = r->uri; } + /* + * 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. + */ + while (*path == '/') { + ++path; + } + if ((rv = apr_filepath_merge(&r->filename, ap_document_root(r), path, + APR_FILEPATH_TRUENAME + | APR_FILEPATH_SECUREROOT, r->pool)) + != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00127) + "Cannot map %s to file", r->the_request); + return HTTP_FORBIDDEN; + } + r->canonical_filename = r->filename; return OK; } @@ -3636,8 +4137,7 @@ 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); + (core_dir_config *)ap_get_core_module_config(r->per_dir_config); /* Check for overrides with ForceType / SetHandler */ @@ -3653,9 +4153,9 @@ static int core_override_type(request_rec *r) * beginning of the fixup phase (here!), 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 AP_REQ_DEFAULT_PATH_INFO. - * (which the module may interpret to its own customary behavior.) + * (which the module may interpret to its own customary behavior.) * It won't be touched if the value is no longer AP_ACCEPT_PATHINFO_UNSET, - * so any module changing the value prior to the fixup phase + * 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) @@ -3685,9 +4185,8 @@ static int default_handler(request_rec *r) */ 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) + d = (core_dir_config *)ap_get_core_module_config(r->per_dir_config); + bld_content_md5 = (d->content_md5 == AP_CONTENT_MD5_ON) && r->output_filters->frec->ftype != AP_FTYPE_RESOURCE; ap_allow_standard_methods(r, MERGE_ALLOW, M_GET, M_OPTIONS, M_POST, -1); @@ -3702,8 +4201,8 @@ static int default_handler(request_rec *r) } 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, + if (r->finfo.filetype == APR_NOFILE) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00128) "File does not exist: %s", r->filename); return HTTP_NOT_FOUND; } @@ -3712,7 +4211,7 @@ static int default_handler(request_rec *r) * raw I/O on a dir. */ if (r->finfo.filetype == APR_DIR) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00129) "Attempt to serve directory: %s", r->filename); return HTTP_NOT_FOUND; } @@ -3721,7 +4220,7 @@ static int default_handler(request_rec *r) r->path_info && *r->path_info) { /* default to reject */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00130) "File does not exist: %s", apr_pstrcat(r->pool, r->filename, r->path_info, NULL)); return HTTP_NOT_FOUND; @@ -3738,10 +4237,10 @@ static int default_handler(request_rec *r) if (r->method_number != M_GET) { core_request_config *req_cfg; - req_cfg = ap_get_module_config(r->request_config, &core_module); + req_cfg = ap_get_core_module_config(r->request_config); if (!req_cfg->deliver_script) { /* The flag hasn't been set for this request. Punt. */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00131) "This resource does not accept the %s method.", r->method); return HTTP_METHOD_NOT_ALLOWED; @@ -3751,11 +4250,10 @@ static int default_handler(request_rec *r) 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) + | AP_SENDFILE_ENABLED(d->enable_sendfile) #endif , 0, r->pool)) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00132) "file permissions deny server access: %s", r->filename); return HTTP_FORBIDDEN; } @@ -3765,6 +4263,10 @@ static int default_handler(request_rec *r) ap_set_etag(r); ap_set_accept_ranges(r); ap_set_content_length(r, r->finfo.size); + if (bld_content_md5) { + apr_table_setn(r->headers_out, "Content-MD5", + ap_md5digest(r->pool, fd)); + } bb = apr_brigade_create(r->pool, c->bucket_alloc); @@ -3773,40 +4275,13 @@ static int default_handler(request_rec *r) 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); - } + e = apr_brigade_insert_file(bb, fd, 0, r->finfo.size, r->pool); #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); @@ -3820,7 +4295,7 @@ static int default_handler(request_rec *r) } else { /* no way to know what type of error occurred */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00133) "default_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; @@ -3828,8 +4303,19 @@ static int default_handler(request_rec *r) } 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); + /* See if this looks like an undecrypted SSL handshake attempt. + * It's safe to look a couple bytes into the_request if it exists, as it's + * always allocated at least MIN_LINE_ALLOC (80) bytes. + */ + if (r->the_request + && r->the_request[0] == 0x16 + && (r->the_request[1] == 0x2 || r->the_request[1] == 0x3)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00134) + "Invalid method in request %s - possible attempt to establish SSL connection on non-SSL port", r->the_request); + } else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00135) + "Invalid method in request %s", r->the_request); + } return HTTP_NOT_IMPLEMENTED; } @@ -3843,23 +4329,61 @@ static int default_handler(request_rec *r) /* 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; +APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *ap__logio_add_bytes_out; +APR_OPTIONAL_FN_TYPE(authz_some_auth_required) *ap__authz_ap_some_auth_required; + +/* Insist that at least one module will undertake to provide system + * security by dropping startup privileges. + */ +static int sys_privileges = 0; +AP_DECLARE(int) ap_sys_privileges_handlers(int inc) +{ + sys_privileges += inc; + return sys_privileges; +} + +static int core_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ + ap_mutex_init(pconf); + + if (!saved_server_config_defines) + init_config_defines(pconf); + apr_pool_cleanup_register(pconf, NULL, reset_config_defines, + apr_pool_cleanup_null); + + mpm_common_pre_config(pconf); + + return OK; +} 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); + ap__logio_add_bytes_out = APR_RETRIEVE_OPTIONAL_FN(ap_logio_add_bytes_out); ident_lookup = APR_RETRIEVE_OPTIONAL_FN(ap_ident_lookup); + ap__authz_ap_some_auth_required = APR_RETRIEVE_OPTIONAL_FN(authz_some_auth_required); + authn_ap_auth_type = APR_RETRIEVE_OPTIONAL_FN(authn_ap_auth_type); + authn_ap_auth_name = APR_RETRIEVE_OPTIONAL_FN(authn_ap_auth_name); + access_compat_ap_satisfies = APR_RETRIEVE_OPTIONAL_FN(access_compat_ap_satisfies); set_banner(pconf); ap_setup_make_content_type(pconf); + ap_setup_auth_internal(ptemp); + if (!sys_privileges) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(00136) + "Server MUST relinquish startup privileges before " + "accepting connections. Please ensure mod_unixd " + "or other system security module is loaded."); + return !OK; + } + apr_pool_cleanup_register(pconf, NULL, ap_mpm_end_gen_helper, + apr_pool_cleanup_null); 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); + ap_get_core_module_config(r->per_dir_config); const char *filter, *filters = conf->output_filters; if (filters) { @@ -3900,7 +4424,7 @@ AP_DECLARE(void **) ap_get_request_note(request_rec *r, apr_size_t note_num) } req_cfg = (core_request_config *) - ap_get_module_config(r->request_config, &core_module); + ap_get_core_module_config(r->request_config); if (!req_cfg) { return NULL; @@ -3909,6 +4433,11 @@ AP_DECLARE(void **) ap_get_request_note(request_rec *r, apr_size_t note_num) return &(req_cfg->notes[note_num]); } +AP_DECLARE(apr_socket_t *) ap_get_conn_socket(conn_rec *c) +{ + return ap_get_core_module_config(c->conn_config); +} + static int core_create_req(request_rec *r) { /* Alloc the config struct and the array of request notes in @@ -3925,14 +4454,14 @@ static int core_create_req(request_rec *r) if (r->main) { core_request_config *main_req_cfg = (core_request_config *) - ap_get_module_config(r->main->request_config, &core_module); + ap_get_core_module_config(r->main->request_config); 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); + ap_set_core_module_config(r->request_config, req_cfg); return OK; } @@ -3961,26 +4490,27 @@ static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, 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, + ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00137) "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)) + if ((rv = apr_socket_addr_get(&c->client_addr, APR_REMOTE, csd)) != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, + ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00138) "apr_socket_addr_get(APR_REMOTE)"); apr_socket_close(csd); return NULL; } - apr_sockaddr_ip_get(&c->remote_ip, c->remote_addr); + apr_sockaddr_ip_get(&c->client_ip, c->client_addr); c->base_server = server; c->id = id; c->bucket_alloc = alloc; + c->clogging_input_filters = 0; return c; @@ -3991,7 +4521,6 @@ 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 @@ -4004,10 +4533,9 @@ static int core_pre_connection(conn_rec *c, void *csd) /* expected cause is that the client disconnected already, * hence the debug level */ - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00139) "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 @@ -4018,7 +4546,7 @@ static int core_pre_connection(conn_rec *c, void *csd) 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, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00140) "apr_socket_timeout_set"); } @@ -4027,38 +4555,201 @@ static int core_pre_connection(conn_rec *c, void *csd) net->out_ctx = NULL; net->client_socket = csd; - ap_set_module_config(net->c->conn_config, &core_module, csd); + ap_set_core_module_config(net->c->conn_config, 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; } +AP_DECLARE(int) ap_state_query(int query) +{ + switch (query) { + case AP_SQ_MAIN_STATE: + return ap_main_state; + case AP_SQ_RUN_MODE: + return ap_run_mode; + case AP_SQ_CONFIG_GEN: + return ap_config_generation; + default: + return AP_SQ_NOT_SUPPORTED; + } +} + +static apr_random_t *rng = NULL; +#if APR_HAS_THREADS +static apr_thread_mutex_t *rng_mutex = NULL; +#endif + +static void core_child_init(apr_pool_t *pchild, server_rec *s) +{ + apr_proc_t proc; +#if APR_HAS_THREADS + int threaded_mpm; + if (ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm) == APR_SUCCESS + && threaded_mpm) + { + apr_thread_mutex_create(&rng_mutex, APR_THREAD_MUTEX_DEFAULT, pchild); + } +#endif + /* The MPMs use plain fork() and not apr_proc_fork(), so we have to call + * apr_random_after_fork() manually in the child + */ + proc.pid = getpid(); + apr_random_after_fork(&proc); +} + +AP_CORE_DECLARE(void) ap_random_parent_after_fork(void) +{ + /* + * To ensure that the RNG state in the parent changes after the fork, we + * pull some data from the RNG and discard it. This ensures that the RNG + * states in the children are different even after the pid wraps around. + * As we only use apr_random for insecure random bytes, pulling 2 bytes + * should be enough. + * XXX: APR should probably have some dedicated API to do this, but it + * XXX: currently doesn't. + */ + apr_uint16_t data; + apr_random_insecure_bytes(rng, &data, sizeof(data)); +} + +AP_CORE_DECLARE(void) ap_init_rng(apr_pool_t *p) +{ + unsigned char seed[8]; + apr_status_t rv; + rng = apr_random_standard_new(p); + do { + rv = apr_generate_random_bytes(seed, sizeof(seed)); + if (rv != APR_SUCCESS) + goto error; + apr_random_add_entropy(rng, seed, sizeof(seed)); + rv = apr_random_insecure_ready(rng); + } while (rv == APR_ENOTENOUGHENTROPY); + if (rv == APR_SUCCESS) + return; +error: + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00141) + "Could not initialize random number generator"); + exit(1); +} + +AP_DECLARE(void) ap_random_insecure_bytes(void *buf, apr_size_t size) +{ +#if APR_HAS_THREADS + if (rng_mutex) + apr_thread_mutex_lock(rng_mutex); +#endif + /* apr_random_insecure_bytes can only fail with APR_ENOTENOUGHENTROPY, + * and we have ruled that out during initialization. Therefore we don't + * need to check the return code. + */ + apr_random_insecure_bytes(rng, buf, size); +#if APR_HAS_THREADS + if (rng_mutex) + apr_thread_mutex_unlock(rng_mutex); +#endif +} + +/* + * Finding a random number in a range. + * n' = a + n(b-a+1)/(M+1) + * where: + * n' = random number in range + * a = low end of range + * b = high end of range + * n = random number of 0..M + * M = maxint + * Algorithm 'borrowed' from PHP's rand() function. + */ +#define RAND_RANGE(__n, __min, __max, __tmax) \ +(__n) = (__min) + (long) ((double) ((__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0))) +AP_DECLARE(apr_uint32_t) ap_random_pick(apr_uint32_t min, apr_uint32_t max) +{ + apr_uint32_t number; + if (max < 16384) { + apr_uint16_t num16; + ap_random_insecure_bytes(&num16, sizeof(num16)); + RAND_RANGE(num16, min, max, APR_UINT16_MAX); + number = num16; + } + else { + ap_random_insecure_bytes(&number, sizeof(number)); + RAND_RANGE(number, min, max, APR_UINT32_MAX); + } + return number; +} + +static void core_dump_config(apr_pool_t *p, server_rec *s) +{ + core_server_config *sconf = ap_get_core_module_config(s->module_config); + apr_file_t *out = NULL; + char *tmp; + const char **defines; + int i; + if (!ap_exists_config_define("DUMP_RUN_CFG")) + return; + + apr_file_open_stdout(&out, p); + apr_file_printf(out, "ServerRoot: \"%s\"\n", ap_server_root); + tmp = ap_server_root_relative(p, sconf->ap_document_root); + apr_file_printf(out, "Main DocumentRoot: \"%s\"\n", tmp); + tmp = ap_server_root_relative(p, s->error_fname); + apr_file_printf(out, "Main ErrorLog: \"%s\"\n", tmp); + if (ap_scoreboard_fname) { + tmp = ap_server_root_relative(p, ap_scoreboard_fname); + apr_file_printf(out, "ScoreBoardFile: \"%s\"\n", tmp); + } + ap_dump_mutexes(p, s, out); + ap_mpm_dump_pidfile(p, out); + + defines = (const char **)ap_server_config_defines->elts; + for (i = 0; i < ap_server_config_defines->nelts; i++) { + const char *name = defines[i]; + const char *val = NULL; + if (server_config_defined_vars) + val = apr_table_get(server_config_defined_vars, name); + if (val) + apr_file_printf(out, "Define: %s=%s\n", name, val); + else + apr_file_printf(out, "Define: %s\n", name); + } + +} + 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. + errorlog_hash = apr_hash_make(p); + ap_register_log_hooks(p); + ap_register_config_hooks(p); + ap_expr_init(p); + + /* create_connection and pre_connection should always be hooked + * APR_HOOK_REALLY_LAST by core 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_pre_config(core_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_hook_post_config(core_post_config,NULL,NULL,APR_HOOK_REALLY_FIRST); + ap_hook_test_config(core_dump_config,NULL,NULL,APR_HOOK_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(core_child_init,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); + ap_hook_child_status(ap_core_child_status, NULL, NULL, APR_HOOK_MIDDLE); /* register the core's insert_filter hook and register core-provided * filters @@ -4082,8 +4773,9 @@ static void register_hooks(apr_pool_t *p) NULL, AP_FTYPE_RESOURCE - 10); } -AP_DECLARE_DATA module core_module = { - STANDARD20_MODULE_STUFF, +AP_DECLARE_MODULE(core) = { + MPM20_MODULE_STUFF, + AP_PLATFORM_REWRITE_ARGS_HOOK, /* hook to run before apache parses args */ 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 */ diff --git a/server/core_filters.c b/server/core_filters.c index 7feb157e..686513fe 100644 --- a/server/core_filters.c +++ b/server/core_filters.c @@ -25,14 +25,12 @@ #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" @@ -47,7 +45,6 @@ #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" @@ -77,26 +74,9 @@ do { \ } 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); -} +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX int ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, @@ -214,10 +194,9 @@ int ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, * 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. + * extreme caution. FWLIW, this would be needed by an MPM like Perchild; + * such an 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; @@ -268,6 +247,37 @@ int ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, return APR_SUCCESS; } + /* Have we read as much data as we wanted (be greedy)? */ + if (len < readbytes) { + apr_size_t bucket_len; + + rv = APR_SUCCESS; + /* We already registered the data in e in len */ + e = APR_BUCKET_NEXT(e); + while ((len < readbytes) && (rv == APR_SUCCESS) + && (e != APR_BRIGADE_SENTINEL(ctx->b))) { + /* Check for the availability of buckets with known length */ + if (e->length != -1) { + len += e->length; + e = APR_BUCKET_NEXT(e); + } + else { + /* + * Read from bucket, but non blocking. If there isn't any + * more data, well than this is fine as well, we will + * not wait for more since we already got some and we are + * only checking if there isn't more. + */ + rv = apr_bucket_read(e, &str, &bucket_len, + APR_NONBLOCK_READ); + if (rv == APR_SUCCESS) { + len += bucket_len; + e = APR_BUCKET_NEXT(e); + } + } + } + } + /* We can only return at most what we read. */ if (len < readbytes) { readbytes = len; @@ -279,7 +289,7 @@ int ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, } /* Must do move before CONCAT */ - brigade_move(ctx->b, ctx->tmpbb, e); + ctx->tmpbb = apr_brigade_split_ex(ctx->b, e, ctx->tmpbb); if (mode == AP_MODE_READBYTES) { APR_BRIGADE_CONCAT(b, ctx->b); @@ -305,216 +315,264 @@ int ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, 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; +static void setaside_remaining_output(ap_filter_t *f, + core_output_filter_ctx_t *ctx, + apr_bucket_brigade *bb, + conn_rec *c); - *nbytes = 0; +static apr_status_t send_brigade_nonblocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c); - /* 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; +static void remove_empty_buckets(apr_bucket_brigade *bb); - /* 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; - } +static apr_status_t send_brigade_blocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c); - 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; - } - } +static apr_status_t writev_nonblocking(apr_socket_t *s, + struct iovec *vec, apr_size_t nvec, + apr_bucket_brigade *bb, + apr_size_t *cumulative_bytes_written, + conn_rec *c); - n = len - bytes_written; - } +#if APR_HAS_SENDFILE +static apr_status_t sendfile_nonblocking(apr_socket_t *s, + apr_bucket *bucket, + apr_size_t *cumulative_bytes_written, + conn_rec *c); +#endif - return APR_SUCCESS; -} +/* XXX: Should these be configurable parameters? */ +#define THRESHOLD_MIN_WRITE 4096 +#define THRESHOLD_MAX_BUFFER 65536 +#define MAX_REQUESTS_IN_PIPELINE 5 -/* 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. +/* Optional function coming from mod_logio, used for logging of output + * traffic */ +extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *ap__logio_add_bytes_out; -#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 ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *new_bb) { + conn_rec *c = f->c; + core_net_rec *net = f->ctx; + core_output_filter_ctx_t *ctx = net->out_ctx; + apr_bucket_brigade *bb = NULL; + apr_bucket *bucket, *next, *flush_upto = NULL; + apr_size_t bytes_in_brigade, non_file_bytes_in_brigade; + int eor_buckets_in_brigade; 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 */ + /* Fail quickly if the connection has already been aborted. */ + if (c->aborted) { + if (new_bb != NULL) { + apr_brigade_cleanup(new_bb); } + return APR_ECONNABORTED; + } - AP_DEBUG_ASSERT(total_bytes_left > 0 && tmplen > 0); - - /* partial write, oooh noooo... - * Skip over any header data which was written + if (ctx == NULL) { + ctx = apr_pcalloc(c->pool, sizeof(*ctx)); + net->out_ctx = (core_output_filter_ctx_t *)ctx; + rv = apr_socket_opt_set(net->client_socket, APR_SO_NONBLOCK, 1); + if (rv != APR_SUCCESS) { + return rv; + } + /* + * Need to create tmp brigade with correct lifetime. Passing + * NULL to apr_brigade_split_ex would result in a brigade + * allocated from bb->pool which might be wrong. */ - 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; + ctx->tmp_flush_bb = apr_brigade_create(c->pool, c->bucket_alloc); + /* same for buffered_bb and ap_save_brigade */ + ctx->buffered_bb = apr_brigade_create(c->pool, c->bucket_alloc); + } - hdtr->headers[0].iov_len -= tmplen; - iov_base += tmplen; - hdtr->headers[0].iov_base = iov_base; - tmplen = 0; + if (new_bb != NULL) { + for (bucket = APR_BRIGADE_FIRST(new_bb); bucket != APR_BRIGADE_SENTINEL(new_bb); bucket = APR_BUCKET_NEXT(bucket)) { + if (bucket->length > 0) { + ctx->bytes_in += bucket->length; } } + bb = new_bb; + } - /* Skip over any file data which was written */ - - if (tmplen <= file_bytes_left) { - file_offset += tmplen; - file_bytes_left -= tmplen; - continue; + if ((ctx->buffered_bb != NULL) && + !APR_BRIGADE_EMPTY(ctx->buffered_bb)) { + if (new_bb != NULL) { + APR_BRIGADE_PREPEND(bb, ctx->buffered_bb); } + else { + bb = ctx->buffered_bb; + } + c->data_in_output_filters = 0; + } + else if (new_bb == NULL) { + return APR_SUCCESS; + } - tmplen -= file_bytes_left; - file_bytes_left = 0; - file_offset = 0; + /* Scan through the brigade and decide whether to attempt a write, + * based on the following rules: + * + * 1) The new_bb is null: Do a nonblocking write of as much as + * possible: do a nonblocking write of as much data as possible, + * then save the rest in ctx->buffered_bb. (If new_bb == NULL, + * it probably means that the MPM is doing asynchronous write + * completion and has just determined that this connection + * is writable.) + * + * 2) The brigade contains a flush bucket: Do a blocking write + * of everything up that point. + * + * 3) The request is in CONN_STATE_HANDLER state, and the brigade + * contains at least THRESHOLD_MAX_BUFFER bytes in non-file + * buckets: Do blocking writes until the amount of data in the + * buffer is less than THRESHOLD_MAX_BUFFER. (The point of this + * rule is to provide flow control, in case a handler is + * streaming out lots of data faster than the data can be + * sent to the client.) + * + * 4) The request is in CONN_STATE_HANDLER state, and the brigade + * contains at least MAX_REQUESTS_IN_PIPELINE EOR buckets: + * Do blocking writes until less than MAX_REQUESTS_IN_PIPELINE EOR + * buckets are left. (The point of this rule is to prevent too many + * FDs being kept open by pipelined requests, possibly allowing a + * DoS). + * + * 5) The brigade contains at least THRESHOLD_MIN_WRITE + * bytes: Do a nonblocking write of as much data as possible, + * then save the rest in ctx->buffered_bb. + */ - /* Skip over any trailer data which was written */ + if (new_bb == NULL) { + rv = send_brigade_nonblocking(net->client_socket, bb, + &(ctx->bytes_written), c); + if (APR_STATUS_IS_EAGAIN(rv)) { + rv = APR_SUCCESS; + } + else if (rv != APR_SUCCESS) { + /* The client has aborted the connection */ + c->aborted = 1; + } + setaside_remaining_output(f, ctx, bb, c); + return rv; + } - while (tmplen && hdtr->numtrailers) { - if (tmplen >= hdtr->trailers[0].iov_len) { - tmplen -= hdtr->trailers[0].iov_len; - --hdtr->numtrailers; - ++hdtr->trailers; + bytes_in_brigade = 0; + non_file_bytes_in_brigade = 0; + eor_buckets_in_brigade = 0; + for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); + bucket = next) { + next = APR_BUCKET_NEXT(bucket); + + if (!APR_BUCKET_IS_METADATA(bucket)) { + if (bucket->length == (apr_size_t)-1) { + const char *data; + apr_size_t length; + /* XXX support nonblocking read here? */ + rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + return rv; + } + /* reading may have split the bucket, so recompute next: */ + next = APR_BUCKET_NEXT(bucket); } - else { - char *iov_base = (char *)hdtr->trailers[0].iov_base; + bytes_in_brigade += bucket->length; + if (!APR_BUCKET_IS_FILE(bucket)) { + non_file_bytes_in_brigade += bucket->length; + } + } + else if (AP_BUCKET_IS_EOR(bucket)) { + eor_buckets_in_brigade++; + } - hdtr->trailers[0].iov_len -= tmplen; - iov_base += tmplen; - hdtr->trailers[0].iov_base = iov_base; - tmplen = 0; + if (APR_BUCKET_IS_FLUSH(bucket) || + (non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER) || + (eor_buckets_in_brigade > MAX_REQUESTS_IN_PIPELINE) ) + { + if (APLOGctrace6(c)) { + char *reason = APR_BUCKET_IS_FLUSH(bucket) ? + "FLUSH bucket" : + (non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER) ? + "THRESHOLD_MAX_BUFFER" : + "MAX_REQUESTS_IN_PIPELINE"; + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "core_output_filter: flushing because of %s", + reason); } + /* + * Defer the actual blocking write to avoid doing many writes. + */ + flush_upto = next; + + bytes_in_brigade = 0; + non_file_bytes_in_brigade = 0; + eor_buckets_in_brigade = 0; + } + } + + if (flush_upto != NULL) { + ctx->tmp_flush_bb = apr_brigade_split_ex(bb, flush_upto, + ctx->tmp_flush_bb); + rv = send_brigade_blocking(net->client_socket, bb, + &(ctx->bytes_written), c); + if (rv != APR_SUCCESS) { + /* The client has aborted the connection */ + c->aborted = 1; + return rv; + } + APR_BRIGADE_CONCAT(bb, ctx->tmp_flush_bb); + } + + if (bytes_in_brigade >= THRESHOLD_MIN_WRITE) { + rv = send_brigade_nonblocking(net->client_socket, bb, + &(ctx->bytes_written), c); + if ((rv != APR_SUCCESS) && (!APR_STATUS_IS_EAGAIN(rv))) { + /* The client has aborted the connection */ + c->aborted = 1; + return rv; } - } while (1); + } + + setaside_remaining_output(f, ctx, bb, c); + return APR_SUCCESS; } -#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. + * This function assumes that either ctx->buffered_bb == NULL, or + * ctx->buffered_bb is empty, or ctx->buffered_bb == bb */ -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) +static void setaside_remaining_output(ap_filter_t *f, + core_output_filter_ctx_t *ctx, + apr_bucket_brigade *bb, + conn_rec *c) { - 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 */ + if (bb == NULL) { + return; + } + remove_empty_buckets(bb); + if (!APR_BRIGADE_EMPTY(bb)) { + c->data_in_output_filters = 1; + if (bb != ctx->buffered_bb) { + 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->buffered_bb), &bb, + ctx->deferred_write_pool); + apr_brigade_cleanup(bb); } } - - /* 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; + else if (ctx->deferred_write_pool) { + /* + * There are no more requests in the pipeline. We can just clear the + * pool. + */ + apr_pool_clear(ctx->deferred_write_pool); } - - return rv; } #ifndef APR_MAX_IOVEC_SIZE @@ -527,398 +585,266 @@ static apr_status_t emulate_sendfile(core_net_rec *c, apr_file_t *fd, #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) +static apr_status_t send_brigade_nonblocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c) { + apr_bucket *bucket, *next; 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; + struct iovec vec[MAX_IOVEC_TO_WRITE]; + apr_size_t nvec = 0; - /* Fail quickly if the connection has already been aborted. */ - if (c->aborted) { - apr_brigade_cleanup(b); - return APR_ECONNABORTED; - } + remove_empty_buckets(bb); - 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. + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + bucket = next) { + next = APR_BUCKET_NEXT(bucket); +#if APR_HAS_SENDFILE + if (APR_BUCKET_IS_FILE(bucket)) { + apr_bucket_file *file_bucket = (apr_bucket_file *)(bucket->data); + apr_file_t *fd = file_bucket->fd; + /* Use sendfile to send this file unless: + * - the platform doesn't support sendfile, + * - the file is too small for sendfile to be useful, or + * - sendfile is disabled in the httpd config via "EnableSendfile off" */ - 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 - */ - temp_brig = apr_brigade_create(f->c->pool, - f->c->bucket_alloc); - 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_BRIGADE_PREPEND(b, temp_brig); - brigade_move(temp_brig, b, APR_BUCKET_NEXT(last_merged_bucket)); - } - - 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++; - } + if ((apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) && + (bucket->length >= AP_MIN_SENDFILE_BYTES)) { + if (nvec > 0) { + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 1); + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + nvec = 0; + if (rv != APR_SUCCESS) { + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); + return rv; } - 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; } + rv = sendfile_nonblocking(s, bucket, bytes_written, c); + if (nvec > 0) { + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); + } + if (rv != APR_SUCCESS) { + return rv; + } + break; } } - - - /* 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; - } - } - } +#endif /* APR_HAS_SENDFILE */ + /* didn't sendfile */ + if (!APR_BUCKET_IS_METADATA(bucket)) { + const char *data; + apr_size_t length; + rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + return rv; } - if (!ctx->deferred_write_pool) { - apr_pool_create(&ctx->deferred_write_pool, c->pool); - apr_pool_tag(ctx->deferred_write_pool, "deferred_write"); + /* reading may have split the bucket, so recompute next: */ + next = APR_BUCKET_NEXT(bucket); + vec[nvec].iov_base = (char *)data; + vec[nvec].iov_len = length; + nvec++; + if (nvec == MAX_IOVEC_TO_WRITE) { + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + nvec = 0; + if (rv != APR_SUCCESS) { + return rv; + } + break; } - 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 (nvec > 0) { + rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); + if (rv != APR_SUCCESS) { + return rv; + } + } -#if APR_HAS_SENDFILE - apr_int32_t flags = 0; -#endif + remove_empty_buckets(bb); - memset(&hdtr, '\0', sizeof(hdtr)); - if (nvec) { - hdtr.numheaders = nvec; - hdtr.headers = vec; - } + return APR_SUCCESS; +} - if (nvec_trailers) { - hdtr.numtrailers = nvec_trailers; - hdtr.trailers = vec_trailers; - } +static void remove_empty_buckets(apr_bucket_brigade *bb) +{ + apr_bucket *bucket; + while (((bucket = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) && + (APR_BUCKET_IS_METADATA(bucket) || (bucket->length == 0))) { + APR_BUCKET_REMOVE(bucket); + apr_bucket_destroy(bucket); + } +} -#if APR_HAS_SENDFILE - if (apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) { +static apr_status_t send_brigade_blocking(apr_socket_t *s, + apr_bucket_brigade *bb, + apr_size_t *bytes_written, + conn_rec *c) +{ + apr_status_t rv; - if (c->keepalive == AP_CONN_CLOSE && APR_BUCKET_IS_EOS(last_e)) { - /* Prepare the socket to be reused */ - flags |= APR_SENDFILE_DISCONNECT_SOCKET; + rv = APR_SUCCESS; + while (!APR_BRIGADE_EMPTY(bb)) { + rv = send_brigade_nonblocking(s, bb, bytes_written, c); + if (rv != APR_SUCCESS) { + if (APR_STATUS_IS_EAGAIN(rv)) { + /* Wait until we can send more data */ + apr_int32_t nsds; + apr_interval_time_t timeout; + apr_pollfd_t pollset; + + pollset.p = c->pool; + pollset.desc_type = APR_POLL_SOCKET; + pollset.reqevents = APR_POLLOUT; + pollset.desc.s = s; + apr_socket_timeout_get(s, &timeout); + rv = apr_poll(&pollset, 1, &nsds, timeout); + if (rv != APR_SUCCESS) { + break; } - - 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); + else { + break; } - - 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); } + } + return rv; +} - apr_brigade_cleanup(b); +static apr_status_t writev_nonblocking(apr_socket_t *s, + struct iovec *vec, apr_size_t nvec, + apr_bucket_brigade *bb, + apr_size_t *cumulative_bytes_written, + conn_rec *c) +{ + apr_status_t rv = APR_SUCCESS, arv; + apr_size_t bytes_written = 0, bytes_to_write = 0; + apr_size_t i, offset; + apr_interval_time_t old_timeout; + + arv = apr_socket_timeout_get(s, &old_timeout); + if (arv != APR_SUCCESS) { + return arv; + } + arv = apr_socket_timeout_set(s, 0); + if (arv != APR_SUCCESS) { + return arv; + } - /* 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; + for (i = 0; i < nvec; i++) { + bytes_to_write += vec[i].iov_len; + } + offset = 0; + while (bytes_written < bytes_to_write) { + apr_size_t n = 0; + rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); + if (n > 0) { + bytes_written += n; + for (i = offset; i < nvec; ) { + apr_bucket *bucket = APR_BRIGADE_FIRST(bb); + if (APR_BUCKET_IS_METADATA(bucket)) { + APR_BUCKET_REMOVE(bucket); + apr_bucket_destroy(bucket); + } + else if (n >= vec[i].iov_len) { + APR_BUCKET_REMOVE(bucket); + apr_bucket_destroy(bucket); + offset++; + n -= vec[i++].iov_len; } 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_bucket_split(bucket, n); + APR_BUCKET_REMOVE(bucket); + apr_bucket_destroy(bucket); + vec[i].iov_len -= n; + vec[i].iov_base = (char *) vec[i].iov_base + n; + break; } } - 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_cleanup(more); + break; + } + } + if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { + ap__logio_add_bytes_out(c, bytes_written); + } + *cumulative_bytes_written += bytes_written; - /* No need to check for SUCCESS, we did that above. */ - if (!APR_STATUS_IS_EAGAIN(rv)) { - c->aborted = 1; - return APR_ECONNABORTED; - } + arv = apr_socket_timeout_set(s, old_timeout); + if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { + return arv; + } + else { + return rv; + } +} - return APR_SUCCESS; - } +#if APR_HAS_SENDFILE - b = more; - more = NULL; - } /* end while () */ +static apr_status_t sendfile_nonblocking(apr_socket_t *s, + apr_bucket *bucket, + apr_size_t *cumulative_bytes_written, + conn_rec *c) +{ + apr_status_t rv = APR_SUCCESS; + apr_bucket_file *file_bucket; + apr_file_t *fd; + apr_size_t file_length; + apr_off_t file_offset; + apr_size_t bytes_written = 0; - return APR_SUCCESS; + if (!APR_BUCKET_IS_FILE(bucket)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006) + "core_filter: sendfile_nonblocking: " + "this should never happen"); + return APR_EGENERAL; + } + file_bucket = (apr_bucket_file *)(bucket->data); + fd = file_bucket->fd; + file_length = bucket->length; + file_offset = bucket->start; + + if (bytes_written < file_length) { + apr_size_t n = file_length - bytes_written; + apr_status_t arv; + apr_interval_time_t old_timeout; + + arv = apr_socket_timeout_get(s, &old_timeout); + if (arv != APR_SUCCESS) { + return arv; + } + arv = apr_socket_timeout_set(s, 0); + if (arv != APR_SUCCESS) { + return arv; + } + rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0); + if (rv == APR_SUCCESS) { + bytes_written += n; + file_offset += n; + } + arv = apr_socket_timeout_set(s, old_timeout); + if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { + rv = arv; + } + } + if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { + ap__logio_add_bytes_out(c, bytes_written); + } + *cumulative_bytes_written += bytes_written; + if ((bytes_written < file_length) && (bytes_written > 0)) { + apr_bucket_split(bucket, bytes_written); + APR_BUCKET_REMOVE(bucket); + apr_bucket_destroy(bucket); + } + else if (bytes_written == file_length) { + APR_BUCKET_REMOVE(bucket); + apr_bucket_destroy(bucket); + } + return rv; } + +#endif diff --git a/server/eor_bucket.c b/server/eor_bucket.c new file mode 100644 index 00000000..4d3e1ecf --- /dev/null +++ b/server/eor_bucket.c @@ -0,0 +1,102 @@ +/* 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_request.h" +#include "http_protocol.h" +#include "scoreboard.h" + +static apr_status_t eor_bucket_cleanup(void *data) +{ + apr_bucket *b = (apr_bucket *)data; + request_rec *r = (request_rec *)b->data; + + if (r != NULL) { + /* + * If eor_bucket_destroy is called after us, this prevents + * eor_bucket_destroy from trying to destroy the pool again. + */ + b->data = NULL; + /* Update child status and log the transaction */ + ap_update_child_status(r->connection->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + if (ap_extended_status) { + ap_increment_counts(r->connection->sbh, r); + } + } + return APR_SUCCESS; +} + +static apr_status_t eor_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_eor_make(apr_bucket *b, request_rec *r) +{ + b->length = 0; + b->start = 0; + b->data = r; + b->type = &ap_bucket_type_eor; + + return b; +} + +AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_bucket_alloc_t *list, + request_rec *r) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + if (r) { + /* + * Register a cleanup for the request pool as the eor bucket could + * have been allocated from a different pool then the request pool + * e.g. the parent pool of the request pool. In this case + * eor_bucket_destroy might be called at a point of time when the + * request pool had been already destroyed. + * We need to use a pre-cleanup here because a module may create a + * sub-pool which is still needed during the log_transaction hook. + */ + apr_pool_pre_cleanup_register(r->pool, (void *)b, eor_bucket_cleanup); + } + return ap_bucket_eor_make(b, r); +} + +static void eor_bucket_destroy(void *data) +{ + request_rec *r = (request_rec *)data; + + if (r) { + /* eor_bucket_cleanup will be called when the pool gets destroyed */ + apr_pool_destroy(r->pool); + } +} + +AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_eor = { + "EOR", 5, APR_BUCKET_METADATA, + eor_bucket_destroy, + eor_bucket_read, + apr_bucket_setaside_noop, + apr_bucket_split_notimpl, + apr_bucket_simple_copy +}; + diff --git a/server/gen_test_char.c b/server/gen_test_char.c index a0b55100..1c40bde9 100644 --- a/server/gen_test_char.c +++ b/server/gen_test_char.c @@ -51,6 +51,7 @@ #define T_HTTP_TOKEN_STOP (0x08) #define T_ESCAPE_LOGITEM (0x10) #define T_ESCAPE_FORENSIC (0x20) +#define T_ESCAPE_URLENCODED (0x40) int main(int argc, char *argv[]) { @@ -65,6 +66,7 @@ int main(int argc, char *argv[]) "#define T_HTTP_TOKEN_STOP (%u)\n" "#define T_ESCAPE_LOGITEM (%u)\n" "#define T_ESCAPE_FORENSIC (%u)\n" + "#define T_ESCAPE_URLENCODED (%u)\n" "\n" "static const unsigned char test_char_table[256] = {", T_ESCAPE_SHELL_CMD, @@ -72,7 +74,8 @@ int main(int argc, char *argv[]) T_OS_ESCAPE_PATH, T_HTTP_TOKEN_STOP, T_ESCAPE_LOGITEM, - T_ESCAPE_FORENSIC); + T_ESCAPE_FORENSIC, + T_ESCAPE_URLENCODED); for (c = 0; c < 256; ++c) { flags = 0; @@ -108,6 +111,10 @@ int main(int argc, char *argv[]) flags |= T_OS_ESCAPE_PATH; } + if (!apr_isalnum(c) && !strchr(".-*_ ", c)) { + flags |= T_ESCAPE_URLENCODED; + } + /* these are the "tspecials" (RFC2068) or "separators" (RFC2616) */ if (c && (apr_iscntrl(c) || strchr(" \t()<>@,;:\\\"/[]?={}", c))) { flags |= T_HTTP_TOKEN_STOP; diff --git a/server/gen_test_char.dep b/server/gen_test_char.dep deleted file mode 100644 index c749241e..00000000 --- a/server/gen_test_char.dep +++ /dev/null @@ -1,5 +0,0 @@ -# Microsoft Developer Studio Generated Dependency File, included by gen_test_char.mak - -.\gen_test_char.c : \ - "..\srclib\apr\include\apr_lib.h"\ - diff --git a/server/gen_test_char.mak b/server/gen_test_char.mak deleted file mode 100644 index d6e7bfb1..00000000 --- a/server/gen_test_char.mak +++ /dev/null @@ -1,234 +0,0 @@ -# Microsoft Developer Studio Generated NMAKE File, Based on gen_test_char.dsp -!IF "$(CFG)" == "" -CFG=gen_test_char - Win32 Debug -!MESSAGE No configuration specified. Defaulting to gen_test_char - Win32 Debug. -!ENDIF - -!IF "$(CFG)" != "gen_test_char - Win32 Release" && "$(CFG)" != "gen_test_char - Win32 Debug" -!MESSAGE Invalid configuration "$(CFG)" specified. -!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 -!ERROR An invalid configuration is specified. -!ENDIF - -!IF "$(OS)" == "Windows_NT" -NULL= -!ELSE -NULL=nul -!ENDIF - -!IF "$(CFG)" == "gen_test_char - Win32 Release" - -OUTDIR=. -INTDIR=.\Release -# Begin Custom Macros -OutDir=. -# End Custom Macros - -!IF "$(RECURSE)" == "0" - -ALL : "$(OUTDIR)\gen_test_char.exe" - -!ELSE - -ALL : "libapr - Win32 Release" "$(OUTDIR)\gen_test_char.exe" - -!ENDIF - -!IF "$(RECURSE)" == "1" -CLEAN :"libapr - Win32 ReleaseCLEAN" -!ELSE -CLEAN : -!ENDIF - -@erase "$(INTDIR)\gen_test_char.idb" - -@erase "$(INTDIR)\gen_test_char.obj" - -@erase "$(OUTDIR)\gen_test_char.exe" - -"$(INTDIR)" : - if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" - -CPP=cl.exe -CPP_PROJ=/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" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\gen_test_char" /FD /c - -.c{$(INTDIR)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cpp{$(INTDIR)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cxx{$(INTDIR)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.c{$(INTDIR)}.sbr:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cpp{$(INTDIR)}.sbr:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cxx{$(INTDIR)}.sbr:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -RSC=rc.exe -BSC32=bscmake.exe -BSC32_FLAGS=/nologo /o"$(OUTDIR)\gen_test_char.bsc" -BSC32_SBRS= \ - -LINK32=link.exe -LINK32_FLAGS=kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"$(OUTDIR)\Release\gen_test_char.pdb" /out:"$(OUTDIR)\gen_test_char.exe" /opt:ref -LINK32_OBJS= \ - "$(INTDIR)\gen_test_char.obj" \ - "..\srclib\apr\Release\libapr-1.lib" - -"$(OUTDIR)\gen_test_char.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) - $(LINK32) @<< - $(LINK32_FLAGS) $(LINK32_OBJS) -<< - -!ELSEIF "$(CFG)" == "gen_test_char - Win32 Debug" - -OUTDIR=. -INTDIR=.\Debug -# Begin Custom Macros -OutDir=. -# End Custom Macros - -!IF "$(RECURSE)" == "0" - -ALL : "$(OUTDIR)\gen_test_char.exe" - -!ELSE - -ALL : "libapr - Win32 Debug" "$(OUTDIR)\gen_test_char.exe" - -!ENDIF - -!IF "$(RECURSE)" == "1" -CLEAN :"libapr - Win32 DebugCLEAN" -!ELSE -CLEAN : -!ENDIF - -@erase "$(INTDIR)\gen_test_char.idb" - -@erase "$(INTDIR)\gen_test_char.obj" - -@erase "$(OUTDIR)\Debug\gen_test_char.pdb" - -@erase "$(OUTDIR)\gen_test_char.exe" - -"$(INTDIR)" : - if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)" - -CPP=cl.exe -CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "..\include" /I "..\srclib\apr\include" /I "..\srclib\apr-util\include" /I "..\os\win32" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\gen_test_char" /FD /EHsc /c - -.c{$(INTDIR)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cpp{$(INTDIR)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cxx{$(INTDIR)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.c{$(INTDIR)}.sbr:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cpp{$(INTDIR)}.sbr:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cxx{$(INTDIR)}.sbr:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -RSC=rc.exe -BSC32=bscmake.exe -BSC32_FLAGS=/nologo /o"$(OUTDIR)\gen_test_char.bsc" -BSC32_SBRS= \ - -LINK32=link.exe -LINK32_FLAGS=kernel32.lib /nologo /subsystem:console /incremental:no /pdb:"$(OUTDIR)\Debug\gen_test_char.pdb" /debug /out:"$(OUTDIR)\gen_test_char.exe" -LINK32_OBJS= \ - "$(INTDIR)\gen_test_char.obj" \ - "..\srclib\apr\Debug\libapr-1.lib" - -"$(OUTDIR)\gen_test_char.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) - $(LINK32) @<< - $(LINK32_FLAGS) $(LINK32_OBJS) -<< - -!ENDIF - - -!IF "$(NO_EXTERNAL_DEPS)" != "1" -!IF EXISTS("gen_test_char.dep") -!INCLUDE "gen_test_char.dep" -!ELSE -!MESSAGE Warning: cannot find "gen_test_char.dep" -!ENDIF -!ENDIF - - -!IF "$(CFG)" == "gen_test_char - Win32 Release" || "$(CFG)" == "gen_test_char - Win32 Debug" - -!IF "$(CFG)" == "gen_test_char - Win32 Release" - -"libapr - Win32 Release" : - cd ".\..\srclib\apr" - $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" - cd "..\..\server" - -"libapr - Win32 ReleaseCLEAN" : - cd ".\..\srclib\apr" - $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN - cd "..\..\server" - -!ELSEIF "$(CFG)" == "gen_test_char - Win32 Debug" - -"libapr - Win32 Debug" : - cd ".\..\srclib\apr" - $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" - cd "..\..\server" - -"libapr - Win32 DebugCLEAN" : - cd ".\..\srclib\apr" - $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN - cd "..\..\server" - -!ENDIF - -SOURCE=.\gen_test_char.c - -"$(INTDIR)\gen_test_char.obj" : $(SOURCE) "$(INTDIR)" - - - -!ENDIF - diff --git a/server/listen.c b/server/listen.c index b284d83b..a4935aaa 100644 --- a/server/listen.c +++ b/server/listen.c @@ -20,16 +20,18 @@ #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" +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + AP_DECLARE_DATA ap_listen_rec *ap_listeners = NULL; static ap_listen_rec *old_listeners; @@ -54,7 +56,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) #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, + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00067) "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)", server->bind_addr); apr_socket_close(s); @@ -64,7 +66,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) 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, + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00068) "make_sock: for address %pI, apr_socket_opt_set: (SO_KEEPALIVE)", server->bind_addr); apr_socket_close(s); @@ -75,7 +77,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) 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, + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00069) "make_sock: for address %pI, apr_socket_opt_set: " "(IPV6_V6ONLY)", server->bind_addr); @@ -107,7 +109,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) 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, + ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p, APLOGNO(00070) "make_sock: failed to set SendBufferSize for " "address %pI, using default", server->bind_addr); @@ -117,7 +119,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) 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, + ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p, APLOGNO(00071) "make_sock: failed to set ReceiveBufferSize for " "address %pI, using default", server->bind_addr); @@ -130,7 +132,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) #endif if ((stat = apr_socket_bind(s, server->bind_addr)) != APR_SUCCESS) { - ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p, + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p, APLOGNO(00072) "make_sock: could not bind to address %pI", server->bind_addr); apr_socket_close(s); @@ -138,7 +140,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) } if ((stat = apr_socket_listen(s, ap_listenbacklog)) != APR_SUCCESS) { - ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, stat, p, + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, stat, p, APLOGNO(00073) "make_sock: unable to listen for connections " "on address %pI", server->bind_addr); @@ -159,7 +161,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) */ 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, + ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p, APLOGNO(00074) "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)", server->bind_addr); apr_socket_close(s); @@ -170,11 +172,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) 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; } @@ -182,8 +180,7 @@ static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server) 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); + core_server_config *conf = ap_get_core_module_config(s->module_config); if (!proto) { return NULL; } @@ -219,19 +216,17 @@ static void ap_apply_accept_filter(apr_pool_t *p, ap_listen_rec *lis, 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, + ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, APLOGNO(00075) "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); + rv = apr_socket_opt_set(s, APR_TCP_DEFER_ACCEPT, 30); if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) { - ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, + ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p, APLOGNO(00076) "Failed to enable APR_TCP_DEFER_ACCEPT"); } #endif -#endif } } @@ -242,7 +237,8 @@ static apr_status_t close_listeners_on_exec(void *v) } static const char *alloc_listener(process_rec *process, char *addr, - apr_port_t port, const char* proto) + apr_port_t port, const char* proto, + void *dummy) { ap_listen_rec **walk, *last; apr_status_t status; @@ -277,13 +273,16 @@ static const char *alloc_listener(process_rec *process, char *addr, } if (found_listener) { + if (ap_listeners->slave != dummy) { + return "Cannot define a slave on the same IP:port as a 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, + ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(00077) "alloc_listener: failed to set up sockaddr for %s", addr); return "Listen setup failed"; @@ -321,7 +320,7 @@ static const char *alloc_listener(process_rec *process, char *addr, } #endif if (status != APR_SUCCESS) { - ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, + ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool, APLOGNO(00078) "alloc_listener: failed to get a socket for %s", addr); return "Listen setup failed"; @@ -334,6 +333,7 @@ static const char *alloc_listener(process_rec *process, char *addr, last->next = new; last = new; } + new->slave = dummy; } return NULL; @@ -416,7 +416,7 @@ static int open_listeners(apr_pool_t *pool) * listen (which would generate an error). IPv4 will be handled * on the established IPv6 socket. */ - if (IS_INADDR_ANY(lr->bind_addr)) { + if (IS_INADDR_ANY(lr->bind_addr) && previous) { for (cur = ap_listeners; cur != lr; cur = cur->next) { if (lr->bind_addr->port == cur->bind_addr->port && IS_IN6ADDR_ANY(cur->bind_addr) @@ -440,7 +440,6 @@ static int open_listeners(apr_pool_t *pool) #endif if (make_sock(pool, lr) == APR_SUCCESS) { ++num_open; - lr->active = 1; } else { #if APR_HAVE_IPV6 @@ -499,7 +498,7 @@ static int open_listeners(apr_pool_t *pool) status = apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, use_nonblock); if (status != APR_SUCCESS) { - ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, pool, + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, pool, APLOGNO(00079) "unable to control socket non-blocking status"); return -1; } @@ -589,6 +588,22 @@ AP_DECLARE_NONSTD(void) ap_close_listeners(void) lr->active = 0; } } +AP_DECLARE_NONSTD(int) ap_close_selected_listeners(ap_slave_t *slave) +{ + ap_listen_rec *lr; + int n = 0; + + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->slave != slave) { + apr_socket_close(lr->sd); + lr->active = 0; + } + else { + ++n; + } + } + return n; +} AP_DECLARE(void) ap_listen_pre_config(void) { @@ -597,7 +612,10 @@ AP_DECLARE(void) ap_listen_pre_config(void) ap_listenbacklog = DEFAULT_LISTENBACKLOG; } - +/* Hack: populate an extra field + * When this gets called from a Listen directive, dummy is null. + * So we can use non-null dummy to pass a data pointer without conflict + */ AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, int argc, char *const argv[]) { @@ -644,7 +662,7 @@ AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy, ap_str_tolower(proto); } - return alloc_listener(cmd->server->process, host, port, proto); + return alloc_listener(cmd->server->process, host, port, proto, dummy); } AP_DECLARE_NONSTD(const char *) ap_set_listenbacklog(cmd_parms *cmd, diff --git a/server/log.c b/server/log.c index 42a77290..de560efc 100644 --- a/server/log.c +++ b/server/log.c @@ -28,6 +28,8 @@ #include "apr_thread_proc.h" #include "apr_lib.h" #include "apr_signal.h" +#include "apr_portable.h" +#include "apr_base64.h" #define APR_WANT_STDIO #define APR_WANT_STRFUNC @@ -39,8 +41,9 @@ #if APR_HAVE_UNISTD_H #include <unistd.h> #endif - -#define CORE_PRIVATE +#if APR_HAVE_PROCESS_H +#include <process.h> /* for getpid() on Win32 */ +#endif #include "ap_config.h" #include "httpd.h" @@ -51,13 +54,23 @@ #include "util_time.h" #include "ap_mpm.h" +#if HAVE_GETTID +#include <sys/syscall.h> +#include <sys/types.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + typedef struct { - char *t_name; - int t_val; + const char *t_name; + int t_val; } TRANS; APR_HOOK_STRUCT( APR_HOOK_LINK(error_log) + APR_HOOK_LINK(generate_log_id) ) int AP_DECLARE_DATA ap_default_loglevel = DEFAULT_LOGLEVEL; @@ -136,6 +149,14 @@ static const TRANS priorities[] = { {"notice", APLOG_NOTICE}, {"info", APLOG_INFO}, {"debug", APLOG_DEBUG}, + {"trace1", APLOG_TRACE1}, + {"trace2", APLOG_TRACE2}, + {"trace3", APLOG_TRACE3}, + {"trace4", APLOG_TRACE4}, + {"trace5", APLOG_TRACE5}, + {"trace6", APLOG_TRACE6}, + {"trace7", APLOG_TRACE7}, + {"trace8", APLOG_TRACE8}, {NULL, -1}, }; @@ -151,14 +172,35 @@ typedef struct 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. We let the plog cleanups close the actual pipes. +/** + * @brief The piped logging structure. + * + * Piped logs are used to move functionality out of the main server. + * For example, log rotation is done with piped logs. */ -static apr_status_t clear_handle_list(void *v) +struct piped_log { + /** The pool to use for the piped log */ + apr_pool_t *p; + /** The pipe between the server and the logging process */ + apr_file_t *read_fd, *write_fd; +#ifdef AP_HAVE_RELIABLE_PIPED_LOGS + /** The name of the program the logging process is running */ + char *program; + /** The pid of the logging process */ + apr_proc_t *pid; + /** How to reinvoke program when it must be replaced */ + apr_cmdtype_e cmdtype; +#endif +}; + +AP_DECLARE(apr_file_t *) ap_piped_log_read_fd(piped_log *pl) { - read_handles = NULL; - return APR_SUCCESS; + return pl->read_fd; +} + +AP_DECLARE(apr_file_t *) ap_piped_log_write_fd(piped_log *pl) +{ + return pl->write_fd; } /* remember to close this handle in the child process @@ -167,7 +209,7 @@ static apr_status_t clear_handle_list(void *v) * take the parent process's child procs. * If the win32 parent instead passed each and every * logger write handle from itself down to the child, - * and the parent manages all aspects of keeping the + * and the parent manages all aspects of keeping the * reliable pipe log children alive, this would still * make no sense :) Cripple it on Win32. */ @@ -206,14 +248,14 @@ AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, 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", + APR_EBADPATH, NULL, APLOGNO(00085) "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, + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL, APLOGNO(00086) "%s: could not open error log file %s.", ap_server_argv0, fname); return rc; @@ -224,18 +266,18 @@ AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, */ stderr_pool = p; } - if ((rc = apr_file_open_stderr(&stderr_log, stderr_pool)) + if ((rc = apr_file_open_stderr(&stderr_log, stderr_pool)) == APR_SUCCESS) { apr_file_flush(stderr_log); - if ((rc = apr_file_dup2(stderr_log, stderr_file, stderr_pool)) + if ((rc = apr_file_dup2(stderr_log, stderr_file, stderr_pool)) == APR_SUCCESS) { apr_file_close(stderr_file); /* * You might ponder why stderr_pool should survive? * The trouble is, stderr_pool may have s_main->error_log, * so we aren't in a position to destory stderr_pool until - * the next recycle. There's also an apparent bug which - * is not; if some folk decided to call this function before + * the next recycle. There's also an apparent bug which + * is not; if some folk decided to call this function before * the core open error logs hook, this pool won't survive. * Neither does the stderr logger, so this isn't a problem. */ @@ -246,7 +288,7 @@ AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, stderr_pool = NULL; if (rc != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rc, NULL, + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, NULL, APLOGNO(00087) "unable to replace stderr with error log file"); } return rc; @@ -255,7 +297,7 @@ AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, 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, + ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00088) "%s", description); } @@ -275,16 +317,18 @@ static int log_child(apr_pool_t *p, const char *progname, apr_status_t rc; apr_procattr_t *procattr; apr_proc_t *procnew; - apr_file_t *outfile, *errfile; + apr_file_t *errfile; if (((rc = apr_procattr_create(&procattr, p)) == APR_SUCCESS) + && ((rc = apr_procattr_dir_set(procattr, + ap_server_root)) == APR_SUCCESS) && ((rc = apr_procattr_cmdtype_set(procattr, cmdtype)) == 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)) + && ((rc = apr_procattr_child_errfn_set(procattr, log_child_errfn)) == APR_SUCCESS)) { char **args; const char *pname; @@ -293,11 +337,8 @@ static int log_child(apr_pool_t *p, const char *progname, pname = apr_pstrdup(p, args[0]); procnew = (apr_proc_t *)apr_pcalloc(p, sizeof(*procnew)); - if ((rc = apr_file_open_stdout(&outfile, p)) == APR_SUCCESS) { - rc = apr_procattr_child_out_set(procattr, outfile, NULL); - if (dummy_stderr) - rc = apr_procattr_child_err_set(procattr, outfile, NULL); - else if ((rc = apr_file_open_stderr(&errfile, p)) == APR_SUCCESS) + if (dummy_stderr) { + if ((rc = apr_file_open_stdout(&errfile, p)) == APR_SUCCESS) rc = apr_procattr_child_err_set(procattr, errfile, NULL); } @@ -325,29 +366,30 @@ static int open_error_log(server_rec *s, int is_main, apr_pool_t *p) if (*s->error_fname == '|') { apr_file_t *dummy = NULL; - apr_cmdtype_e cmdtype = APR_SHELLCMD_ENV; + apr_cmdtype_e cmdtype = APR_PROGRAM_ENV; fname = s->error_fname + 1; /* In 2.4 favor PROGRAM_ENV, accept "||prog" syntax for compatibility * and "|$cmd" to override the default. - * Any 2.2 backport would continue to favor SHELLCMD_ENV so there + * Any 2.2 backport would continue to favor SHELLCMD_ENV so there * accept "||prog" to override, and "|$cmd" to ease conversion. */ - if (*fname == '|') { - cmdtype = APR_PROGRAM_ENV; + if (*fname == '|') ++fname; - } - if (*fname == '$') + if (*fname == '$') { + cmdtype = APR_SHELLCMD_ENV; ++fname; - + } + /* Spawn a new child logger. If this is the main server_rec, * the new child must use a dummy stderr since the current * stderr might be a pipe to the old logger. Otherwise, the * child inherits the parents stderr. */ rc = log_child(p, fname, &dummy, cmdtype, is_main); if (rc != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL, - "Couldn't start ErrorLog process"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL, APLOGNO(00089) + "Couldn't start ErrorLog process '%s'.", + s->error_fname + 1); return DONE; } @@ -379,7 +421,7 @@ static int open_error_log(server_rec *s, int is_main, apr_pool_t *p) else { fname = ap_server_root_relative(p, s->error_fname); if (!fname) { - ap_log_error(APLOG_MARK, APLOG_STARTUP, APR_EBADPATH, NULL, + ap_log_error(APLOG_MARK, APLOG_STARTUP, APR_EBADPATH, NULL, APLOGNO(00090) "%s: Invalid error log path %s.", ap_server_argv0, s->error_fname); return DONE; @@ -387,7 +429,7 @@ static int open_error_log(server_rec *s, int is_main, apr_pool_t *p) 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, + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL, APLOGNO(00091) "%s: could not open error log file %s.", ap_server_argv0, fname); return DONE; @@ -408,24 +450,24 @@ int ap_open_logs(apr_pool_t *pconf, apr_pool_t *p /* plog */, /* Register to throw away the read_handles list when we * cleanup plog. Upon fork() for the apache children, * this read_handles list is closed so only the parent - * can relaunch a lost log child. These read handles + * can relaunch a lost log child. These read handles * are always closed on exec. - * We won't care what happens to our stderr log child - * between log phases, so we don't mind losing stderr's + * We won't care what happens to our stderr log child + * between log phases, so we don't mind losing stderr's * read_handle a little bit early. */ - apr_pool_cleanup_register(p, NULL, clear_handle_list, + apr_pool_cleanup_register(p, &read_handles, ap_pool_cleanup_set_null, apr_pool_cleanup_null); /* HERE we need a stdout log that outlives plog. - * We *presume* the parent of plog is a process + * We *presume* the parent of plog is a process * or global pool which spans server restarts. * Create our stderr_pool as a child of the plog's * parent pool. */ apr_pool_create(&stderr_p, apr_pool_parent_get(p)); apr_pool_tag(stderr_p, "stderr_pool"); - + if (open_error_log(s_main, 1, stderr_p) != OK) { return DONE; } @@ -433,12 +475,12 @@ int ap_open_logs(apr_pool_t *pconf, apr_pool_t *p /* plog */, replace_stderr = 1; if (s_main->error_log) { apr_status_t rv; - + /* Replace existing stderr with new log. */ apr_file_flush(s_main->error_log); rv = apr_file_dup2(stderr_log, s_main->error_log, stderr_p); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s_main, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s_main, APLOGNO(00092) "unable to replace stderr with error_log"); } else { @@ -465,9 +507,10 @@ int ap_open_logs(apr_pool_t *pconf, apr_pool_t *p /* plog */, * 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 + * errno-as-apr_status_t is also non-portable */ if (replace_stderr && freopen("/dev/null", "w", stderr) == NULL) { - ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s_main, + ap_log_error(APLOG_MARK, APLOG_CRIT, errno, s_main, APLOGNO(00093) "unable to replace stderr with /dev/null"); } @@ -505,91 +548,144 @@ AP_DECLARE(void) ap_error_log2stderr(server_rec *s) { } } -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) +static int cpystrn(char *buf, const char *arg, int buflen) { - 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; + char *end; + if (!arg) + return 0; + end = apr_cpystrn(buf, arg, buflen); + return end - buf; +} - 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; - } +static int log_remote_address(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r && !(arg && *arg == 'c')) + return apr_snprintf(buf, buflen, "%s:%d", info->r->useragent_ip, + info->r->useragent_addr->port); + else if (info->c) + return apr_snprintf(buf, buflen, "%s:%d", info->c->client_ip, + info->c->client_addr->port); + else + return 0; +} - 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; - } +static int log_local_address(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->c) + return apr_snprintf(buf, buflen, "%s:%d", info->c->local_ip, + info->c->local_addr->port); + else + return 0; +} - 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; - } +static int log_pid(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + pid_t pid = getpid(); + return apr_snprintf(buf, buflen, "%" APR_PID_T_FMT, pid); +} - logf = stderr; +static int log_tid(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ +#if APR_HAS_THREADS + int result; +#endif +#if HAVE_GETTID + if (arg && *arg == 'g') { + pid_t tid = syscall(SYS_gettid); + if (tid == -1) + return 0; + return apr_snprintf(buf, buflen, "%"APR_PID_T_FMT, tid); } -#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; - } +#endif +#if APR_HAS_THREADS + if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS + && result != AP_MPMQ_NOT_SUPPORTED) + { + apr_os_thread_t tid = apr_os_thread_current(); + return apr_snprintf(buf, buflen, "%pT", &tid); } +#endif + return 0; +} - 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; +static int log_ctime(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + int time_len = buflen; + int option = AP_CTIME_OPTION_NONE; + + while(arg && *arg) { + switch (*arg) { + case 'u': option |= AP_CTIME_OPTION_USEC; + break; + case 'c': option |= AP_CTIME_OPTION_COMPACT; + break; + } + arg++; } - if ((level & APLOG_STARTUP) != APLOG_STARTUP) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "[%s] ", priorities[level_and_mask].t_name); + ap_recent_ctime_ex(buf, apr_time_now(), option, &time_len); + + /* ap_recent_ctime_ex includes the trailing \0 in time_len */ + return time_len - 1; +} + +static int log_loglevel(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->level < 0) + return 0; + else + return cpystrn(buf, priorities[info->level].t_name, buflen); +} + +static int log_log_id(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + /* + * C: log conn log_id if available, + * c: log conn log id if available and not a once-per-request log line + * else: log request log id if available + */ + if (arg && !strcasecmp(arg, "c")) { + if (info->c && (*arg != 'C' || !info->r)) { + return cpystrn(buf, info->c->log_id, buflen); + } + } + else if (info->rmain) { + return cpystrn(buf, info->rmain->log_id, buflen); } + return 0; +} + +static int log_keepalives(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (!info->c) + return 0; + + return apr_snprintf(buf, buflen, "%d", info->c->keepalives); +} + +static int log_module_name(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + return cpystrn(buf, ap_find_module_short_name(info->module_index), buflen); +} -#ifndef TPF - if (file && level_and_mask == APLOG_DEBUG) { +static int log_file_line(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->file == NULL) { + return 0; + } + else { + const char *file = info->file; #if defined(_OSD_POSIX) || defined(WIN32) || defined(__MVS__) char tmp[256]; char *e = strrchr(file, '/'); @@ -621,71 +717,362 @@ static void log_error_core(const char *file, int line, int level, file = p + 1; } #endif /*_OSD_POSIX || WIN32 */ - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - "%s(%d): ", file, line); + return apr_snprintf(buf, buflen, "%s(%d)", file, info->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); +static int log_apr_status(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + apr_status_t status = info->status; + int len; + if (!status) + return 0; + + if (status < APR_OS_START_EAIERR) { + len = apr_snprintf(buf, buflen, "(%d)", status); } - 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); + else if (status < APR_OS_START_SYSERR) { + len = apr_snprintf(buf, buflen, "(EAI %d)", + status - APR_OS_START_EAIERR); + } + else if (status < 100000 + APR_OS_START_SYSERR) { + len = apr_snprintf(buf, buflen, "(OS %d)", + status - APR_OS_START_SYSERR); + } + else { + len = apr_snprintf(buf, buflen, "(os 0x%08x)", + status - APR_OS_START_SYSERR); + } + apr_strerror(status, buf + len, buflen - len); + len += strlen(buf + len); + return len; +} + +static int log_server_name(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r) + return cpystrn(buf, ap_get_server_name((request_rec *)info->r), buflen); + + return 0; +} + +static int log_virtual_host(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->s) + return cpystrn(buf, info->s->server_hostname, buflen); + + return 0; +} + + +static int log_table_entry(const apr_table_t *table, const char *name, + char *buf, int buflen) +{ +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + const char *value; + char scratch[MAX_STRING_LEN]; + + if ((value = apr_table_get(table, name)) != NULL) { + ap_escape_errorlog_item(scratch, value, MAX_STRING_LEN); + return cpystrn(buf, scratch, buflen); + } + + return 0; +#else + return cpystrn(buf, apr_table_get(table, name), buflen); +#endif +} + +static int log_header(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r) + return log_table_entry(info->r->headers_in, arg, buf, buflen); + + return 0; +} + +static int log_note(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + /* XXX: maybe escaping the entry is not necessary for notes? */ + if (info->r) + return log_table_entry(info->r->notes, arg, buf, buflen); + + return 0; +} + +static int log_env_var(const ap_errorlog_info *info, const char *arg, + char *buf, int buflen) +{ + if (info->r) + return log_table_entry(info->r->subprocess_env, arg, buf, buflen); + + return 0; +} + +static int core_generate_log_id(const conn_rec *c, const request_rec *r, + const char **idstring) +{ + apr_uint64_t id, tmp; + pid_t pid; + int len; + char *encoded; + + if (r && r->request_time) { + id = (apr_uint64_t)r->request_time; + } + else { + id = (apr_uint64_t)apr_time_now(); + } + + pid = getpid(); + if (sizeof(pid_t) > 2) { + tmp = pid; + tmp = tmp << 40; + id ^= tmp; + pid = pid >> 24; + tmp = pid; + tmp = tmp << 56; + id ^= tmp; + } + else { + tmp = pid; + tmp = tmp << 40; + id ^= tmp; + } +#if APR_HAS_THREADS + { + apr_uintptr_t tmp2 = (apr_uintptr_t)c->current_thread; + tmp = tmp2; + tmp = tmp << 32; + id ^= tmp; + } +#endif + + len = apr_base64_encode_len(sizeof(id)); /* includes trailing \0 */ + encoded = apr_palloc(r ? r->pool : c->pool, len); + apr_base64_encode(encoded, (char *)&id, sizeof(id)); + + /* Skip the last char, it is always '=' */ + encoded[len - 2] = '\0'; + + *idstring = encoded; + + return OK; +} + +static void add_log_id(const conn_rec *c, const request_rec *r) +{ + const char **id; + /* need to cast const away */ + if (r) { + id = &((request_rec *)r)->log_id; + } + else { + id = &((conn_rec *)c)->log_id; + } + + ap_run_generate_log_id(c, r, id); +} + +AP_DECLARE(void) ap_register_log_hooks(apr_pool_t *p) +{ + ap_hook_generate_log_id(core_generate_log_id, NULL, NULL, + APR_HOOK_REALLY_LAST); + + ap_register_errorlog_handler(p, "a", log_remote_address, 0); + ap_register_errorlog_handler(p, "A", log_local_address, 0); + ap_register_errorlog_handler(p, "e", log_env_var, 0); + ap_register_errorlog_handler(p, "E", log_apr_status, 0); + ap_register_errorlog_handler(p, "F", log_file_line, 0); + ap_register_errorlog_handler(p, "i", log_header, 0); + ap_register_errorlog_handler(p, "k", log_keepalives, 0); + ap_register_errorlog_handler(p, "l", log_loglevel, 0); + ap_register_errorlog_handler(p, "L", log_log_id, 0); + ap_register_errorlog_handler(p, "m", log_module_name, 0); + ap_register_errorlog_handler(p, "n", log_note, 0); + ap_register_errorlog_handler(p, "P", log_pid, 0); + ap_register_errorlog_handler(p, "t", log_ctime, 0); + ap_register_errorlog_handler(p, "T", log_tid, 0); + ap_register_errorlog_handler(p, "v", log_virtual_host, 0); + ap_register_errorlog_handler(p, "V", log_server_name, 0); +} + +/* + * This is used if no error log format is defined and during startup. + * It automatically omits the timestamp if logging to syslog. + */ +static int do_errorlog_default(const ap_errorlog_info *info, char *buf, + int buflen, int *errstr_start, int *errstr_end, + const char *errstr_fmt, va_list args) +{ + int len = 0; + int field_start = 0; + int item_len; +#ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED + char scratch[MAX_STRING_LEN]; +#endif + + if (!info->using_syslog && !info->startup) { + buf[len++] = '['; + len += log_ctime(info, "u", buf + len, buflen - len); + buf[len++] = ']'; + buf[len++] = ' '; + } + + if (!info->startup) { + buf[len++] = '['; + len += log_module_name(info, NULL, buf + len, buflen - len); + buf[len++] = ':'; + len += log_loglevel(info, NULL, buf + len, buflen - len); + len += cpystrn(buf + len, "] [pid ", buflen - len); + + len += log_pid(info, NULL, buf + len, buflen - len); +#if APR_HAS_THREADS + field_start = len; + len += cpystrn(buf + len, ":tid ", buflen - len); + item_len = log_tid(info, NULL, buf + len, buflen - len); + if (!item_len) + len = field_start; + else + len += item_len; +#endif + buf[len++] = ']'; + buf[len++] = ' '; + } + + if (info->level >= APLOG_DEBUG) { + item_len = log_file_line(info, NULL, buf + len, buflen - len); + if (item_len) { + len += item_len; + len += cpystrn(buf + len, ": ", buflen - len); } - 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'; + } + + if (info->status) { + item_len = log_apr_status(info, NULL, buf + len, buflen - len); + if (item_len) { + len += item_len; + len += cpystrn(buf + len, ": ", buflen - len); } } - errstrlen = len; + /* + * useragent_ip/client_ip can be client or backend server. If we have + * a scoreboard handle, it is likely a client. + */ + if (info->r) { + len += apr_snprintf(buf + len, buflen - len, + info->r->connection->sbh ? "[client %s:%d] " : "[remote %s:%d] ", + info->r->useragent_ip, info->r->useragent_addr->port); + } + else if (info->c) { + len += apr_snprintf(buf + len, buflen - len, + info->c->sbh ? "[client %s:%d] " : "[remote %s:%d] ", + info->c->client_ip, info->c->client_addr->port); + } + + /* the actual error message */ + *errstr_start = 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); + if (apr_vsnprintf(scratch, MAX_STRING_LEN, errstr_fmt, args)) { + len += ap_escape_errorlog_item(buf + len, scratch, + buflen - len); + } #else - len += apr_vsnprintf(errstr + len, MAX_STRING_LEN - len, fmt, args); + len += apr_vsnprintf(buf + len, buflen - len, errstr_fmt, args); #endif + *errstr_end = len; + + field_start = len; + len += cpystrn(buf + len, ", referer: ", buflen - len); + item_len = log_header(info, "Referer", buf + len, buflen - len); + if (item_len) + len += item_len; + else + len = field_start; - if ( r && (referer = apr_table_get(r->headers_in, "Referer")) + return len; +} + +static int do_errorlog_format(apr_array_header_t *fmt, ap_errorlog_info *info, + char *buf, int buflen, int *errstr_start, + int *errstr_end, const char *err_fmt, va_list args) +{ #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED - && ap_escape_errorlog_item(scratch, referer, MAX_STRING_LEN - len) + char scratch[MAX_STRING_LEN]; #endif - ) { - len += apr_snprintf(errstr + len, MAX_STRING_LEN - len, - ", referer: %s", + int i; + int len = 0; + int field_start = 0; + int skipping = 0; + ap_errorlog_format_item *items = (ap_errorlog_format_item *)fmt->elts; + + for (i = 0; i < fmt->nelts; ++i) { + ap_errorlog_format_item *item = &items[i]; + if (item->flags & AP_ERRORLOG_FLAG_FIELD_SEP) { + if (skipping) { + skipping = 0; + } + else { + field_start = len; + } + } + + if (item->flags & AP_ERRORLOG_FLAG_MESSAGE) { + /* the actual error message */ + *errstr_start = len; #ifndef AP_UNSAFE_ERROR_LOG_UNESCAPED - scratch + if (apr_vsnprintf(scratch, MAX_STRING_LEN, err_fmt, args)) { + len += ap_escape_errorlog_item(buf + len, scratch, + buflen - len); + + } #else - referer + len += apr_vsnprintf(buf + len, buflen - len, err_fmt, args); #endif - ); + *errstr_end = len; + } + else if (skipping) { + continue; + } + else if (info->level != -1 && (int)item->min_loglevel > info->level) { + len = field_start; + skipping = 1; + } + else { + int item_len = (*item->func)(info, item->arg, buf + len, + buflen - len); + if (!item_len) { + if (item->flags & AP_ERRORLOG_FLAG_REQUIRED) { + /* required item is empty. skip whole line */ + buf[0] = '\0'; + return 0; + } + else if (item->flags & AP_ERRORLOG_FLAG_NULL_AS_HYPHEN) { + buf[len++] = '-'; + } + else { + len = field_start; + skipping = 1; + } + } + else { + len += item_len; + } + } } + return len; +} +static void write_logline(char *errstr, apr_size_t len, apr_file_t *logf, + int level) +{ /* NULL if we are logging to syslog */ if (logf) { /* Truncate for the terminator (as apr_snprintf does) */ @@ -698,44 +1085,238 @@ static void log_error_core(const char *file, int line, int level, } #ifdef HAVE_SYSLOG else { - syslog(level_and_mask, "%s", errstr); + syslog(level < LOG_PRIMASK ? level : APLOG_DEBUG, "%s", errstr); } #endif +} + +static void log_error_core(const char *file, int line, int module_index, + 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]; + apr_file_t *logf = NULL; + int level_and_mask = level & APLOG_LEVELMASK; + const request_rec *rmain = NULL; + core_server_config *sconf = NULL; + ap_errorlog_info info; + + /* do we need to log once-per-req or once-per-conn info? */ + int log_conn_info = 0, log_req_info = 0; + apr_array_header_t **lines = NULL; + int done = 0; + int line_number = 0; + + 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 + */ +#ifndef DEBUG + if ((level_and_mask != APLOG_NOTICE) + && (level_and_mask > ap_default_loglevel)) { + return; + } +#endif + + logf = stderr_log; + } + else { + int configured_level = r ? ap_get_request_module_loglevel(r, module_index) : + c ? ap_get_conn_server_module_loglevel(c, s, module_index) : + ap_get_server_module_loglevel(s, module_index); + if (s->error_log) { + /* + * If we are doing normal logging, don't log messages that are + * above the module's log level unless it is a startup/shutdown notice + */ + if ((level_and_mask != APLOG_NOTICE) + && (level_and_mask > configured_level)) { + return; + } + + logf = s->error_log; + } + else { + /* + * If we are doing syslog logging, don't log messages that are + * above the module's log level (including a startup/shutdown notice) + */ + if (level_and_mask > configured_level) { + return; + } + } + + /* the faked server_rec from mod_cgid does not have s->module_config */ + if (s->module_config) { + sconf = ap_get_core_module_config(s->module_config); + if (c && !c->log_id) { + add_log_id(c, NULL); + if (sconf->error_log_conn && sconf->error_log_conn->nelts > 0) + log_conn_info = 1; + } + if (r) { + if (r->main) + rmain = r->main; + else + rmain = r; + + if (!rmain->log_id) { + /* XXX: do we need separate log ids for subrequests? */ + if (sconf->error_log_req && sconf->error_log_req->nelts > 0) + log_req_info = 1; + /* + * XXX: potential optimization: only create log id if %L is + * XXX: actually used + */ + add_log_id(c, rmain); + } + } + } + } + + info.s = s; + info.c = c; + info.pool = pool; + info.file = NULL; + info.line = 0; + info.status = 0; + info.using_syslog = (logf == NULL); + info.startup = ((level & APLOG_STARTUP) == APLOG_STARTUP); + info.format = fmt; + + while (!done) { + apr_array_header_t *log_format; + int len = 0, errstr_start = 0, errstr_end = 0; + /* XXX: potential optimization: format common prefixes only once */ + if (log_conn_info) { + /* once-per-connection info */ + if (line_number == 0) { + lines = (apr_array_header_t **)sconf->error_log_conn->elts; + info.r = NULL; + info.rmain = NULL; + info.level = -1; + info.module_index = APLOG_NO_MODULE; + } + + log_format = lines[line_number++]; + + if (line_number == sconf->error_log_conn->nelts) { + /* this is the last line of once-per-connection info */ + line_number = 0; + log_conn_info = 0; + } + } + else if (log_req_info) { + /* once-per-request info */ + if (line_number == 0) { + lines = (apr_array_header_t **)sconf->error_log_req->elts; + info.r = rmain; + info.rmain = rmain; + info.level = -1; + info.module_index = APLOG_NO_MODULE; + } + + log_format = lines[line_number++]; + + if (line_number == sconf->error_log_req->nelts) { + /* this is the last line of once-per-request info */ + line_number = 0; + log_req_info = 0; + } + } + else { + /* the actual error message */ + info.r = r; + info.rmain = rmain; + info.level = level_and_mask; + info.module_index = module_index; + info.file = file; + info.line = line; + info.status = status; + log_format = sconf ? sconf->error_log_format : NULL; + done = 1; + } + + /* + * prepare and log one line + */ + + if (log_format) { + len += do_errorlog_format(log_format, &info, errstr + len, + MAX_STRING_LEN - len, + &errstr_start, &errstr_end, fmt, args); + } + else { + len += do_errorlog_default(&info, errstr + len, MAX_STRING_LEN - len, + &errstr_start, &errstr_end, fmt, args); + } + + if (!*errstr) + { + /* + * Don't log empty lines. This can happen with once-per-conn/req + * info if an item with AP_ERRORLOG_FLAG_REQUIRED is NULL. + */ + continue; + } + write_logline(errstr, len, logf, level_and_mask); + + if (done) { + /* + * We don't call the error_log hook for per-request/per-conn + * lines, and we only pass the actual log message, not the + * prefix and suffix. + */ + errstr[errstr_end] = '\0'; + ap_run_error_log(&info, errstr + errstr_start); + } - ap_run_error_log(file, line, level, status, s, r, pool, errstr + errstrlen); + *errstr = '\0'; + } } -AP_DECLARE(void) ap_log_error(const char *file, int line, int level, - apr_status_t status, const server_rec *s, - const char *fmt, ...) +AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, + 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); + log_error_core(file, line, module_index, 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, ...) +AP_DECLARE(void) ap_log_perror_(const char *file, int line, int module_index, + 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); + log_error_core(file, line, module_index, 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, ...) +AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index, + 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); + log_error_core(file, line, module_index, level, status, r->server, NULL, r, + NULL, fmt, args); /* * IF APLOG_TOCLIENT is set, @@ -756,18 +1337,77 @@ AP_DECLARE(void) ap_log_rerror(const char *file, int line, int level, 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, ...) +AP_DECLARE(void) ap_log_cserror_(const char *file, int line, int module_index, + int level, apr_status_t status, + const conn_rec *c, const server_rec *s, + 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); + log_error_core(file, line, module_index, level, status, s, c, + NULL, NULL, fmt, args); va_end(args); } +AP_DECLARE(void) ap_log_cerror_(const char *file, int line, int module_index, + 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, module_index, level, status, c->base_server, c, + NULL, NULL, fmt, args); + va_end(args); +} + +AP_DECLARE(void) ap_log_command_line(apr_pool_t *plog, server_rec *s) +{ + int i; + process_rec *process = s->process; + char *result; + int len_needed = 0; + + /* Piece together the command line from the pieces + * in process->argv, with spaces in between. + */ + for (i = 0; i < process->argc; i++) { + len_needed += strlen(process->argv[i]) + 1; + } + + result = (char *) apr_palloc(plog, len_needed); + *result = '\0'; + + for (i = 0; i < process->argc; i++) { + strcat(result, process->argv[i]); + if ((i+1)< process->argc) { + strcat(result, " "); + } + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00094) + "Command line: '%s'", result); +} + +AP_DECLARE(void) ap_remove_pid(apr_pool_t *p, const char *rel_fname) +{ + apr_status_t rv; + const char *fname = ap_server_root_relative(p, rel_fname); + + if (fname != NULL) { + rv = apr_file_remove(fname, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00095) + "failed to remove PID file %s", fname); + } + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00096) + "removed PID file %s (pid=%" APR_PID_T_FMT ")", + fname, getpid()); + } + } +} + AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) { apr_file_t *pid_file = NULL; @@ -784,7 +1424,7 @@ AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) 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); + NULL, APLOGNO(00097) "Invalid PID file path %s, ignoring.", filename); return; } @@ -798,7 +1438,7 @@ AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) * 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, + ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p, APLOGNO(00098) "pid file %s overwritten -- Unclean " "shutdown of previous Apache run?", fname); @@ -808,14 +1448,14 @@ AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) 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, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00099) "could not create %s", fname); - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00100) "%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_printf(pid_file, "%" APR_PID_T_FMT APR_EOL_STR, mypid); apr_file_close(pid_file); saved_pid = mypid; } @@ -837,7 +1477,7 @@ AP_DECLARE(apr_status_t) ap_read_pid(apr_pool_t *p, const char *filename, 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); + NULL, APLOGNO(00101) "Invalid PID file path %s, ignoring.", filename); return APR_EGENERAL; } @@ -872,7 +1512,7 @@ AP_DECLARE(void) ap_log_assert(const char *szExp, const char *szFile, char time_str[APR_CTIME_LEN]; apr_ctime(time_str, apr_time_now()); - ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(00102) "[%s] file %s, line %d, assertion \"%s\" failed", time_str, szFile, nLine, szExp); #if defined(WIN32) @@ -897,30 +1537,26 @@ static apr_status_t piped_log_spawn(piped_log *pl) apr_status_t status; if (((status = apr_procattr_create(&procattr, pl->p)) != APR_SUCCESS) || + ((status = apr_procattr_dir_set(procattr, ap_server_root)) + != APR_SUCCESS) || ((status = apr_procattr_cmdtype_set(procattr, pl->cmdtype)) != APR_SUCCESS) || ((status = apr_procattr_child_in_set(procattr, - ap_piped_log_read_fd(pl), - ap_piped_log_write_fd(pl))) + pl->read_fd, + pl->write_fd)) != 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, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00103) "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_file_t *outfile, *errfile; - - if ((status = apr_file_open_stdout(&outfile, pl->p)) == APR_SUCCESS) - status = apr_procattr_child_out_set(procattr, outfile, NULL); - if ((status = apr_file_open_stderr(&errfile, pl->p)) == APR_SUCCESS) - status = apr_procattr_child_err_set(procattr, errfile, NULL); apr_tokenize_to_argv(pl->program, &args, pl->p); pname = apr_pstrdup(pl->p, args[0]); @@ -930,19 +1566,19 @@ static apr_status_t piped_log_spawn(piped_log *pl) if (status == APR_SUCCESS) { pl->pid = procnew; - /* procnew->in was dup2'd from ap_piped_log_write_fd(pl); + /* procnew->in was dup2'd from pl->write_fd; * 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)); + pl->write_fd, pl->p); + close_handle_in_child(pl->p, pl->read_fd); } else { char buf[120]; /* Something bad happened, give up and go away. */ - ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00104) "unable to start piped log program '%s': %s", pl->program, apr_strerror(status, buf, sizeof(buf))); } @@ -967,20 +1603,20 @@ static void piped_log_maintenance(int reason, void *data, apr_wait_t status) 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, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00105) "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, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00106) "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, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00107) "piped_log_maintenance: unable to respawn '%s': %s", pl->program, apr_strerror(stats, buf, sizeof(buf))); } @@ -1009,8 +1645,8 @@ 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)); + apr_file_close(pl->read_fd); + apr_file_close(pl->write_fd); return APR_SUCCESS; } @@ -1037,16 +1673,17 @@ AP_DECLARE(piped_log *) ap_open_piped_log_ex(apr_pool_t *p, pl->program = apr_pstrdup(p, program); pl->pid = NULL; pl->cmdtype = cmdtype; - if (apr_file_pipe_create(&ap_piped_log_read_fd(pl), - &ap_piped_log_write_fd(pl), p) != APR_SUCCESS) { + if (apr_file_pipe_create_ex(&pl->read_fd, + &pl->write_fd, + APR_FULL_BLOCK, 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)); + apr_file_close(pl->read_fd); + apr_file_close(pl->write_fd); return NULL; } return pl; @@ -1058,7 +1695,7 @@ static apr_status_t piped_log_cleanup(void *data) { piped_log *pl = data; - apr_file_close(ap_piped_log_write_fd(pl)); + apr_file_close(pl->write_fd); return APR_SUCCESS; } @@ -1072,15 +1709,16 @@ AP_DECLARE(piped_log *) ap_open_piped_log_ex(apr_pool_t *p, rc = log_child(p, program, &dummy, cmdtype, 0); if (rc != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL, - "Couldn't start piped log process"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, NULL, APLOGNO(00108) + "Couldn't start piped log process '%s'.", + (program == NULL) ? "NULL" : program); 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; + pl->read_fd = NULL; + pl->write_fd = dummy; apr_pool_cleanup_register(p, pl, piped_log_cleanup, piped_log_cleanup); return pl; @@ -1091,19 +1729,19 @@ AP_DECLARE(piped_log *) ap_open_piped_log_ex(apr_pool_t *p, AP_DECLARE(piped_log *) ap_open_piped_log(apr_pool_t *p, const char *program) { - apr_cmdtype_e cmdtype = APR_SHELLCMD_ENV; + apr_cmdtype_e cmdtype = APR_PROGRAM_ENV; /* In 2.4 favor PROGRAM_ENV, accept "||prog" syntax for compatibility * and "|$cmd" to override the default. - * Any 2.2 backport would continue to favor SHELLCMD_ENV so there + * Any 2.2 backport would continue to favor SHELLCMD_ENV so there * accept "||prog" to override, and "|$cmd" to ease conversion. */ - if (*program == '|') { - cmdtype = APR_PROGRAM_ENV; + if (*program == '|') ++program; - } - if (*program == '$') + if (*program == '$') { + cmdtype = APR_SHELLCMD_ENV; ++program; + } return ap_open_piped_log_ex(p, program, cmdtype); } @@ -1113,10 +1751,30 @@ AP_DECLARE(void) ap_close_piped_log(piped_log *pl) apr_pool_cleanup_run(pl->p, pl, piped_log_cleanup); } +AP_DECLARE(const char *) ap_parse_log_level(const char *str, int *val) +{ + char *err = "Log level keyword must be one of emerg/alert/crit/error/warn/" + "notice/info/debug/trace1/.../trace8"; + int i = 0; + + if (str == NULL) + return err; + + while (priorities[i].t_name != NULL) { + if (!strcasecmp(str, priorities[i].t_name)) { + *val = priorities[i].t_val; + return NULL; + } + i++; + } + return err; +} + 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)) + (const ap_errorlog_info *info, const char *errstr), + (info, errstr)) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, generate_log_id, + (const conn_rec *c, const request_rec *r, + const char **id), + (c, r, id), DECLINED) diff --git a/server/main.c b/server/main.c index 04ed6c78..204d66c7 100644 --- a/server/main.c +++ b/server/main.c @@ -28,18 +28,22 @@ #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 "mod_core.h" +#include "http_request.h" #include "http_vhost.h" #include "apr_uri.h" #include "util_ebcdic.h" #include "ap_mpm.h" -#include "mpm_common.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif /* WARNING: Win32 binds http_main.c dynamically to the server. Please place * extern functions and global data in another appropriate module. @@ -93,10 +97,15 @@ static void show_compile_settings(void) 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", +#if APR_MAJOR_VERSION >= 2 + printf("Server loaded: APR %s\n", apr_version_string()); + printf("Compiled using: APR %s\n", APR_VERSION_STRING); +#else + printf("Server loaded: APR %s, APR-UTIL %s\n", apr_version_string(), apu_version_string()); - printf("Compiled using: APR %s, APR-Util %s\n", + printf("Compiled using: APR %s, APR-UTIL %s\n", APR_VERSION_STRING, APU_VERSION_STRING); +#endif /* sizeof(foo) is long on some platforms so we might as well * make it long everywhere to keep the printf format * consistent @@ -118,10 +127,6 @@ static void show_compile_settings(void) 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 @@ -212,10 +217,6 @@ static void show_compile_settings(void) 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"); @@ -225,10 +226,6 @@ static void show_compile_settings(void) 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 @@ -237,10 +234,6 @@ static void show_compile_settings(void) 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 @@ -267,11 +260,19 @@ static void destroy_and_exit_process(process_rec *process, * might get lost. */ apr_sleep(TASK_SWITCH_SLEEP); + ap_main_state = AP_SQ_MS_EXITING; apr_pool_destroy(process->pool); /* and destroy all descendent pools */ apr_terminate(); exit(process_exit_value); } +/* APR callback invoked if allocation fails. */ +static int abort_on_oom(int retcode) +{ + ap_abort_on_oom(); + return retcode; /* unreachable, hopefully. */ +} + static process_rec *init_process(int *argc, const char * const * *argv) { process_rec *process; @@ -289,16 +290,22 @@ static process_rec *init_process(int *argc, const char * const * *argv) /* For all intents and purposes, this is impossibly unlikely, * but APR doesn't exist yet, we can't use it for reporting * these earliest two failures; + * + * XXX: Note the apr_ctime() and apr_time_now() calls. These + * work, today, against an uninitialized APR, but in the future + * (if they relied on global pools or mutexes, for example) then + * the datestamp logic will need to be replaced. */ char ctimebuff[APR_CTIME_LEN]; apr_ctime(ctimebuff, apr_time_now()); fprintf(stderr, "[%s] [crit] (%d) %s: %s failed " - "to initial context, exiting\n", + "to initial context, exiting\n", ctimebuff, stat, (*argv)[0], failed); apr_terminate(); exit(1); } + apr_pool_abort_set(abort_on_oom, cntx); apr_pool_tag(cntx, "process"); ap_open_stderr_log(cntx); @@ -323,47 +330,33 @@ static void usage(process_rec *process) const char *bin = process->argv[0]; int pad_len = strlen(bin); -#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_len, " "); #ifdef WIN32 ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, - " %*s [-w] [-k start|restart|stop|shutdown]", pad_len, " "); + " %*s [-w] [-k start|restart|stop|shutdown] [-n service_name]", + pad_len, " "); ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, " %*s [-k install|config|uninstall] [-n service_name]", pad_len, " "); -#endif -#ifdef AP_MPM_WANT_SIGNAL_SERVER -#ifdef AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN +#else +/* XXX not all MPMs support signalling the server in general or graceful-stop + * in particular + */ ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, " %*s [-k start|restart|graceful|graceful-stop|stop]", pad_len, " "); -#else - ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, - " %*s [-k start|restart|graceful|stop]", pad_len, " "); -#endif /* AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN */ #endif ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, - " %*s [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S]", + " %*s [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]", pad_len, " "); 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"); @@ -386,7 +379,7 @@ static void usage(process_rec *process) #ifdef WIN32 ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, " -n name : set service name and use its " - "ServerConfigFile"); + "ServerConfigFile and ServerRoot"); ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, " -k start : tell Apache to start"); ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, @@ -423,10 +416,11 @@ static void usage(process_rec *process) " -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)"); + " -t -D DUMP_VHOSTS : show parsed vhost settings"); ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, - " -S : a synonym for -t -D DUMP_VHOSTS"); + " -t -D DUMP_RUN_CFG : show parsed run settings"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -S : a synonym for -t -D DUMP_VHOSTS -D DUMP_RUN_CFG"); 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, @@ -434,7 +428,9 @@ static void usage(process_rec *process) ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, " -t : run syntax check for config files"); ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, - " -T : start without DocumentRoot(s) check"); + " -T : start without DocumentRoot(s) check"); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " -X : debug mode (only one worker, do not detach)"); destroy_and_exit_process(process, 1); } @@ -442,14 +438,12 @@ static void usage(process_rec *process) int main(int argc, const char * const argv[]) { char c; - int configtestonly = 0; + int showcompile = 0, showdirectives = 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 */ @@ -457,23 +451,28 @@ int main(int argc, const char * const argv[]) apr_getopt_t *opt; apr_status_t rv; module **mod; - const char *optarg; + const char *opt_arg; APR_OPTIONAL_FN_TYPE(ap_signal_server) *signal_server; AP_MONCONTROL(0); /* turn off profiling of startup */ process = init_process(&argc, &argv); - pglobal = process->pool; + ap_pglobal = process->pool; pconf = process->pconf; ap_server_argv0 = process->short_name; + ap_init_rng(ap_pglobal); + + /* Set up the OOM callback in the global pool, so all pools should + * by default inherit it. */ + apr_pool_abort_set(abort_on_oom, apr_pool_parent_get(process->pool)); #if APR_CHARSET_EBCDIC - if (ap_init_ebcdic(pglobal) != APR_SUCCESS) { + if (ap_init_ebcdic(ap_pglobal) != APR_SUCCESS) { destroy_and_exit_process(process, 1); } #endif - apr_pool_create(&pcommands, pglobal); + apr_pool_create(&pcommands, ap_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 *)); @@ -481,8 +480,8 @@ int main(int argc, const char * const argv[]) 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); + ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0, NULL, APLOGNO(00012) + "%s: %s", ap_server_argv0, error); destroy_and_exit_process(process, 1); } @@ -493,68 +492,46 @@ int main(int argc, const char * const argv[]) */ apr_getopt_init(&opt, pcommands, process->argc, process->argv); - while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg)) + while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &opt_arg)) == APR_SUCCESS) { char **new; switch (c) { case 'c': new = (char **)apr_array_push(ap_server_post_read_config); - *new = apr_pstrdup(pcommands, optarg); + *new = apr_pstrdup(pcommands, opt_arg); break; case 'C': new = (char **)apr_array_push(ap_server_pre_read_config); - *new = apr_pstrdup(pcommands, optarg); + *new = apr_pstrdup(pcommands, opt_arg); break; case 'd': - def_server_root = optarg; + def_server_root = opt_arg; 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; + *new = apr_pstrdup(pcommands, opt_arg); + /* Setting -D DUMP_VHOSTS should work like setting -S */ + if (strcmp(opt_arg, "DUMP_VHOSTS") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + /* Setting -D DUMP_RUN_CFG should work like setting -S */ + else if (strcmp(opt_arg, "DUMP_RUN_CFG") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; /* Setting -D DUMP_MODULES is equivalent to setting -M */ - if (strcmp(optarg, "DUMP_MODULES") == 0) - configtestonly = 1; + else if (strcmp(opt_arg, "DUMP_MODULES") == 0) + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; 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 { + if (ap_parse_log_level(opt_arg, &ap_default_loglevel) != NULL) usage(process); - } break; case 'E': - temp_error_log = apr_pstrdup(process->pool, optarg); + temp_error_log = apr_pstrdup(process->pool, opt_arg); break; case 'X': @@ -563,7 +540,7 @@ int main(int argc, const char * const argv[]) break; case 'f': - confname = optarg; + confname = opt_arg; break; case 'v': @@ -571,20 +548,18 @@ int main(int argc, const char * const argv[]) 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); + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + showdirectives = 1; + break; case 't': - configtestonly = 1; + if (ap_run_mode == AP_SQ_RM_UNKNOWN) + ap_run_mode = AP_SQ_RM_CONFIG_TEST; break; case 'T': @@ -592,29 +567,46 @@ int main(int argc, const char * const argv[]) break; case 'S': - configtestonly = 1; + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; new = (char **)apr_array_push(ap_server_config_defines); *new = "DUMP_VHOSTS"; + new = (char **)apr_array_push(ap_server_config_defines); + *new = "DUMP_RUN_CFG"; break; case 'M': - configtestonly = 1; + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; new = (char **)apr_array_push(ap_server_config_defines); *new = "DUMP_MODULES"; break; + case 'V': + if (strcmp(ap_show_mpm(), "")) { /* MPM built-in? */ + show_compile_settings(); + destroy_and_exit_process(process, 0); + } + else { + showcompile = 1; + ap_run_mode = AP_SQ_RM_CONFIG_DUMP; + } + break; + case 'h': case '?': usage(process); } } + if (ap_run_mode == AP_SQ_RM_UNKNOWN) + ap_run_mode = AP_SQ_RM_NORMAL; + /* bad cmdline option? then we die */ if (rv != APR_EOF || opt->ind < opt->argc) { usage(process); } - apr_pool_create(&plog, pglobal); + ap_main_state = AP_SQ_MS_CREATE_PRE_CONFIG; + apr_pool_create(&plog, ap_pglobal); apr_pool_tag(plog, "plog"); apr_pool_create(&ptemp, pconf); apr_pool_tag(ptemp, "ptemp"); @@ -629,27 +621,52 @@ int main(int argc, const char * const argv[]) 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) { + ap_server_conf = ap_read_config(process, ptemp, confname, &ap_conftree); + if (!ap_server_conf) { destroy_and_exit_process(process, 1); } + apr_pool_cleanup_register(pconf, &ap_server_conf, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + /* sort hooks here to make sure pre_config hooks are sorted properly */ apr_hook_sort_all(); if (ap_run_pre_config(pconf, plog, ptemp) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, - NULL, "Pre-configuration failed"); + NULL, APLOGNO(00013) "Pre-configuration failed"); destroy_and_exit_process(process, 1); } - rv = ap_process_config_tree(server_conf, ap_conftree, + rv = ap_process_config_tree(ap_server_conf, ap_conftree, process->pconf, ptemp); if (rv == OK) { - ap_fixup_virtual_hosts(pconf, server_conf); - ap_fini_vhost_config(pconf, server_conf); + ap_fixup_virtual_hosts(pconf, ap_server_conf); + ap_fini_vhost_config(pconf, ap_server_conf); + /* + * Sort hooks again because ap_process_config_tree may have add modules + * and hence hooks. This happens with mod_perl and modules written in + * perl. + */ + apr_hook_sort_all(); + + if (ap_run_check_config(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, + NULL, APLOGNO(00014) "Configuration check failed"); + destroy_and_exit_process(process, 1); + } - if (configtestonly) { - ap_run_test_config(pconf, server_conf); - ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK"); + if (ap_run_mode != AP_SQ_RM_NORMAL) { + if (showcompile) { /* deferred due to dynamically loaded MPM */ + show_compile_settings(); + } + else if (showdirectives) { /* deferred in case of DSOs */ + ap_show_directives(); + destroy_and_exit_process(process, 0); + } + else { + ap_run_test_config(pconf, ap_server_conf); + if (ap_run_mode == AP_SQ_RM_CONFIG_TEST) + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK"); + } destroy_and_exit_process(process, 0); } } @@ -670,24 +687,28 @@ int main(int argc, const char * const argv[]) apr_pool_clear(plog); - if ( ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) { + if ( ap_run_open_logs(pconf, plog, ptemp, ap_server_conf) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, - 0, NULL, "Unable to open logs"); + 0, NULL, APLOGNO(00015) "Unable to open logs"); destroy_and_exit_process(process, 1); } - if ( ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) { + if ( ap_run_post_config(pconf, plog, ptemp, ap_server_conf) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, - NULL, "Configuration Failed"); + NULL, APLOGNO(00016) "Configuration Failed"); destroy_and_exit_process(process, 1); } apr_pool_destroy(ptemp); for (;;) { + ap_main_state = AP_SQ_MS_DESTROY_CONFIG; apr_hook_deregister_all(); apr_pool_clear(pconf); + ap_clear_auth_internal(); + ap_main_state = AP_SQ_MS_CREATE_CONFIG; + ap_config_generation++; for (mod = ap_prelinked_modules; *mod != NULL; mod++) { ap_register_hooks(*mod, pconf); } @@ -700,34 +721,50 @@ int main(int argc, const char * const argv[]) 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) { + ap_server_conf = ap_read_config(process, ptemp, confname, &ap_conftree); + if (!ap_server_conf) { destroy_and_exit_process(process, 1); } + apr_pool_cleanup_register(pconf, &ap_server_conf, + ap_pool_cleanup_set_null, apr_pool_cleanup_null); + /* sort hooks here to make sure pre_config hooks are sorted properly */ apr_hook_sort_all(); if (ap_run_pre_config(pconf, plog, ptemp) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, - 0, NULL, "Pre-configuration failed"); + 0, NULL, APLOGNO(00017) "Pre-configuration failed"); destroy_and_exit_process(process, 1); } - if (ap_process_config_tree(server_conf, ap_conftree, process->pconf, + if (ap_process_config_tree(ap_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); + ap_fixup_virtual_hosts(pconf, ap_server_conf); + ap_fini_vhost_config(pconf, ap_server_conf); + /* + * Sort hooks again because ap_process_config_tree may have add modules + * and hence hooks. This happens with mod_perl and modules written in + * perl. + */ + apr_hook_sort_all(); + + if (ap_run_check_config(pconf, plog, ptemp, ap_server_conf) != OK) { + ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0, + NULL, APLOGNO(00018) "Configuration check failed"); + destroy_and_exit_process(process, 1); + } + apr_pool_clear(plog); - if (ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) { + if (ap_run_open_logs(pconf, plog, ptemp, ap_server_conf) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, - 0, NULL, "Unable to open logs"); + 0, NULL, APLOGNO(00019) "Unable to open logs"); destroy_and_exit_process(process, 1); } - if (ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) { + if (ap_run_post_config(pconf, plog, ptemp, ap_server_conf) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, - 0, NULL, "Configuration Failed"); + 0, NULL, APLOGNO(00020) "Configuration Failed"); destroy_and_exit_process(process, 1); } @@ -736,7 +773,8 @@ int main(int argc, const char * const argv[]) ap_run_optional_fn_retrieve(); - if (ap_mpm_run(pconf, plog, server_conf)) + ap_main_state = AP_SQ_MS_RUN_MPM; + if (ap_run_mpm(pconf, plog, ap_server_conf) != OK) break; apr_pool_lock(pconf, 0); @@ -754,8 +792,8 @@ int main(int argc, const char * const argv[]) * 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) +const void *ap_suck_in_APR(void); +const void *ap_suck_in_APR(void) { extern const void *ap_ugly_hack; diff --git a/server/mpm/MPM.NAMING b/server/mpm/MPM.NAMING index 83c0694d..c07884d4 100644 --- a/server/mpm/MPM.NAMING +++ b/server/mpm/MPM.NAMING @@ -2,14 +2,13 @@ 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 + event ......... Multi Process model with threads. One acceptor thread, + multiple worker threads, separate poller threads for idle + connections and asynchoneous write completion. 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 index 2decbde6..a158f8b0 100644 --- a/server/mpm/Makefile.in +++ b/server/mpm/Makefile.in @@ -1,4 +1,4 @@ -SUBDIRS = $(MPM_SUBDIR_NAME) +SUBDIRS = $(MPM_SUBDIRS) include $(top_builddir)/build/rules.mk diff --git a/server/mpm/beos/Makefile.in b/server/mpm/beos/Makefile.in deleted file mode 100644 index 3f88b041..00000000 --- a/server/mpm/beos/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ - -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 deleted file mode 100644 index 70515ee0..00000000 --- a/server/mpm/beos/beos.c +++ /dev/null @@ -1,1207 +0,0 @@ -/* 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_description()); - - 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 deleted file mode 100644 index 8b7aed7d..00000000 --- a/server/mpm/beos/beos.h +++ /dev/null @@ -1,34 +0,0 @@ -/* 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 deleted file mode 100644 index 4f201408..00000000 --- a/server/mpm/beos/config5.m4 +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index d61594b0..00000000 --- a/server/mpm/beos/mpm.h +++ /dev/null @@ -1,50 +0,0 @@ -/* 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 deleted file mode 100644 index 316a4c09..00000000 --- a/server/mpm/beos/mpm_default.h +++ /dev/null @@ -1,84 +0,0 @@ -/* 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 index b0907769..0b263cbc 100644 --- a/server/mpm/config.m4 +++ b/server/mpm/config.m4 @@ -1,79 +1,117 @@ -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|winnt}),[ - APACHE_MPM=$withval -],[ - if test "x$APACHE_MPM" = "x"; then - APACHE_MPM=prefork - fi +dnl common platform checks needed by MPMs, methods for MPMs to state +dnl their support for the platform, functions to query MPM properties + +APR_CHECK_APR_DEFINE(APR_HAS_THREADS) + +have_threaded_sig_graceful=yes +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 + have_threaded_sig_graceful=no + ;; + esac + ;; +esac + +dnl See if APR supports APR_POLLSET_THREADSAFE. +dnl XXX This hack tests for the underlying functions used by APR when it supports +dnl XXX APR_POLLSET_THREADSAFE, and duplicates APR's Darwin version check. +dnl A run-time check for +dnl apr_pollset_create(,,APR_POLLSET_THREADSAFE) == APR_SUCCESS +dnl would be great but an in-tree apr (srclib/apr) hasn't been built yet. + +AC_CACHE_CHECK([whether APR supports thread-safe pollsets], [ac_cv_have_threadsafe_pollset], [ + case $host in + *-apple-darwin[[1-9]].*) + APR_SETIFNULL(ac_cv_func_kqueue, [no]) + ;; + esac + AC_CHECK_FUNCS(kqueue port_create epoll_create) + if test "$ac_cv_func_kqueue$ac_cv_func_port_create$ac_cv_func_epoll_create" != "nonono"; then + ac_cv_have_threadsafe_pollset=yes + else + ac_cv_have_threadsafe_pollset=no + 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" -o "$apache_cv_mpm" = "winnt" ; then - return 0 +dnl See if this is a forking platform w.r.t. MPMs +case $host in + *mingw32* | *os2-emx*) + forking_mpms_supported=no + ;; + *) + forking_mpms_supported=yes + ;; +esac + +dnl APACHE_MPM_SUPPORTED(name, supports-shared, is_threaded) +AC_DEFUN(APACHE_MPM_SUPPORTED,[ + if test "$2" = "yes"; then + eval "ap_supported_mpm_$1=shared" + ap_supported_shared_mpms="$ap_supported_shared_mpms $1 " else + eval "ap_supported_mpm_$1=static" + fi + if test "$3" = "yes"; then + eval "ap_threaded_mpm_$1=yes" + fi +])dnl + +dnl APACHE_MPM_ENABLED(name) +AC_DEFUN(APACHE_MPM_ENABLED,[ + if ap_mpm_is_enabled $1; then + : + else + eval "ap_enabled_mpm_$1=yes" + ap_enabled_mpms="$ap_enabled_mpms $1 " + fi +])dnl + +ap_mpm_is_supported () +{ + eval "tmp=\$ap_supported_mpm_$1" + if test -z "$tmp"; then return 1 + else + return 0 fi } -ap_mpm_is_experimental () +ap_mpm_supports_shared () { - if test "$apache_cv_mpm" = "event" ; then + eval "tmp=\$ap_supported_mpm_$1" + if test "$tmp" = "shared"; 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 +ap_mpm_is_threaded () +{ + if test "$mpm_build" = "shared" -a ac_cv_define_APR_HAS_THREADS = "yes"; then + return 0 + fi -APACHE_SUBST(MPM_NAME) -APACHE_SUBST(MPM_SUBDIR_NAME) -MODLIST="$MODLIST mpm_${MPM_NAME}" + for mpm in $ap_enabled_mpms; do + eval "tmp=\$ap_threaded_mpm_$mpm" + if test "$tmp" = "yes"; then + return 0 + fi + done + return 1 +} +ap_mpm_is_enabled () +{ + eval "tmp=\$ap_enabled_mpm_$1" + if test "$tmp" = "yes"; then + return 0 + else + return 1 + fi +} diff --git a/server/mpm/config2.m4 b/server/mpm/config2.m4 new file mode 100644 index 00000000..d7e73ec0 --- /dev/null +++ b/server/mpm/config2.m4 @@ -0,0 +1,89 @@ +AC_MSG_CHECKING(which MPM to use by default) +AC_ARG_WITH(mpm, +APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use by default. + MPM={event|worker|prefork|winnt} + This will be statically linked as the only available MPM unless + --enable-mpms-shared is also specified. +),[ + default_mpm=$withval + AC_MSG_RESULT($withval); +],[ + dnl Order of preference for default MPM: + dnl The Windows and OS/2 MPMs are used on those platforms. + dnl Everywhere else: event, worker, prefork + if ap_mpm_is_supported "winnt"; then + default_mpm=winnt + AC_MSG_RESULT(winnt) + elif ap_mpm_is_supported "mpmt_os2"; then + default_mpm=mpmt_os2 + AC_MSG_RESULT(mpmt_os2) + elif ap_mpm_is_supported "event"; then + default_mpm=event + AC_MSG_RESULT(event) + elif ap_mpm_is_supported "worker"; then + default_mpm=worker + AC_MSG_RESULT(worker - event is not supported) + else + default_mpm=prefork + AC_MSG_RESULT(prefork - event and worker are not supported) + fi +]) + +APACHE_MPM_ENABLED($default_mpm) + +AC_ARG_ENABLE(mpms-shared, +APACHE_HELP_STRING(--enable-mpms-shared=MPM-LIST,Space-separated list of MPM modules to enable for dynamic loading. MPM-LIST=list | "all"),[ + if test "$enableval" = "no"; then + mpm_build=static + else + mpm_build=shared +dnl Build just the default MPM if --enable-mpms-shared has no argument. + if test "$enableval" = "yes"; then + enableval=$default_mpm + fi + for i in $enableval; do + if test "$i" = "all"; then + for j in $ap_supported_shared_mpms; do + eval "enable_mpm_$j=shared" + APACHE_MPM_ENABLED($j) + done + else + i=`echo $i | sed 's/-/_/g'` + if ap_mpm_supports_shared $i; then + eval "enable_mpm_$i=shared" + APACHE_MPM_ENABLED($i) + else + AC_MSG_ERROR([MPM $i does not support dynamic loading.]) + fi + fi + done + fi +], [mpm_build=static]) + +for i in $ap_enabled_mpms; do + if ap_mpm_is_supported $i; then + : + else + AC_MSG_ERROR([MPM $i is not supported on this platform.]) + fi +done + +if test $mpm_build = "shared"; then + eval "tmp=\$enable_mpm_$default_mpm" + if test "$tmp" != "shared"; then + AC_MSG_ERROR([The default MPM ($default_mpm) must be included in --enable-mpms-shared. Use --with-mpm to change the default MPM.]) + fi +fi + +APACHE_FAST_OUTPUT(server/mpm/Makefile) + +if test $mpm_build = "shared"; then + MPM_LIB="" +else + MPM_LIB=server/mpm/$default_mpm/lib${default_mpm}.la + MODLIST="$MODLIST mpm_${default_mpm}" +fi + +MPM_SUBDIRS=$ap_enabled_mpms +APACHE_SUBST(MPM_SUBDIRS) +APACHE_SUBST(MPM_LIB) diff --git a/server/mpm/event/Makefile.in b/server/mpm/event/Makefile.in new file mode 100644 index 00000000..f34af9cb --- /dev/null +++ b/server/mpm/event/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/event/config.m4 b/server/mpm/event/config.m4 new file mode 100644 index 00000000..351f1acf --- /dev/null +++ b/server/mpm/event/config.m4 @@ -0,0 +1,13 @@ +AC_MSG_CHECKING(if event MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +elif test $ac_cv_define_APR_HAS_THREADS != yes; then + AC_MSG_RESULT(no - APR does not support threads) +elif test $have_threaded_sig_graceful != yes; then + AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM) +elif test $ac_cv_have_threadsafe_pollset != yes; then + AC_MSG_RESULT(no - APR_POLLSET_THREADSAFE is not supported) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(event, yes, yes) +fi diff --git a/server/mpm/event/config3.m4 b/server/mpm/event/config3.m4 new file mode 100644 index 00000000..e7ec6320 --- /dev/null +++ b/server/mpm/event/config3.m4 @@ -0,0 +1,7 @@ +dnl ## XXX - Need a more thorough check of the proper flags to use + +APACHE_SUBST(MOD_MPM_EVENT_LDADD) + +APACHE_MPM_MODULE(event, $enable_mpm_event, event.lo fdqueue.lo pod.lo,[ + AC_CHECK_FUNCS(pthread_kill) +], , [\$(MOD_MPM_EVENT_LDADD)]) diff --git a/server/mpm/experimental/event/event.c b/server/mpm/event/event.c index bbee899e..b7f1d874 100644 --- a/server/mpm/experimental/event/event.c +++ b/server/mpm/event/event.c @@ -25,7 +25,7 @@ * 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 + * The MPM assumes the underlying apr_pollset implementation 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. @@ -51,12 +51,13 @@ #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" +#include "apr_atomic.h" #define APR_WANT_STRFUNC #include "apr_want.h" +#include "apr_version.h" #if APR_HAVE_UNISTD_H #include <unistd.h> @@ -75,8 +76,6 @@ #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" @@ -92,10 +91,12 @@ #include "fdqueue.h" #include "mpm_default.h" #include "http_vhost.h" +#include "unixd.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. @@ -136,36 +137,117 @@ #define MAX_THREAD_LIMIT 100000 #endif +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) + +#if !APR_VERSION_AT_LEAST(1,4,0) +#define apr_time_from_msec(x) (x * 1000) +#endif + +#ifndef MAX_SECS_TO_LINGER +#define MAX_SECS_TO_LINGER 30 +#endif +#define SECONDS_TO_LINGER 2 + /* * Actual definitions of config globals */ -int ap_threads_per_child = 0; /* Worker threads per child */ +#ifndef DEFAULT_WORKER_FACTOR +#define DEFAULT_WORKER_FACTOR 2 +#endif +#define WORKER_FACTOR_SCALE 16 /* scale factor to allow fractional values */ +static unsigned int worker_factor = DEFAULT_WORKER_FACTOR * WORKER_FACTOR_SCALE; + +static int 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 max_workers = 0; +static int server_limit = 0; +static int thread_limit = 0; 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 apr_uint32_t connection_count = 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_thread_mutex_t *timeout_mutex; +struct event_conn_state_t { + /** APR_RING of expiration timeouts */ + APR_RING_ENTRY(event_conn_state_t) timeout_list; + /** the expiration time of the next keepalive timeout */ + apr_time_t expiration_time; + /** connection record this struct refers to */ + conn_rec *c; + /** memory pool to allocate from */ + apr_pool_t *p; + /** bucket allocator */ + apr_bucket_alloc_t *bucket_alloc; + /** poll file descriptor information */ + apr_pollfd_t pfd; + /** public parts of the connection state */ + conn_state_t pub; +}; +APR_RING_HEAD(timeout_head_t, event_conn_state_t); + +struct timeout_queue { + struct timeout_head_t head; + int count; + const char *tag; +}; +/* + * Several timeout queues that use different timeouts, so that we always can + * simply append to the end. + * write_completion_q uses TimeOut + * keepalive_q uses KeepAliveTimeOut + * linger_q uses MAX_SECS_TO_LINGER + * short_linger_q uses SECONDS_TO_LINGER + */ +static struct timeout_queue write_completion_q, keepalive_q, linger_q, + short_linger_q; +static apr_pollfd_t *listener_pollfd; + +/* + * Macros for accessing struct timeout_queue. + * For TO_QUEUE_APPEND and TO_QUEUE_REMOVE, timeout_mutex must be held. + */ +#define TO_QUEUE_APPEND(q, el) \ + do { \ + APR_RING_INSERT_TAIL(&(q).head, el, event_conn_state_t, timeout_list); \ + (q).count++; \ + } while (0) + +#define TO_QUEUE_REMOVE(q, el) \ + do { \ + APR_RING_REMOVE(el, timeout_list); \ + (q).count--; \ + } while (0) + +#define TO_QUEUE_INIT(q) \ + do { \ + APR_RING_INIT(&(q).head, event_conn_state_t, timeout_list); \ + (q).tag = #q; \ + } while (0) + +#define TO_QUEUE_ELEM_INIT(el) APR_RING_ELEM_INIT(el, timeout_list) + +/* + * The pollset for sockets that are in any of the timeout queues. Currently + * we use the timeout_mutex to make sure that connections are added/removed + * atomically to/from both event_pollset and a timeout queue. Otherwise + * some confusion can happen under high load if timeout queues and pollset + * get out of sync. + * XXX: It should be possible to make the lock unnecessary in many or even all + * XXX: cases. + */ static apr_pollset_t *event_pollset; /* The structure used to pass unique initialization info to each thread */ @@ -196,27 +278,47 @@ typedef enum 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. +/* data retained by event across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook */ -int ap_max_daemons_limit = -1; - -static ap_pod_t *pod; +typedef struct event_retained_data { + int first_server_limit; + int first_thread_limit; + int module_loads; + int sick_child_detected; + ap_generation_t my_generation; + int volatile is_graceful; /* set from signal handler */ + int maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. + * We use this value to optimize routines that have to scan the entire + * scoreboard. + */ + int max_daemons_limit; + /* + * 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. + */ + int idle_spawn_rate; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif + int hold_off_on_exponential_spawning; +} event_retained_data; +static event_retained_data *retained; -/* *Non*-shared http_main globals... */ +#define ID_FROM_CHILD_THREAD(c, t) ((c * thread_limit) + t) -server_rec *ap_server_conf; +static ap_event_pod_t *pod; -/* The worker MPM respects a couple of runtime flags that can aid +/* The event 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 @@ -256,10 +358,36 @@ static apr_os_thread_t *listener_os_thread; */ static apr_socket_t **worker_sockets; +static void disable_listensocks(int process_slot) +{ + int i; + for (i = 0; i < num_listensocks; i++) { + apr_pollset_remove(event_pollset, &listener_pollfd[i]); + } + ap_scoreboard_image->parent[process_slot].not_accepting = 1; +} + +static void enable_listensocks(int process_slot) +{ + int i; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00457) + "Accepting new connections again: " + "%u active conns, %u idle workers", + apr_atomic_read32(&connection_count), + ap_queue_info_get_idlers(worker_queue_info)); + for (i = 0; i < num_listensocks; i++) + apr_pollset_add(event_pollset, &listener_pollfd[i]); + /* + * XXX: This is not yet optimal. If many workers suddenly become available, + * XXX: the parent may kill some processes off too soon. + */ + ap_scoreboard_image->parent[process_slot].not_accepting = 0; +} + static void close_worker_sockets(void) { int i; - for (i = 0; i < ap_threads_per_child; i++) { + for (i = 0; i < threads_per_child; i++) { if (worker_sockets[i]) { apr_socket_close(worker_sockets[i]); worker_sockets[i] = NULL; @@ -278,6 +406,10 @@ static void wakeup_listener(void) */ return; } + + /* unblock the listener if it's waiting for a worker */ + ap_queue_info_term(worker_queue_info); + /* * 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 @@ -316,58 +448,112 @@ static void signal_threads(int mode) 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) +static int event_query(int query_code, int *result, apr_status_t *rv) { + *rv = APR_SUCCESS; switch (query_code) { case AP_MPMQ_MAX_DAEMON_USED: - *result = ap_max_daemons_limit; - return APR_SUCCESS; + *result = retained->max_daemons_limit; + break; case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_STATIC; - return APR_SUCCESS; + break; case AP_MPMQ_IS_FORKED: *result = AP_MPMQ_DYNAMIC; - return APR_SUCCESS; + break; case AP_MPMQ_IS_ASYNC: *result = 1; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_DAEMONS: *result = server_limit; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_THREADS: *result = thread_limit; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_THREADS: - *result = ap_threads_per_child; - return APR_SUCCESS; + *result = threads_per_child; + break; case AP_MPMQ_MIN_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_THREADS: *result = min_spare_threads; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_THREADS: *result = max_spare_threads; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_REQUESTS_DAEMON: *result = ap_max_requests_per_child; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_DAEMONS: *result = ap_daemons_limit; - return APR_SUCCESS; + break; case AP_MPMQ_MPM_STATE: *result = mpm_state; - return APR_SUCCESS; + break; + case AP_MPMQ_GENERATION: + *result = retained->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; } - return APR_ENOTIMPL; + return OK; +} + +static void event_note_child_killed(int childnum, pid_t pid, ap_generation_t gen) +{ + if (childnum != -1) { /* child had a scoreboard slot? */ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[childnum].pid, + ap_scoreboard_image->parent[childnum].generation, + childnum, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[childnum].pid = 0; + } + else { + ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED); + } +} + +static void event_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + retained->my_generation, slot, MPM_CHILD_STARTED); +} + +static void event_note_child_lost_slot(int slot, pid_t newpid) +{ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00458) + "pid %" APR_PID_T_FMT " taking over scoreboard slot from " + "%" APR_PID_T_FMT "%s", + newpid, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].quiescing ? + " (quiescing)" : ""); + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_LOST_SLOT); + /* 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_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation); +} + +static const char *event_get_name(void) +{ + return "event"; } /* a clean exit from a child with proper cleanup */ @@ -378,6 +564,11 @@ static void clean_child_exit(int code) if (pchild) { apr_pool_destroy(pchild); } + + if (one_process) { + event_note_child_killed(/* slot */ 0, 0, 0); + } + exit(code); } @@ -390,12 +581,16 @@ static void just_die(int sig) * Connection structures and accounting... */ -/* volatile just in case */ +static int child_fatal; + +/* volatile because they're updated from a signal handler */ 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; + +static apr_status_t decrement_connection_count(void *dummy) { + apr_atomic_dec32(&connection_count); + return APR_SUCCESS; +} /* * ap_start_shutdown() and ap_start_restart(), below, are a first stab at @@ -427,7 +622,7 @@ static void ap_start_shutdown(int graceful) return; } shutdown_pending = 1; - is_graceful = graceful; + retained->is_graceful = graceful; } /* do a graceful restart if graceful == 1 */ @@ -439,7 +634,7 @@ static void ap_start_restart(int graceful) return; } restart_pending = 1; - is_graceful = graceful; + retained->is_graceful = graceful; } static void sig_term(int sig) @@ -468,34 +663,37 @@ static void set_signals(void) sa.sa_handler = sig_term; if (sigaction(SIGTERM, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00459) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00460) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00461) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00462) "sigaction(SIGXCPU)"); #endif #ifdef SIGXFSZ - sa.sa_handler = SIG_DFL; + /* For systems following the LFS standard, ignoring SIGXFSZ allows + * a write() beyond the 2GB limit to fail gracefully with E2BIG + * rather than terminate the process. */ + sa.sa_handler = SIG_IGN; if (sigaction(SIGXFSZ, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00463) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00464) "sigaction(SIGPIPE)"); #endif @@ -505,10 +703,10 @@ static void set_signals(void) 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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00465) "sigaction(SIGHUP)"); if (sigaction(AP_SIG_GRACEFUL, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00466) "sigaction(" AP_SIG_GRACEFUL_STRING ")"); #else if (!one_process) { @@ -516,7 +714,7 @@ static void set_signals(void) apr_signal(SIGXCPU, SIG_DFL); #endif /* SIGXCPU */ #ifdef SIGXFSZ - apr_signal(SIGXFSZ, SIG_DFL); + apr_signal(SIGXFSZ, SIG_IGN); #endif /* SIGXFSZ */ } @@ -537,68 +735,140 @@ static void set_signals(void) #endif } -/***************************************************************** - * Here follows a long bunch of generic server bookkeeping stuff... +/* + * close our side of the connection + * Pre-condition: cs is not in any timeout queue and not in the pollset, + * timeout_mutex is not locked + * return: 0 if connection is fully closed, + * 1 if connection is lingering + * may be called by listener or by worker thread */ - -int ap_graceful_stop_signalled(void) - /* XXX this is really a bad confusing obsolete name - * maybe it should be ap_mpm_process_exiting? - */ +static int start_lingering_close(event_conn_state_t *cs) { - /* note: for a graceful termination, listener_may_exit will be set before - * workers_may_exit, so check listener_may_exit - */ - return listener_may_exit; + apr_status_t rv; + + cs->c->sbh = NULL; /* prevent scoreboard updates from the listener + * worker will loop around and set SERVER_READY soon + */ + + if (ap_start_lingering_close(cs->c)) { + apr_pool_clear(cs->p); + ap_push_pool(worker_queue_info, cs->p); + return 0; + } + else { + apr_socket_t *csd = ap_get_conn_socket(cs->c); + struct timeout_queue *q; + + rv = apr_socket_timeout_set(csd, 0); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + /* + * If some module requested a shortened waiting period, only wait for + * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain + * DoS attacks. + */ + if (apr_table_get(cs->c->notes, "short-lingering-close")) { + cs->expiration_time = + apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER); + q = &short_linger_q; + cs->pub.state = CONN_STATE_LINGER_SHORT; + } + else { + cs->expiration_time = + apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER); + q = &linger_q; + cs->pub.state = CONN_STATE_LINGER_NORMAL; + } + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(*q, cs); + cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR; + rv = apr_pollset_add(event_pollset, &cs->pfd); + apr_thread_mutex_unlock(timeout_mutex); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "start_lingering_close: apr_pollset_add failure"); + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_REMOVE(*q, cs); + apr_thread_mutex_unlock(timeout_mutex); + apr_socket_close(cs->pfd.desc.s); + apr_pool_clear(cs->p); + ap_push_pool(worker_queue_info, cs->p); + return 0; + } + } + return 1; } -/***************************************************************** - * Child process main loop. +/* + * forcibly close a lingering connection after the lingering period has + * expired + * Pre-condition: cs is not in any timeout queue and not in the pollset + * return: irrelevant (need same prototype as start_lingering_close) */ +static int stop_lingering_close(event_conn_state_t *cs) +{ + apr_status_t rv; + apr_socket_t *csd = ap_get_conn_socket(cs->c); + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "socket reached timeout in lingering-close state"); + rv = apr_socket_close(csd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00468) "error closing socket"); + AP_DEBUG_ASSERT(0); + } + apr_pool_clear(cs->p); + ap_push_pool(worker_queue_info, cs->p); + return 0; +} -static int process_socket(apr_pool_t * p, apr_socket_t * sock, - conn_state_t * cs, int my_child_num, +/* + * process one connection in the worker + * return: 1 if the connection has been completed, + * 0 if it is still open and waiting for some event + */ +static int process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock, + event_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)); - + listener_poll_type *pt = apr_pcalloc(p, sizeof(*pt)); + cs = apr_pcalloc(p, sizeof(event_conn_state_t)); cs->bucket_alloc = apr_bucket_alloc_create(p); c = ap_run_create_connection(p, ap_server_conf, sock, conn_id, sbh, cs->bucket_alloc); + if (!c) { + apr_bucket_alloc_destroy(cs->bucket_alloc); + apr_pool_clear(p); + ap_push_pool(worker_queue_info, p); + return 1; + } + apr_atomic_inc32(&connection_count); + apr_pool_cleanup_register(c->pool, NULL, decrement_connection_count, apr_pool_cleanup_null); + c->current_thread = thd; cs->c = c; - c->cs = cs; + c->cs = &(cs->pub); 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; + TO_QUEUE_ELEM_INIT(cs); 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"); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(00469) + "process_socket: connection aborted"); c->aborted = 1; } @@ -615,12 +885,13 @@ static int process_socket(apr_pool_t * p, apr_socket_t * sock, * 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; + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; } else { c = cs->c; c->sbh = sbh; + c->current_thread = thd; } if (c->clogging_input_filters && !c->aborted) { @@ -629,10 +900,13 @@ static int process_socket(apr_pool_t * p, apr_socket_t * sock, * like the Worker MPM does. */ ap_run_process_connection(c); - cs->state = CONN_STATE_LINGER; + if (cs->pub.state != CONN_STATE_SUSPENDED) { + cs->pub.state = CONN_STATE_LINGER; + } } - if (cs->state == CONN_STATE_READ_REQUEST_LINE) { +read_request: + if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE) { if (!c->aborted) { ap_run_process_connection(c); @@ -642,19 +916,55 @@ static int process_socket(apr_pool_t * p, apr_socket_t * sock, */ } else { - cs->state = CONN_STATE_LINGER; + cs->pub.state = CONN_STATE_LINGER; } } - if (cs->state == CONN_STATE_LINGER) { - ap_lingering_close(c); - apr_pool_clear(p); - ap_push_pool(worker_queue_info, p); - return 0; + if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) { + ap_filter_t *output_filter = c->output_filters; + apr_status_t rv; + ap_update_child_status_from_conn(sbh, SERVER_BUSY_WRITE, c); + while (output_filter->next != NULL) { + output_filter = output_filter->next; + } + rv = output_filter->frec->filter_func.out_func(output_filter, NULL); + if (rv != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00470) + "network write failure in core output filter"); + cs->pub.state = CONN_STATE_LINGER; + } + else if (c->data_in_output_filters) { + /* Still in WRITE_COMPLETION_STATE: + * Set a write timeout for this connection, and let the + * event thread poll for writeability. + */ + cs->expiration_time = ap_server_conf->timeout + apr_time_now(); + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_APPEND(write_completion_q, cs); + cs->pfd.reqevents = APR_POLLOUT | APR_POLLHUP | APR_POLLERR; + rc = apr_pollset_add(event_pollset, &cs->pfd); + apr_thread_mutex_unlock(timeout_mutex); + return 1; + } + else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted || + listener_may_exit) { + cs->pub.state = CONN_STATE_LINGER; + } + else if (c->data_in_input_filters) { + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + goto read_request; + } + else { + cs->pub.state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; + } } - else if (cs->state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { + + if (cs->pub.state == CONN_STATE_LINGER) { + if (!start_lingering_close(cs)) + return 0; + } + else if (cs->pub.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 @@ -664,14 +974,14 @@ static int process_socket(apr_pool_t * p, apr_socket_t * sock, * 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; + cs->expiration_time = ap_server_conf->keep_alive_timeout + + apr_time_now(); apr_thread_mutex_lock(timeout_mutex); - APR_RING_INSERT_TAIL(&timeout_head, cs, conn_state_t, timeout_list); + TO_QUEUE_APPEND(keepalive_q, cs); - pt->status = 0; - /* Add work to pollset. These are always read events */ + /* Add work to pollset. */ + cs->pfd.reqevents = APR_POLLIN; rc = apr_pollset_add(event_pollset, &cs->pfd); - apr_thread_mutex_unlock(timeout_mutex); if (rc != APR_SUCCESS) { @@ -684,7 +994,7 @@ static int process_socket(apr_pool_t * p, apr_socket_t * sock, } /* 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 + "MaxConnectionsPerChild 0", and keep going in that case. Doing it this way simplifies the hot path in worker_thread */ static void check_infinite_requests(void) { @@ -692,21 +1002,27 @@ static void check_infinite_requests(void) 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 close_listeners(int process_slot, int *closed) { + if (!*closed) { + int i; + disable_listensocks(process_slot); + ap_close_listeners(); + *closed = 1; + dying = 1; + ap_scoreboard_image->parent[process_slot].quiescing = 1; + for (i = 0; i < threads_per_child; ++i) { + ap_update_child_status_from_indexes(process_slot, i, + SERVER_GRACEFUL, NULL); + } + /* wake up the main thread */ + kill(ap_my_pid, SIGTERM); + } +} + static void unblock_signal(int sig) { sigset_t sig_mask; @@ -727,31 +1043,58 @@ static void dummy_signal_handler(int sig) */ } -static apr_status_t push2worker(const apr_pollfd_t * pfd, - apr_pollset_t * pollset) + +static apr_status_t init_pollset(apr_pool_t *p) { - listener_poll_type *pt = (listener_poll_type *) pfd->client_data; - conn_state_t *cs = (conn_state_t *) pt->baton; - apr_status_t rc; + ap_listen_rec *lr; + listener_poll_type *pt; + int i = 0; + + TO_QUEUE_INIT(write_completion_q); + TO_QUEUE_INIT(keepalive_q); + TO_QUEUE_INIT(linger_q); + TO_QUEUE_INIT(short_linger_q); + + listener_pollfd = apr_palloc(p, sizeof(apr_pollfd_t) * num_listensocks); + for (lr = ap_listeners; lr != NULL; lr = lr->next, i++) { + apr_pollfd_t *pfd; + AP_DEBUG_ASSERT(i < num_listensocks); + pfd = &listener_pollfd[i]; + pt = apr_pcalloc(p, sizeof(*pt)); + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = lr->sd; + pfd->reqevents = APR_POLLIN; - if (pt->status == 1) { - return 0; - } + pt->type = PT_ACCEPT; + pt->baton = lr; - pt->status = 1; + pfd->client_data = pt; - rc = apr_pollset_remove(pollset, pfd); + apr_socket_opt_set(pfd->desc.s, APR_SO_NONBLOCK, 1); + apr_pollset_add(event_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; + lr->accept_func = ap_unixd_accept; } + return APR_SUCCESS; +} + +static apr_status_t push_timer2worker(timer_event_t* te) +{ + return ap_queue_push_timer(worker_queue, te); +} + +/* + * Pre-condition: pfd->cs is neither in pollset nor timeout queue + * this function may only be called by the listener + */ +static apr_status_t push2worker(const apr_pollfd_t * pfd, + apr_pollset_t * pollset) +{ + listener_poll_type *pt = (listener_poll_type *) pfd->client_data; + event_conn_state_t *cs = (event_conn_state_t *) pt->baton; + apr_status_t rc; + 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 @@ -760,50 +1103,200 @@ static apr_status_t push2worker(const apr_pollfd_t * pfd, 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"); + ap_server_conf, APLOGNO(00471) "push2worker: ap_queue_push failed"); apr_pool_clear(cs->p); ap_push_pool(worker_queue_info, cs->p); } - return APR_SUCCESS; + return rc; } /* 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. + * If *have_idle_worker_p == 0, reserve a worker thread, and set + * *have_idle_worker_p = 1. + * If *have_idle_worker_p is already 1, will do nothing. + * If blocking == 1, block if all workers are currently busy. + * If no worker was available immediately, will set *all_busy to 1. + * XXX: If there are no workers, we should not block immediately but + * XXX: close all keep-alive connections first. */ -static int get_worker(int *have_idle_worker_p) +static void get_worker(int *have_idle_worker_p, int blocking, int *all_busy) { 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 { + if (*have_idle_worker_p) { /* already reserved a worker thread - must have hit a * transient error on a previous pass */ - return 1; + return; + } + + if (blocking) + rc = ap_queue_info_wait_for_idler(worker_queue_info, all_busy); + else + rc = ap_queue_info_try_get_idler(worker_queue_info); + + if (rc == APR_SUCCESS) { + *have_idle_worker_p = 1; + } + else if (!blocking && rc == APR_EAGAIN) { + *all_busy = 1; + } + else if (!APR_STATUS_IS_EOF(rc)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, APLOGNO(00472) + "ap_queue_info_wait_for_idler failed. " + "Attempting to shutdown process gracefully"); + signal_threads(ST_GRACEFUL); + } +} + +/* XXXXXX: Convert to skiplist or other better data structure + * (yes, this is VERY VERY VERY VERY BAD) + */ + +/* Structures to reuse */ +static APR_RING_HEAD(timer_free_ring_t, timer_event_t) timer_free_ring; +/* Active timers */ +static APR_RING_HEAD(timer_ring_t, timer_event_t) timer_ring; + +static apr_thread_mutex_t *g_timer_ring_mtx; + +static apr_status_t event_register_timed_callback(apr_time_t t, + ap_mpm_callback_fn_t *cbfn, + void *baton) +{ + int inserted = 0; + timer_event_t *ep; + timer_event_t *te; + /* oh yeah, and make locking smarter/fine grained. */ + apr_thread_mutex_lock(g_timer_ring_mtx); + + if (!APR_RING_EMPTY(&timer_free_ring, timer_event_t, link)) { + te = APR_RING_FIRST(&timer_free_ring); + APR_RING_REMOVE(te, link); } + else { + /* XXXXX: lol, pool allocation without a context from any thread.Yeah. Right. MPMs Suck. */ + te = ap_malloc(sizeof(timer_event_t)); + APR_RING_ELEM_INIT(te, link); + } + + te->cbfunc = cbfn; + te->baton = baton; + /* XXXXX: optimize */ + te->when = t + apr_time_now(); + + /* Okay, insert sorted by when.. */ + for (ep = APR_RING_FIRST(&timer_ring); + ep != APR_RING_SENTINEL(&timer_ring, + timer_event_t, link); + ep = APR_RING_NEXT(ep, link)) + { + if (ep->when > te->when) { + inserted = 1; + APR_RING_INSERT_BEFORE(ep, te, link); + break; + } + } + + if (!inserted) { + APR_RING_INSERT_TAIL(&timer_ring, te, timer_event_t, link); + } + + apr_thread_mutex_unlock(g_timer_ring_mtx); + + return APR_SUCCESS; } -static void *listener_thread(apr_thread_t * thd, void *dummy) +/* + * Close socket and clean up if remote closed its end while we were in + * lingering close. + * Only to be called in the listener thread; + * Pre-condition: cs is in one of the linger queues and in the pollset + */ +static void process_lingering_close(event_conn_state_t *cs, const apr_pollfd_t *pfd) +{ + apr_socket_t *csd = ap_get_conn_socket(cs->c); + char dummybuf[2048]; + apr_size_t nbytes; + apr_status_t rv; + struct timeout_queue *q; + q = (cs->pub.state == CONN_STATE_LINGER_SHORT) ? &short_linger_q : &linger_q; + + /* socket is already in non-blocking state */ + do { + nbytes = sizeof(dummybuf); + rv = apr_socket_recv(csd, dummybuf, &nbytes); + } while (rv == APR_SUCCESS); + + if (!APR_STATUS_IS_EOF(rv)) { + return; + } + + apr_thread_mutex_lock(timeout_mutex); + rv = apr_pollset_remove(event_pollset, pfd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + rv = apr_socket_close(csd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + TO_QUEUE_REMOVE(*q, cs); + apr_thread_mutex_unlock(timeout_mutex); + TO_QUEUE_ELEM_INIT(cs); + + apr_pool_clear(cs->p); + ap_push_pool(worker_queue_info, cs->p); +} + +/* call 'func' for all elements of 'q' with timeout less than 'timeout_time'. + * Pre-condition: timeout_mutex must already be locked + * Post-condition: timeout_mutex will be locked again + */ +static void process_timeout_queue(struct timeout_queue *q, + apr_time_t timeout_time, + int (*func)(event_conn_state_t *)) { + int count = 0; + event_conn_state_t *first, *cs, *last; + apr_status_t rv; + if (!q->count) { + return; + } + AP_DEBUG_ASSERT(!APR_RING_EMPTY(&q->head, event_conn_state_t, timeout_list)); + + cs = first = APR_RING_FIRST(&q->head); + while (cs != APR_RING_SENTINEL(&q->head, event_conn_state_t, timeout_list) + && cs->expiration_time < timeout_time) { + last = cs; + rv = apr_pollset_remove(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS && !APR_STATUS_IS_NOTFOUND(rv)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, cs->c, APLOGNO(00473) + "apr_pollset_remove failed"); + } + cs = APR_RING_NEXT(cs, timeout_list); + count++; + } + if (!count) + return; + + APR_RING_UNSPLICE(first, last, timeout_list); + AP_DEBUG_ASSERT(q->count >= count); + q->count -= count; + apr_thread_mutex_unlock(timeout_mutex); + while (count) { + cs = APR_RING_NEXT(first, timeout_list); + TO_QUEUE_ELEM_INIT(first); + func(first); + first = cs; + count--; + } + apr_thread_mutex_lock(timeout_mutex); +} + +static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) +{ + timer_event_t *ep; + timer_event_t *te; apr_status_t rc; proc_info *ti = dummy; int process_slot = ti->pid; @@ -812,206 +1305,275 @@ static void *listener_thread(apr_thread_t * thd, void *dummy) apr_pool_t *ptrans; /* Pool for per-transaction stuff */ ap_listen_rec *lr; int have_idle_worker = 0; - conn_state_t *cs; + event_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; + apr_time_t timeout_time = 0, now, last_log; listener_poll_type *pt; + int closed = 0, listeners_disabled = 0; + last_log = apr_time_now(); 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 +#define EVENT_FUDGE_FACTOR 10000 - /* 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); + rc = init_pollset(tpool); 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"); + "failed to initialize pollset, " + "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) { + for (;;) { + int workers_were_busy = 0; + if (listener_may_exit) { + close_listeners(process_slot, &closed); + if (terminate_mode == ST_UNGRACEFUL + || apr_atomic_read32(&connection_count) == 0) + break; + } if (requests_this_child <= 0) { check_infinite_requests(); } - rc = apr_pollset_poll(event_pollset, timeout_interval, &num, - &out_pfd); + now = apr_time_now(); + if (APLOGtrace6(ap_server_conf)) { + /* trace log status every second */ + if (now - last_log > apr_time_from_msec(1000)) { + last_log = now; + apr_thread_mutex_lock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "connections: %d (write-completion: %d " + "keep-alive: %d lingering: %d)", + connection_count, write_completion_q.count, + keepalive_q.count, + linger_q.count + short_linger_q.count); + apr_thread_mutex_unlock(timeout_mutex); + } + } + apr_thread_mutex_lock(g_timer_ring_mtx); + if (!APR_RING_EMPTY(&timer_ring, timer_event_t, link)) { + te = APR_RING_FIRST(&timer_ring); + if (te->when > now) { + timeout_interval = te->when - now; + } + else { + timeout_interval = 1; + } + } + else { + timeout_interval = apr_time_from_msec(100); + } + apr_thread_mutex_unlock(g_timer_ring_mtx); + + 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, "apr_pollset_poll failed. Attempting to " "shutdown process gracefully"); signal_threads(ST_GRACEFUL); } } - if (listener_may_exit) - break; + if (listener_may_exit) { + close_listeners(process_slot, &closed); + if (terminate_mode == ST_UNGRACEFUL + || apr_atomic_read32(&connection_count) == 0) + break; + } - while (num && get_worker(&have_idle_worker)) { + now = apr_time_now(); + apr_thread_mutex_lock(g_timer_ring_mtx); + for (ep = APR_RING_FIRST(&timer_ring); + ep != APR_RING_SENTINEL(&timer_ring, + timer_event_t, link); + ep = APR_RING_FIRST(&timer_ring)) + { + if (ep->when < now + EVENT_FUDGE_FACTOR) { + APR_RING_REMOVE(ep, link); + push_timer2worker(ep); + } + else { + break; + } + } + apr_thread_mutex_unlock(g_timer_ring_mtx); + + while (num) { 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) { + struct timeout_queue *remove_from_q = &write_completion_q; + int blocking = 1; + cs = (event_conn_state_t *) pt->baton; + switch (cs->pub.state) { case CONN_STATE_CHECK_REQUEST_LINE_READABLE: - cs->state = CONN_STATE_READ_REQUEST_LINE; + cs->pub.state = CONN_STATE_READ_REQUEST_LINE; + remove_from_q = &keepalive_q; + /* don't wait for a worker for a keepalive request */ + blocking = 0; + /* FALL THROUGH */ + case CONN_STATE_WRITE_COMPLETION: + get_worker(&have_idle_worker, blocking, + &workers_were_busy); + apr_thread_mutex_lock(timeout_mutex); + TO_QUEUE_REMOVE(*remove_from_q, cs); + rc = apr_pollset_remove(event_pollset, &cs->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 && !APR_STATUS_IS_NOTFOUND(rc)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, + "pollset remove failed"); + apr_thread_mutex_unlock(timeout_mutex); + start_lingering_close(cs); + break; + } + + apr_thread_mutex_unlock(timeout_mutex); + TO_QUEUE_ELEM_INIT(cs); + /* If we didn't get a worker immediately for a keep-alive + * request, we close the connection, so that the client can + * re-connect to a different process. + */ + if (!have_idle_worker) { + start_lingering_close(cs); + break; + } + 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; + } + break; + case CONN_STATE_LINGER_NORMAL: + case CONN_STATE_LINGER_SHORT: + process_lingering_close(cs, out_pfd); break; default: - ap_log_error(APLOG_MARK, APLOG_ERR, rc, + ap_log_error(APLOG_MARK, APLOG_CRIT, 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; + cs->pub.state); + ap_assert(0); } } - else { + else if (pt->type == PT_ACCEPT) { /* 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; - } + if (workers_were_busy) { + if (!listeners_disabled) + disable_listensocks(process_slot); + listeners_disabled = 1; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "All workers busy, not accepting new conns" + "in this process"); } - else { - ptrans = recycled_pool; + else if (apr_atomic_read32(&connection_count) > threads_per_child + + ap_queue_info_get_idlers(worker_queue_info) * + worker_factor / WORKER_FACTOR_SCALE) + { + if (!listeners_disabled) + disable_listensocks(process_slot); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Too many open connections (%u), " + "not accepting new conns in this process", + apr_atomic_read32(&connection_count)); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "Idle workers: %u", + ap_queue_info_get_idlers(worker_queue_info)); + listeners_disabled = 1; + } + else if (listeners_disabled) { + listeners_disabled = 0; + enable_listensocks(process_slot); } + if (!listeners_disabled) { + lr = (ap_listen_rec *) pt->baton; + ap_pop_pool(&ptrans, worker_queue_info); - apr_pool_tag(ptrans, "transaction"); + if (ptrans == 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; + } + } + apr_pool_tag(ptrans, "transaction"); - rc = lr->accept_func(&csd, lr, ptrans); + get_worker(&have_idle_worker, 1, &workers_were_busy); + 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); + /* 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 (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); + 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 { - have_idle_worker = 0; + apr_pool_clear(ptrans); + ap_push_pool(worker_queue_info, ptrans); } } - else { - apr_pool_clear(ptrans); - ap_push_pool(worker_queue_info, ptrans); - } } /* if:else on pt->type */ out_pfd++; num--; @@ -1020,53 +1582,64 @@ static void *listener_thread(apr_thread_t * thd, void *dummy) /* 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) { - - cs->state = CONN_STATE_LINGER; + now = apr_time_now(); + /* we only do this once per 0.1s (TIMEOUT_FUDGE_FACTOR) */ + if (now > timeout_time) { + struct process_score *ps; + timeout_time = now + TIMEOUT_FUDGE_FACTOR; - APR_RING_REMOVE(cs, timeout_list); - apr_thread_mutex_unlock(timeout_mutex); + /* handle timed out sockets */ + apr_thread_mutex_lock(timeout_mutex); - if (!get_worker(&have_idle_worker)) { - apr_thread_mutex_lock(timeout_mutex); - APR_RING_INSERT_HEAD(&timeout_head, cs, - conn_state_t, timeout_list); - break; + /* Step 1: keepalive timeouts */ + /* If all workers are busy, we kill older keep-alive connections so that they + * may connect to another process. + */ + if (workers_were_busy && keepalive_q.count) { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "All workers are busy, will close %d keep-alive " + "connections", + keepalive_q.count); + process_timeout_queue(&keepalive_q, + timeout_time + ap_server_conf->keep_alive_timeout, + start_lingering_close); } - - 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 - */ + else { + process_timeout_queue(&keepalive_q, timeout_time, + start_lingering_close); } - have_idle_worker = 0; - apr_thread_mutex_lock(timeout_mutex); - cs = APR_RING_FIRST(&timeout_head); - } - apr_thread_mutex_unlock(timeout_mutex); + /* Step 2: write completion timeouts */ + process_timeout_queue(&write_completion_q, timeout_time, start_lingering_close); + /* Step 3: (normal) lingering close completion timeouts */ + process_timeout_queue(&linger_q, timeout_time, stop_lingering_close); + /* Step 4: (short) lingering close completion timeouts */ + process_timeout_queue(&short_linger_q, timeout_time, stop_lingering_close); + + ps = ap_get_scoreboard_process(process_slot); + ps->write_completion = write_completion_q.count; + ps->lingering_close = linger_q.count + short_linger_q.count; + ps->keep_alive = keepalive_q.count; + apr_thread_mutex_unlock(timeout_mutex); + ps->connections = apr_atomic_read32(&connection_count); + /* XXX: should count CONN_STATE_SUSPENDED and set ps->suspended */ + } + if (listeners_disabled && !workers_were_busy && + (int)apr_atomic_read32(&connection_count) < + ((int)ap_queue_info_get_idlers(worker_queue_info) - 1) * + worker_factor / WORKER_FACTOR_SCALE + threads_per_child) + { + listeners_disabled = 0; + enable_listensocks(process_slot); + } + /* + * XXX: do we need to set some timeout that re-enables the listensocks + * XXX: in case no other event occurs? + */ } /* listener main loop */ - ap_close_listeners(); + close_listeners(process_slot, &closed); 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; @@ -1085,15 +1658,17 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) int process_slot = ti->pid; int thread_slot = ti->tid; apr_socket_t *csd = NULL; - conn_state_t *cs; + event_conn_state_t *cs; apr_pool_t *ptrans; /* Pool for per-transaction stuff */ apr_status_t rv; int is_idle = 0; + timer_event_t *te = NULL; 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_scoreboard_image->servers[process_slot][thread_slot].tid = apr_os_thread_current(); + ap_scoreboard_image->servers[process_slot][thread_slot].generation = retained->my_generation; ap_update_child_status_from_indexes(process_slot, thread_slot, SERVER_STARTING, NULL); @@ -1111,12 +1686,14 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) } ap_update_child_status_from_indexes(process_slot, thread_slot, - SERVER_READY, NULL); + dying ? SERVER_GRACEFUL : SERVER_READY, NULL); worker_pop: if (workers_may_exit) { break; } - rv = ap_queue_pop(worker_queue, &csd, &cs, &ptrans); + + te = NULL; + rv = ap_queue_pop_something(worker_queue, &csd, &cs, &ptrans, &te); if (rv != APR_SUCCESS) { /* We get APR_EOF during a graceful shutdown once all the @@ -1146,17 +1723,28 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) } continue; } - is_idle = 0; - worker_sockets[thread_slot] = csd; - rv = process_socket(ptrans, csd, cs, process_slot, thread_slot); - if (!rv) { - requests_this_child--; + if (te != NULL) { + te->cbfunc(te->baton); + + { + apr_thread_mutex_lock(g_timer_ring_mtx); + APR_RING_INSERT_TAIL(&timer_free_ring, te, timer_event_t, link); + apr_thread_mutex_unlock(g_timer_ring_mtx); + } + } + else { + is_idle = 0; + worker_sockets[thread_slot] = csd; + rv = process_socket(thd, ptrans, csd, cs, process_slot, thread_slot); + if (!rv) { + requests_this_child--; + } + worker_sockets[thread_slot] = NULL; } - worker_sockets[thread_slot] = NULL; } ap_update_child_status_from_indexes(process_slot, thread_slot, - (dying) ? SERVER_DEAD : + dying ? SERVER_DEAD : SERVER_GRACEFUL, (request_rec *) NULL); @@ -1183,14 +1771,14 @@ static void create_listener_thread(thread_starter * ts) proc_info *my_info; apr_status_t rv; - my_info = (proc_info *) malloc(sizeof(proc_info)); + my_info = (proc_info *) ap_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, + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00474) "apr_thread_create: unable to create listener thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); @@ -1217,32 +1805,64 @@ static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) int listener_started = 0; int loops; int prev_threads_created; + int max_recycled_pools = -1; /* 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); + rv = ap_queue_init(worker_queue, 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); } + if (ap_max_mem_free != APR_ALLOCATOR_MAX_FREE_UNLIMITED) { + /* If we want to conserve memory, let's not keep an unlimited number of + * pools & allocators. + * XXX: This should probably be a separate config directive + */ + max_recycled_pools = threads_per_child * 3 / 4 ; + } rv = ap_queue_info_create(&worker_queue_info, pchild, - ap_threads_per_child); + threads_per_child, max_recycled_pools); 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 + /* Create the timeout mutex and main pollset before the listener + * thread starts. + */ + rv = apr_thread_mutex_create(&timeout_mutex, APR_THREAD_MUTEX_DEFAULT, + pchild); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "creation of the timeout mutex failed."); + clean_child_exit(APEXIT_CHILDFATAL); + } + + /* Create the main pollset */ + rv = apr_pollset_create(&event_pollset, + threads_per_child, /* XXX don't we need more, to handle + * connections in K-A or lingering + * close? + */ + pchild, APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "apr_pollset_create with Thread Safety failed."); + clean_child_exit(APEXIT_CHILDFATAL); + } + + worker_sockets = apr_pcalloc(pchild, 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++) { + /* threads_per_child does not include the listener thread */ + for (i = 0; i < threads_per_child; i++) { int status = ap_scoreboard_image->servers[child_num_arg][i].status; @@ -1250,12 +1870,7 @@ static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) 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 = (proc_info *) ap_malloc(sizeof(proc_info)); my_info->pid = my_child_num; my_info->tid = i; my_info->sd = 0; @@ -1284,7 +1899,7 @@ static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) } - if (start_thread_may_exit || threads_created == ap_threads_per_child) { + if (start_thread_may_exit || threads_created == threads_per_child) { break; } /* wait for previous generation to clean up an entry */ @@ -1296,7 +1911,7 @@ static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) "child %" APR_PID_T_FMT " isn't taking over " "slots very quickly (%d of %d)", ap_my_pid, threads_created, - ap_threads_per_child); + threads_per_child); } prev_threads_created = threads_created; } @@ -1333,36 +1948,30 @@ static void join_workers(apr_thread_t * listener, apr_thread_t ** threads) */ 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 */ + while (iter < 10 && !dying) { + /* listener has not stopped accepting 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"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00475) + "the listener thread didn't stop accepting"); } else { rv = apr_thread_join(&thread_rv, listener); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00476) "apr_thread_join: unable to join listener thread"); } } } - for (i = 0; i < ap_threads_per_child; i++) { + for (i = 0; i < 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00477) "apr_thread_join: unable to join worker " "thread %d", i); } @@ -1380,7 +1989,7 @@ static void join_start_thread(apr_thread_t * start_thread_id) */ rv = apr_thread_join(&thread_rv, start_thread_id); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00478) "apr_thread_join: unable to join the start " "thread"); } } @@ -1403,10 +2012,13 @@ static void child_main(int child_num_arg) /*stuff to do before we switch id's, so we have permissions. */ ap_reopen_scoreboard(pchild, NULL, 0); - if (unixd_setup_child()) { + if (ap_run_drop_privileges(pchild, ap_server_conf)) { clean_child_exit(APEXIT_CHILDFATAL); } + apr_thread_mutex_create(&g_timer_ring_mtx, APR_THREAD_MUTEX_DEFAULT, pchild); + APR_RING_INIT(&timer_free_ring, timer_event_t, link); + APR_RING_INIT(&timer_ring, timer_event_t, link); ap_run_child_init(pchild, ap_server_conf); /* done with init critical section */ @@ -1417,7 +2029,7 @@ static void child_main(int child_num_arg) */ rv = apr_setup_signal_thread(); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00479) "Couldn't initialize signal thread"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -1435,16 +2047,8 @@ static void child_main(int child_num_arg) /* 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)); + threads = ap_calloc(threads_per_child, sizeof(apr_thread_t *)); + ts = apr_palloc(pchild, sizeof(*ts)); apr_threadattr_create(&thread_attr, pchild); /* 0 means PTHREAD_CREATE_JOINABLE */ @@ -1462,7 +2066,7 @@ static void child_main(int child_num_arg) 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, + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00480) "apr_thread_create: unable to create worker thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); @@ -1501,13 +2105,13 @@ static void child_main(int child_num_arg) 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 + * (e.g., for MaxConnectionsPerChild) 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); + rv = ap_event_pod_check(pod); if (rv == AP_NORESTART) { /* see if termination was triggered while we slept */ switch (terminate_mode) { @@ -1549,24 +2153,27 @@ 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 (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; } if (one_process) { set_signals(); - ap_scoreboard_image->parent[slot].pid = getpid(); + event_note_child_started(slot, getpid()); child_main(slot); + /* NOTREACHED */ } if ((pid = fork()) == -1) { - ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00481) "fork: Unable to fork new process"); - /* fork didn't succeed. Fix the scoreboard or else - * it will say SERVER_STARTING forever and ever + /* fork didn't succeed. There's no need to touch the scoreboard; + * if we were trying to replace a failed child process, then + * server_main_loop() marked its workers SERVER_DEAD, and if + * we were trying to replace a child process that exited normally, + * its worker_thread()s left SERVER_DEAD or SERVER_GRACEFUL behind. */ - 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 @@ -1584,20 +2191,27 @@ static int make_child(server_rec * s, int slot) 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); + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00482) + "processor unbind failed"); #endif RAISE_SIGSTOP(MAKE_CHILD); apr_signal(SIGTERM, just_die); child_main(slot); - - clean_child_exit(0); + /* NOTREACHED */ } /* 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. + */ + event_note_child_lost_slot(slot, pid); + } ap_scoreboard_image->parent[slot].quiescing = 0; - ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].not_accepting = 0; + event_note_child_started(slot, pid); return 0; } @@ -1617,19 +2231,6 @@ static void startup_children(int 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; @@ -1652,17 +2253,21 @@ static void perform_idle_server_maintenance(void) 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 */ + * that 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) + if (i >= retained->max_daemons_limit + && totally_free_length == retained->idle_spawn_rate) + /* short cut if all active processes have been examined and + * enough empty scoreboard slots have been found + */ + break; ps = &ap_scoreboard_image->parent[i]; - for (j = 0; j < ap_threads_per_child; j++) { + for (j = 0; j < threads_per_child; j++) { ws = &ap_scoreboard_image->servers[i][j]; status = ws->status; @@ -1681,8 +2286,9 @@ static void perform_idle_server_maintenance(void) */ 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) { + if (status <= SERVER_READY && !ps->quiescing && !ps->not_accepting + && ps->generation == retained->my_generation) + { ++idle_thread_count; } if (status >= SERVER_READY && status < SERVER_GRACEFUL) { @@ -1691,7 +2297,7 @@ static void perform_idle_server_maintenance(void) } } if (any_dead_threads - && totally_free_length < idle_spawn_rate + && totally_free_length < retained->idle_spawn_rate && free_length < MAX_SPAWN_RATE && (!ps->pid /* no process in the slot */ || ps->quiescing)) { /* or at least one is going away */ @@ -1720,12 +2326,12 @@ static void perform_idle_server_maintenance(void) } } - if (sick_child_detected) { + if (retained->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; + retained->sick_child_detected = 0; } else { /* looks like a basket case. give up. @@ -1733,7 +2339,7 @@ static void perform_idle_server_maintenance(void) shutdown_pending = 1; child_fatal = 1; ap_log_error(APLOG_MARK, APLOG_ALERT, 0, - ap_server_conf, + ap_server_conf, APLOGNO(00483) "No active workers found..." " Apache is exiting!"); /* the child already logged the failure details */ @@ -1741,35 +2347,39 @@ static void perform_idle_server_maintenance(void) } } - ap_max_daemons_limit = last_non_dead + 1; + retained->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; + ap_event_pod_signal(pod, TRUE); + retained->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; + if (free_length == 0) { /* scoreboard is full, can't fork */ + + if (active_thread_count >= ap_daemons_limit * threads_per_child) { + if (!retained->maxclients_reported) { + /* only report this condition once */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00484) + "server reached MaxRequestWorkers setting, " + "consider raising the MaxRequestWorkers " + "setting"); + retained->maxclients_reported = 1; + } } - idle_spawn_rate = 1; + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00485) + "scoreboard is full, not at MaxRequestWorkers"); + } + retained->idle_spawn_rate = 1; } else { - if (free_length > idle_spawn_rate) { - free_length = idle_spawn_rate; + if (free_length > retained->idle_spawn_rate) { + free_length = retained->idle_spawn_rate; } - if (idle_spawn_rate >= 8) { - ap_log_error(APLOG_MARK, APLOG_INFO, 0, - ap_server_conf, + if (retained->idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00486) "server seems busy, (you may need " "to increase StartServers, ThreadsPerChild " "or Min/MaxSpareThreads), " @@ -1783,21 +2393,22 @@ static void perform_idle_server_maintenance(void) /* 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; + if (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; } - else if (idle_spawn_rate < MAX_SPAWN_RATE) { - idle_spawn_rate *= 2; + else if (retained->idle_spawn_rate < MAX_SPAWN_RATE) { + retained->idle_spawn_rate *= 2; } } } else { - idle_spawn_rate = 1; + retained->idle_spawn_rate = 1; } } static void server_main_loop(int remaining_children_to_start) { + ap_generation_t old_gen; int child_slot; apr_exit_why_e exitwhy; int status, processed_status; @@ -1805,34 +2416,50 @@ static void server_main_loop(int remaining_children_to_start) int i; while (!restart_pending && !shutdown_pending) { - ap_wait_or_timeout(&exitwhy, &status, &pid, pconf); + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); if (pid.pid != -1) { processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); if (processed_status == APEXIT_CHILDFATAL) { - shutdown_pending = 1; - child_fatal = 1; - return; + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->my_generation) { + shutdown_pending = 1; + child_fatal = 1; + return; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00487) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + retained->sick_child_detected = 1; + } } else if (processed_status == APEXIT_CHILDSICK) { /* tell perform_idle_server_maintenance to check into this * on the next timer pop */ - sick_child_detected = 1; + retained->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++) + for (i = 0; i < 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; + event_note_child_killed(child_slot, 0, 0); ap_scoreboard_image->parent[child_slot].quiescing = 0; if (processed_status == APEXIT_CHILDSICK) { /* resource shortage, minimize the fork rate */ - idle_spawn_rate = 1; + retained->idle_spawn_rate = 1; } else if (remaining_children_to_start && child_slot < ap_daemons_limit) { @@ -1842,6 +2469,11 @@ static void server_main_loop(int remaining_children_to_start) make_child(ap_server_conf, child_slot); --remaining_children_to_start; } + } + else if (ap_unregister_extra_mpm_process(pid.pid, &old_gen) == 1) { + + event_note_child_killed(-1, /* already out of the scoreboard */ + pid.pid, old_gen); #if APR_HAS_OTHER_CHILD } else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, @@ -1849,12 +2481,12 @@ static void server_main_loop(int remaining_children_to_start) /* handled */ #endif } - else if (is_graceful) { + else if (retained->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, + ap_server_conf, APLOGNO(00488) "long lost child came home! (pid %ld)", (long) pid.pid); } @@ -1883,93 +2515,79 @@ static void server_main_loop(int remaining_children_to_start) } } -int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) +static int event_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 (!retained->is_graceful) { if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { mpm_state = AP_MPMQ_STOPPING; - return 1; + return DONE; } /* fix the generation number in the global score; we just got a new, * cleared scoreboard */ - ap_scoreboard_image->global->running_generation = ap_my_generation; + ap_scoreboard_image->global->running_generation = retained->my_generation; } + restart_pending = shutdown_pending = 0; 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 (max_spare_threads < min_spare_threads + threads_per_child) + max_spare_threads = min_spare_threads + 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 + * rapidly... and for each one that exits we may start a new one, until + * there are at least min_spare_threads idle threads, counting across + * all children. But we may be permitted to start more children 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) { + if (!retained->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; + retained->hold_off_on_exponential_spawning = 10; } - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00489) "%s configured -- resuming normal operations", ap_get_server_description()); - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00490) "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); - 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) { + if (shutdown_pending && !retained->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 */ + ap_event_pod_killpg(pod, ap_daemons_limit, FALSE); + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + event_note_child_killed); 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_remove_pid(pconf, ap_pid_fname); ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, - ap_server_conf, "caught SIGTERM, shutting down"); + ap_server_conf, APLOGNO(00491) "caught SIGTERM, shutting down"); } - return 1; + return DONE; } else if (shutdown_pending) { /* Time to gracefully shut down: * Kill child processes, tell them to call child_exit, etc... @@ -1980,20 +2598,13 @@ int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) /* 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(); + ap_event_pod_killpg(pod, ap_daemons_limit, TRUE); + ap_relieve_child_processes(event_note_child_killed); 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, + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00492) "caught " AP_SIG_GRACEFUL_STOP_STRING ", shutting down gracefully"); } @@ -2010,7 +2621,7 @@ int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) apr_sleep(apr_time_from_sec(1)); /* Relieve any children which have now exited */ - ap_relieve_child_processes(); + ap_relieve_child_processes(event_note_child_killed); active_children = 0; for (index = 0; index < ap_daemons_limit; ++index) { @@ -2027,10 +2638,10 @@ int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) * 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); + ap_event_pod_killpg(pod, ap_daemons_limit, FALSE); + ap_reclaim_child_processes(1, event_note_child_killed); - return 1; + return DONE; } /* we've been told to restart */ @@ -2038,22 +2649,22 @@ int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) if (one_process) { /* not worth thinking about */ - return 1; + return DONE; } /* 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; + ++retained->my_generation; + ap_scoreboard_image->global->running_generation = retained->my_generation; - if (is_graceful) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + if (retained->is_graceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00493) 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); + ap_event_pod_killpg(pod, ap_daemons_limit, TRUE); /* This is mostly for debugging... so that we know what is still @@ -2066,91 +2677,62 @@ int ap_mpm_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) * 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_event_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, + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + event_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00494) "SIGHUP received. Attempting to restart"); } - return 0; + 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 worker_open_logs(apr_pool_t * p, apr_pool_t * plog, - apr_pool_t * ptemp, server_rec * s) +static int event_open_logs(apr_pool_t * p, apr_pool_t * plog, + apr_pool_t * ptemp, server_rec * s) { + int startup = 0; + int level_flags = 0; apr_status_t rv; pconf = p; - ap_server_conf = s; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } 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"); + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "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."); + if ((rv = ap_event_pod_open(pconf, &pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "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 event_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; + const char *userdata_key = "mpm_event_module"; 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) { @@ -2164,64 +2746,309 @@ static int worker_pre_config(apr_pool_t * pconf, apr_pool_t * plog, } /* sigh, want this only the second time around */ - if (restart_num++ == 1) { - is_graceful = 0; + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->max_daemons_limit = -1; + retained->idle_spawn_rate = 1; + } + ++retained->module_loads; + if (retained->module_loads == 2) { rv = apr_pollset_create(&event_pollset, 1, plog, - APR_POLLSET_THREADSAFE); + APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00495) "Couldn't create a Thread Safe Pollset. " - "Is it supported on your platform?"); + "Is it supported on your platform?" + "Also check system or user limits!"); return HTTP_INTERNAL_SERVER_ERROR; } apr_pollset_destroy(event_pollset); if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00496) "apr_proc_detach failed"); return HTTP_INTERNAL_SERVER_ERROR; } } - parent_pid = ap_my_pid = getpid(); } - unixd_pre_config(ptemp); + parent_pid = ap_my_pid = getpid(); + 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; + server_limit = DEFAULT_SERVER_LIMIT; + thread_limit = DEFAULT_THREAD_LIMIT; 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; + threads_per_child = DEFAULT_THREADS_PER_CHILD; + max_workers = ap_daemons_limit * threads_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 int event_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00497) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of", server_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d servers, decreasing to %d.", + MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00498) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00499) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00500) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00501) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00502) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of", thread_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00503) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00504) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00505) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_thread_limit) { + retained->first_thread_limit = thread_limit; + } + else if (thread_limit != retained->first_thread_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00506) + "changing ThreadLimit to %d from original value of %d " + "not allowed during restart", + thread_limit, retained->first_thread_limit); + thread_limit = retained->first_thread_limit; + } + + if (threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00507) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of", threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + thread_limit, thread_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the ThreadLimit " + "directive."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00508) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + threads_per_child, thread_limit); + } + threads_per_child = thread_limit; + } + else if (threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00509) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00510) + "ThreadsPerChild of %d not allowed, increasing to 1", + threads_per_child); + } + threads_per_child = 1; + } + + if (max_workers < threads_per_child) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00511) + "WARNING: MaxRequestWorkers of %d is less than " + "ThreadsPerChild of", max_workers); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d, increasing to %d. MaxRequestWorkers must be at " + "least as large", + threads_per_child, threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " as the number of threads in a single server."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00512) + "MaxRequestWorkers of %d is less than ThreadsPerChild " + "of %d, increasing to match", + max_workers, threads_per_child); + } + max_workers = threads_per_child; + } + + ap_daemons_limit = max_workers / threads_per_child; + + if (max_workers % threads_per_child) { + int tmp_max_workers = ap_daemons_limit * threads_per_child; + + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00513) + "WARNING: MaxRequestWorkers of %d is not an integer " + "multiple of", max_workers); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " ThreadsPerChild of %d, decreasing to nearest " + "multiple %d,", threads_per_child, + tmp_max_workers); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " for a maximum of %d servers.", + ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00514) + "MaxRequestWorkers of %d is not an integer multiple " + "of ThreadsPerChild of %d, decreasing to nearest " + "multiple %d", max_workers, threads_per_child, + tmp_max_workers); + } + max_workers = tmp_max_workers; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00515) + "WARNING: MaxRequestWorkers of %d would require %d " + "servers and ", max_workers, ap_daemons_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " would exceed ServerLimit of %d, decreasing to %d.", + server_limit, server_limit * threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the ServerLimit " + "directive."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00516) + "MaxRequestWorkers of %d would require %d servers and " + "exceed ServerLimit of %d, decreasing to %d", + max_workers, ap_daemons_limit, server_limit, + server_limit * threads_per_child); + } + ap_daemons_limit = server_limit; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in ap_mpm_run() */ + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00517) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00518) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00519) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1", min_spare_threads); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " to avoid almost certain server failure."); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " Please read the documentation."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00520) + "MinSpareThreads of %d not allowed, increasing to 1", + min_spare_threads); + } + min_spare_threads = 1; + } + + /* max_spare_threads < min_spare_threads + threads_per_child + * checked in ap_mpm_run() + */ return OK; } static void event_hooks(apr_pool_t * p) { - /* The worker open_logs phase must run before the core's, or stderr + /* Our open_logs hook function 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); + ap_hook_open_logs(event_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); /* 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); + ap_hook_pre_config(event_pre_config, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_hook_check_config(event_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(event_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(event_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_register_timed_callback(event_register_timed_callback, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(event_get_name, NULL, NULL, APR_HOOK_MIDDLE); } static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, @@ -2245,16 +3072,6 @@ static const char *set_min_spare_threads(cmd_parms * cmd, void *dummy, } 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; } @@ -2270,62 +3087,19 @@ static const char *set_max_spare_threads(cmd_parms * cmd, void *dummy, return NULL; } -static const char *set_max_clients(cmd_parms * cmd, void *dummy, +static const char *set_max_workers(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; + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00521) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); } + max_workers = atoi(arg); return NULL; } @@ -2337,107 +3111,54 @@ static const char *set_threads_per_child(cmd_parms * cmd, void *dummy, 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; - } + threads_per_child = atoi(arg); 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; - } + server_limit = atoi(arg); 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; + thread_limit = atoi(arg); + return NULL; +} - 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; +static const char *set_worker_factor(cmd_parms * cmd, void *dummy, + const char *arg) +{ + double val; + char *endptr; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; } + + val = strtod(arg, &endptr); + if (*endptr) + return "error parsing value"; + + worker_factor = val * WORKER_FACTOR_SCALE; + if (worker_factor == 0) + worker_factor = 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"), @@ -2447,20 +3168,25 @@ static const command_rec event_cmds[] = { "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, + AP_INIT_TAKE1("MaxClients", set_max_workers, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), + AP_INIT_TAKE1("MaxRequestWorkers", set_max_workers, 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_INIT_TAKE1("AsyncRequestWorkerFactor", set_worker_factor, NULL, RSRC_CONF, + "How many additional connects will be accepted per idle " + "worker thread"), AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, {NULL} }; -module AP_MODULE_DECLARE_DATA mpm_event_module = { +AP_DECLARE_MODULE(mpm_event) = { MPM20_MODULE_STUFF, - ap_mpm_rewrite_args, /* hook to run before apache parses args */ + 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 */ diff --git a/server/mpm/experimental/event/fdqueue.c b/server/mpm/event/fdqueue.c index 5a1baa94..32911757 100644 --- a/server/mpm/experimental/event/fdqueue.c +++ b/server/mpm/event/fdqueue.c @@ -34,6 +34,8 @@ struct fd_queue_info_t apr_thread_cond_t *wait_for_idler; int terminated; int max_idlers; + int max_recycled_pools; + apr_uint32_t recycled_pools_count; recycled_pool *recycled_pools; }; @@ -50,7 +52,7 @@ static apr_status_t queue_info_cleanup(void *data_) break; } if (apr_atomic_casptr - ((volatile void **) &(qi->recycled_pools), first_pool->next, + ((void*) &(qi->recycled_pools), first_pool->next, first_pool) == first_pool) { apr_pool_destroy(first_pool->pool); } @@ -60,7 +62,8 @@ static apr_status_t queue_info_cleanup(void *data_) } apr_status_t ap_queue_info_create(fd_queue_info_t ** queue_info, - apr_pool_t * pool, int max_idlers) + apr_pool_t * pool, int max_idlers, + int max_recycled_pools) { apr_status_t rv; fd_queue_info_t *qi; @@ -77,6 +80,7 @@ apr_status_t ap_queue_info_create(fd_queue_info_t ** queue_info, return rv; } qi->recycled_pools = NULL; + qi->max_recycled_pools = max_recycled_pools; qi->max_idlers = max_idlers; apr_pool_cleanup_register(pool, qi, queue_info_cleanup, apr_pool_cleanup_null); @@ -95,7 +99,12 @@ apr_status_t ap_queue_info_set_idle(fd_queue_info_t * queue_info, ap_push_pool(queue_info, pool_to_recycle); /* Atomically increment the count of idle workers */ - prev_idlers = apr_atomic_inc32(&(queue_info->idlers)); + /* + * TODO: The atomics expect unsigned whereas we're using signed. + * Need to double check that they work as expected or else + * rework how we determine blocked. + */ + prev_idlers = apr_atomic_inc32((apr_uint32_t *)&(queue_info->idlers)); /* If other threads are waiting on a worker, wake one up */ if (prev_idlers < 0) { @@ -118,20 +127,34 @@ apr_status_t ap_queue_info_set_idle(fd_queue_info_t * queue_info, return APR_SUCCESS; } -apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info) +apr_status_t ap_queue_info_try_get_idler(fd_queue_info_t * queue_info) +{ + int prev_idlers; + prev_idlers = apr_atomic_dec32((apr_uint32_t *)&(queue_info->idlers)); + if (prev_idlers <= 0) { + apr_atomic_inc32((apr_uint32_t *)&(queue_info->idlers)); /* back out dec */ + return APR_EAGAIN; + } + return APR_SUCCESS; +} + +apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info, + int *had_to_block) { 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); + /* See TODO in ap_queue_info_set_idle() */ + prev_idlers = apr_atomic_add32((apr_uint32_t *)&(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 */ + /* See TODO in ap_queue_info_set_idle() */ + apr_atomic_inc32((apr_uint32_t *)&(queue_info->idlers)); /* back out dec */ return rv; } /* Re-check the idle worker count to guard against a @@ -154,6 +177,7 @@ apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info) * threads are waiting on an idle worker. */ if (queue_info->idlers < 0) { + *had_to_block = 1; rv = apr_thread_cond_wait(queue_info->wait_for_idler, queue_info->idlers_mutex); if (rv != APR_SUCCESS) { @@ -180,33 +204,48 @@ apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info) } } +apr_uint32_t ap_queue_info_get_idlers(fd_queue_info_t * queue_info) +{ + apr_int32_t val; + val = (apr_int32_t)apr_atomic_read32((apr_uint32_t *)&queue_info->idlers); + if (val < 0) + return 0; + return val; +} void ap_push_pool(fd_queue_info_t * queue_info, apr_pool_t * pool_to_recycle) { + struct recycled_pool *new_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 (;;) { - /* - * Save queue_info->recycled_pool in local variable next because - * new_recycle->next can be changed after apr_atomic_casptr - * function call. For gory details see PR 44402. - */ - struct recycled_pool *next = queue_info->recycled_pools; - new_recycle->next = next; - if (apr_atomic_casptr - ((volatile void **) &(queue_info->recycled_pools), - new_recycle, next) == next) { - break; - } + if (!pool_to_recycle) + return; + + if (queue_info->max_recycled_pools >= 0) { + apr_uint32_t cnt = apr_atomic_read32(&queue_info->recycled_pools_count); + if (cnt >= queue_info->max_recycled_pools) { + apr_pool_destroy(pool_to_recycle); + return; } + apr_atomic_inc32(&queue_info->recycled_pools_count); + } + + new_recycle = (struct recycled_pool *) apr_palloc(pool_to_recycle, + sizeof (*new_recycle)); + new_recycle->pool = pool_to_recycle; + for (;;) { + /* + * Save queue_info->recycled_pool in local variable next because + * new_recycle->next can be changed after apr_atomic_casptr + * function call. For gory details see PR 44402. + */ + struct recycled_pool *next = queue_info->recycled_pools; + new_recycle->next = next; + if (apr_atomic_casptr((void*) &(queue_info->recycled_pools), + new_recycle, next) == next) + break; } } @@ -231,9 +270,11 @@ void ap_pop_pool(apr_pool_t ** recycled_pool, fd_queue_info_t * queue_info) break; } if (apr_atomic_casptr - ((volatile void **) &(queue_info->recycled_pools), + ((void*) &(queue_info->recycled_pools), first_pool->next, first_pool) == first_pool) { *recycled_pool = first_pool->pool; + if (queue_info->max_recycled_pools >= 0) + apr_atomic_dec32(&queue_info->recycled_pools_count); break; } } @@ -261,7 +302,7 @@ apr_status_t ap_queue_info_term(fd_queue_info_t * queue_info) * 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) +#define ap_queue_empty(queue) ((queue)->nelts == 0 && APR_RING_EMPTY(&queue->timers ,timer_event_t, link)) /** * Callback routine that is called to destroy this @@ -298,9 +339,13 @@ apr_status_t ap_queue_init(fd_queue_t * queue, int queue_capacity, return rv; } + APR_RING_INIT(&queue->timers, timer_event_t, link); + queue->data = apr_palloc(a, queue_capacity * sizeof(fd_queue_elem_t)); queue->bounds = queue_capacity; queue->nelts = 0; + queue->in = 0; + queue->out = 0; /* Set all the sockets in the queue to NULL */ for (i = 0; i < queue_capacity; ++i) @@ -319,7 +364,7 @@ apr_status_t ap_queue_init(fd_queue_t * queue, int queue_capacity, * 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) + event_conn_state_t * ecs, apr_pool_t * p) { fd_queue_elem_t *elem; apr_status_t rv; @@ -331,9 +376,12 @@ apr_status_t ap_queue_push(fd_queue_t * queue, apr_socket_t * sd, AP_DEBUG_ASSERT(!queue->terminated); AP_DEBUG_ASSERT(!ap_queue_full(queue)); - elem = &queue->data[queue->nelts]; + elem = &queue->data[queue->in]; + queue->in++; + if (queue->in >= queue->bounds) + queue->in -= queue->bounds; elem->sd = sd; - elem->cs = cs; + elem->ecs = ecs; elem->p = p; queue->nelts++; @@ -346,14 +394,36 @@ apr_status_t ap_queue_push(fd_queue_t * queue, apr_socket_t * sd, return APR_SUCCESS; } +apr_status_t ap_queue_push_timer(fd_queue_t * queue, timer_event_t *te) +{ + apr_status_t rv; + + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { + return rv; + } + + AP_DEBUG_ASSERT(!queue->terminated); + + APR_RING_INSERT_TAIL(&queue->timers, te, timer_event_t, link); + + 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) +apr_status_t ap_queue_pop_something(fd_queue_t * queue, apr_socket_t ** sd, + event_conn_state_t ** ecs, apr_pool_t ** p, + timer_event_t ** te_out) { fd_queue_elem_t *elem; apr_status_t rv; @@ -382,14 +452,26 @@ apr_status_t ap_queue_pop(fd_queue_t * queue, apr_socket_t ** sd, } } - elem = &queue->data[--queue->nelts]; - *sd = elem->sd; - *cs = elem->cs; - *p = elem->p; + *te_out = NULL; + + if (!APR_RING_EMPTY(&queue->timers, timer_event_t, link)) { + *te_out = APR_RING_FIRST(&queue->timers); + APR_RING_REMOVE(*te_out, link); + } + else { + elem = &queue->data[queue->out]; + queue->out++; + if (queue->out >= queue->bounds) + queue->out -= queue->bounds; + queue->nelts--; + *sd = elem->sd; + *ecs = elem->ecs; + *p = elem->p; #ifdef AP_DEBUG - elem->sd = NULL; - elem->p = NULL; + elem->sd = NULL; + elem->p = NULL; #endif /* AP_DEBUG */ + } rv = apr_thread_mutex_unlock(queue->one_big_mutex); return rv; diff --git a/server/mpm/experimental/event/fdqueue.h b/server/mpm/event/fdqueue.h index bd18adfd..955816b7 100644 --- a/server/mpm/experimental/event/fdqueue.h +++ b/server/mpm/event/fdqueue.h @@ -37,28 +37,48 @@ #endif #include <apr_errno.h> +#include "ap_mpm.h" + typedef struct fd_queue_info_t fd_queue_info_t; +typedef struct event_conn_state_t event_conn_state_t; apr_status_t ap_queue_info_create(fd_queue_info_t ** queue_info, - apr_pool_t * pool, int max_idlers); + apr_pool_t * pool, int max_idlers, + int max_recycled_pools); 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_try_get_idler(fd_queue_info_t * queue_info); +apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t * queue_info, + int *had_to_block); apr_status_t ap_queue_info_term(fd_queue_info_t * queue_info); +apr_uint32_t ap_queue_info_get_idlers(fd_queue_info_t * queue_info); struct fd_queue_elem_t { apr_socket_t *sd; apr_pool_t *p; - conn_state_t *cs; + event_conn_state_t *ecs; }; typedef struct fd_queue_elem_t fd_queue_elem_t; +typedef struct timer_event_t timer_event_t; + +struct timer_event_t { + APR_RING_ENTRY(timer_event_t) link; + apr_time_t when; + ap_mpm_callback_fn_t *cbfunc; + void *baton; +}; + + struct fd_queue_t { + APR_RING_HEAD(timers_t, timer_event_t) timers; fd_queue_elem_t *data; - int nelts; - int bounds; + unsigned int nelts; + unsigned int bounds; + unsigned int in; + unsigned int out; apr_thread_mutex_t *one_big_mutex; apr_thread_cond_t *not_empty; int terminated; @@ -66,15 +86,17 @@ struct fd_queue_t 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, +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); + event_conn_state_t * ecs, apr_pool_t * p); +apr_status_t ap_queue_push_timer(fd_queue_t *queue, timer_event_t *te); +apr_status_t ap_queue_pop_something(fd_queue_t * queue, apr_socket_t ** sd, + event_conn_state_t ** ecs, apr_pool_t ** p, + timer_event_t ** te); apr_status_t ap_queue_interrupt_all(fd_queue_t * queue); apr_status_t ap_queue_term(fd_queue_t * queue); diff --git a/server/mpm/experimental/event/mpm_default.h b/server/mpm/event/mpm_default.h index c26ee714..079e71fe 100644 --- a/server/mpm/experimental/event/mpm_default.h +++ b/server/mpm/event/mpm_default.h @@ -51,29 +51,5 @@ #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/event/pod.c index 5a9999f7..5deed8ba 100644 --- a/server/mpm/experimental/event/pod.c +++ b/server/mpm/event/pod.c @@ -16,11 +16,15 @@ #include "pod.h" +#include "apr_portable.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) +APLOG_USE_MODULE(mpm_event); + +AP_DECLARE(apr_status_t) ap_event_pod_open(apr_pool_t * p, ap_event_pod_t ** pod) { apr_status_t rv; @@ -41,7 +45,7 @@ AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t * p, ap_pod_t ** pod) return APR_SUCCESS; } -AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t * pod) +AP_DECLARE(int) ap_event_pod_check(ap_event_pod_t * pod) { char c; apr_os_file_t fd; @@ -63,7 +67,7 @@ AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t * pod) return AP_NORESTART; } -AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t * pod) +AP_DECLARE(apr_status_t) ap_event_pod_close(ap_event_pod_t * pod) { apr_status_t rv; @@ -79,7 +83,7 @@ AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t * pod) return rv; } -static apr_status_t pod_signal_internal(ap_pod_t * pod, int graceful) +static apr_status_t pod_signal_internal(ap_event_pod_t * pod, int graceful) { apr_status_t rv; char char_of_death = graceful ? GRACEFUL_CHAR : RESTART_CHAR; @@ -87,18 +91,18 @@ static apr_status_t pod_signal_internal(ap_pod_t * pod, int graceful) 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, + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00522) "write pipe_of_death"); } return rv; } -AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t * pod, int graceful) +AP_DECLARE(apr_status_t) ap_event_pod_signal(ap_event_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) +AP_DECLARE(void) ap_event_pod_killpg(ap_event_pod_t * pod, int num, int graceful) { int i; apr_status_t rv = APR_SUCCESS; diff --git a/server/mpm/experimental/event/pod.h b/server/mpm/event/pod.h index 78e2b9d7..861e4d99 100644 --- a/server/mpm/experimental/event/pod.h +++ b/server/mpm/event/pod.h @@ -31,7 +31,6 @@ #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" @@ -43,18 +42,18 @@ #define AP_RESTART 0 #define AP_GRACEFUL 1 -typedef struct ap_pod_t ap_pod_t; +typedef struct ap_event_pod_t ap_event_pod_t; -struct ap_pod_t +struct ap_event_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); +AP_DECLARE(apr_status_t) ap_event_pod_open(apr_pool_t * p, ap_event_pod_t ** pod); +AP_DECLARE(int) ap_event_pod_check(ap_event_pod_t * pod); +AP_DECLARE(apr_status_t) ap_event_pod_close(ap_event_pod_t * pod); +AP_DECLARE(apr_status_t) ap_event_pod_signal(ap_event_pod_t * pod, int graceful); +AP_DECLARE(void) ap_event_pod_killpg(ap_event_pod_t * pod, int num, int graceful); /** @} */ diff --git a/server/mpm/experimental/event/Makefile.in b/server/mpm/experimental/event/Makefile.in deleted file mode 100644 index 7c2a1a7a..00000000 --- a/server/mpm/experimental/event/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ - -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 deleted file mode 100644 index 5e1db539..00000000 --- a/server/mpm/experimental/event/config5.m4 +++ /dev/null @@ -1,6 +0,0 @@ -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/mpm.h b/server/mpm/experimental/event/mpm.h deleted file mode 100644 index cf4e61d9..00000000 --- a/server/mpm/experimental/event/mpm.h +++ /dev/null @@ -1,62 +0,0 @@ -/* 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/mpmt_os2/Makefile.in b/server/mpm/mpmt_os2/Makefile.in index 38e598ed..f34af9cb 100644 --- a/server/mpm/mpmt_os2/Makefile.in +++ b/server/mpm/mpmt_os2/Makefile.in @@ -1,5 +1 @@ - -LTLIBRARY_NAME = libmpmt_os2.la -LTLIBRARY_SOURCES = mpmt_os2.c mpmt_os2_child.c - -include $(top_srcdir)/build/ltlib.mk +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/mpmt_os2/config.m4 b/server/mpm/mpmt_os2/config.m4 new file mode 100644 index 00000000..9a29903c --- /dev/null +++ b/server/mpm/mpmt_os2/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if mpmt_os2 MPM supports this platform) +case $host in + *os2-emx*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(mpmt_os2, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/mpmt_os2/config5.m4 b/server/mpm/mpmt_os2/config5.m4 index b27c296d..c74f1452 100644 --- a/server/mpm/mpmt_os2/config5.m4 +++ b/server/mpm/mpmt_os2/config5.m4 @@ -1,5 +1,3 @@ -if test "$MPM_NAME" = "mpmt_os2" ; then - AC_CACHE_SAVE - APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile) +APACHE_MPM_MODULE(mpmt_os2, $enable_mpm_mpmt_os2, mpmt_os2.lo mpmt_os2_child.lo,[ APR_ADDTO(CFLAGS,-Zmt) -fi +]) diff --git a/server/mpm/mpmt_os2/mpm.h b/server/mpm/mpmt_os2/mpm.h deleted file mode 100644 index f8bbc30e..00000000 --- a/server/mpm/mpmt_os2/mpm.h +++ /dev/null @@ -1,43 +0,0 @@ -/* 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 index 2a21cd50..02b4a74d 100644 --- a/server/mpm/mpmt_os2/mpm_default.h +++ b/server/mpm/mpmt_os2/mpm_default.h @@ -17,7 +17,7 @@ /** * @file mpmt_os2/mpm_default.h * @brief os2 MPM defaults - * + * * @addtogroup APACHE_MPM_OS2 * @{ */ @@ -45,11 +45,6 @@ #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. */ @@ -57,12 +52,5 @@ #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 index b3a83c43..3bd360d0 100644 --- a/server/mpm/mpmt_os2/mpmt_os2.c +++ b/server/mpm/mpmt_os2/mpmt_os2.c @@ -34,9 +34,8 @@ /* Todo list - - Enforce MaxClients somehow + - Enforce MaxRequestWorkers somehow */ -#define CORE_PRIVATE #define INCL_NOPMAPI #define INCL_DOS #define INCL_DOSERRORS @@ -49,11 +48,11 @@ #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 "scoreboard.h" #include "apr_strings.h" #include <os2.h> #include <process.h> @@ -71,13 +70,11 @@ 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; @@ -111,7 +108,7 @@ 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 ) +static int mpmt_os2_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) { char *listener_shm_name; parent_info_t *parent_info; @@ -164,7 +161,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) is_parent_process = TRUE; if (ap_setup_listeners(ap_server_conf) < 1) { - ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00200) "no listening sockets available, shutting down"); return 1; } @@ -176,15 +173,8 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) 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, + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00201) "caught SIGTERM, shutting down"); return 1; } @@ -211,7 +201,7 @@ static char master_main() set_signals(); if (ap_setup_listeners(ap_server_conf) < 1) { - ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00202) "no listening sockets available, shutting down"); return FALSE; } @@ -227,7 +217,7 @@ static char master_main() PAG_READ|PAG_WRITE|PAG_COMMIT); if (rc) { - ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00203) "failure allocating shared memory, shutting down"); return FALSE; } @@ -244,7 +234,7 @@ static char master_main() 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, + ap_log_error(APLOG_MARK, APLOG_ALERT, APR_FROM_OS_ERROR(rc), s, APLOGNO(00204) "failure creating accept mutex, shutting down"); return FALSE; } @@ -259,7 +249,7 @@ static char master_main() PAG_COMMIT|PAG_READ|PAG_WRITE); if (rc) { - ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00205) "unable to allocate shared memory for scoreboard , exiting"); return FALSE; } @@ -268,17 +258,11 @@ static char master_main() } ap_scoreboard_image->global->restart_time = apr_time_now(); - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00206) "%s configured -- resuming normal operations", ap_get_server_description()); - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00207) "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); @@ -355,7 +339,7 @@ static void spawn_child(int slot) ppib->pib_pchcmd, NULL, &proc_rc, progname); if (rc) { - ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00208) "error spawning child, slot %d", slot); } @@ -398,63 +382,83 @@ static void set_signals() sa.sa_handler = sig_term; if (sigaction(SIGTERM, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00209) "sigaction(SIGTERM)"); if (sigaction(SIGINT, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGINT)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00210) "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)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00211) "sigaction(SIGHUP)"); if (sigaction(SIGUSR1, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGUSR1)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00212) "sigaction(SIGUSR1)"); } /* Enquiry functions used get MPM status info */ -AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +static apr_status_t mpmt_os2_query(int query_code, int *result, apr_status_t *rv) { + *rv = APR_SUCCESS; + switch (query_code) { case AP_MPMQ_MAX_DAEMON_USED: *result = ap_max_daemons_limit; - return APR_SUCCESS; + break; + case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_DYNAMIC; - return APR_SUCCESS; + break; + case AP_MPMQ_IS_FORKED: *result = AP_MPMQ_NOT_SUPPORTED; - return APR_SUCCESS; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: *result = HARD_SERVER_LIMIT; - return APR_SUCCESS; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: *result = HARD_THREAD_LIMIT; - return APR_SUCCESS; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: *result = ap_max_requests_per_child; - return APR_SUCCESS; + break; + + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + + default: + *rv = APR_ENOTIMPL; + break; } - return APR_ENOTIMPL; + + return OK; } -int ap_graceful_stop_signalled(void) + +static const char *mpmt_os2_get_name(void) { - return is_graceful; + return "mpmt_os2"; } + /* Configuration handling stuff */ static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) @@ -465,14 +469,56 @@ static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t * 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 + ap_sys_privileges_handlers(1); + + return OK; +} + + + +static int mpmt_os2_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + static int restart_num = 0; + int startup = 0; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00213) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00214) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00215) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1", ap_min_spare_threads); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " to avoid almost certain server failure."); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " Please read the documentation."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00216) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_min_spare_threads); + } + ap_min_spare_threads = 1; + } return OK; } @@ -482,6 +528,10 @@ static int mpmt_os2_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t * static void mpmt_os2_hooks(apr_pool_t *p) { ap_hook_pre_config(mpmt_os2_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(mpmt_os2_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(mpmt_os2_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(mpmt_os2_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(mpmt_os2_get_name, NULL, NULL, APR_HOOK_MIDDLE); } @@ -510,17 +560,6 @@ static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, } 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; } @@ -565,7 +604,7 @@ AP_INIT_TAKE1("ScoreBoardFile", ignore_cmd, NULL, RSRC_CONF, \ { NULL } }; -module AP_MODULE_DECLARE_DATA mpm_mpmt_os2_module = { +AP_DECLARE_MODULE(mpm_mpmt_os2) = { MPM20_MODULE_STUFF, NULL, /* hook to run before apache parses args */ NULL, /* create per-directory config structure */ diff --git a/server/mpm/mpmt_os2/mpmt_os2_child.c b/server/mpm/mpmt_os2/mpmt_os2_child.c index bc313315..ca9f5947 100644 --- a/server/mpm/mpmt_os2/mpmt_os2_child.c +++ b/server/mpm/mpmt_os2/mpmt_os2_child.c @@ -14,7 +14,6 @@ * limitations under the License. */ -#define CORE_PRIVATE #define INCL_NOPMAPI #define INCL_DOS #define INCL_DOSERRORS @@ -27,7 +26,7 @@ #include "http_config.h" #include "http_core.h" /* for get_remote_host */ #include "http_connection.h" -#include "mpm.h" +#include "scoreboard.h" #include "ap_mpm.h" #include "ap_listen.h" #include "apr_portable.h" @@ -37,6 +36,8 @@ #include <os2.h> #include <process.h> +APLOG_USE_MODULE(mpm_mpmt_os2); + /* XXXXXX move these to header file private to this MPM */ /* We don't need many processes, @@ -116,7 +117,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) rc = DosCreateEventSem(NULL, &shutdown_event, 0, FALSE); if (rc) { - ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00189) "unable to create shutdown semaphore, exiting"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -126,7 +127,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) PAG_READ|PAG_WRITE); if (rc) { - ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00190) "scoreboard not readable in child, exiting"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -138,7 +139,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) rc = DosOpenMutexSem(NULL, &ap_mpm_accept_mutex); if (rc) { - ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00191) "accept mutex couldn't be accessed in child, exiting"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -147,7 +148,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) 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, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00192) "child pid not found in scoreboard, exiting"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -161,7 +162,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) 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, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00193) "unable to create work queue, exiting"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -234,7 +235,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) if (rv != APR_SUCCESS) { if (!APR_STATUS_IS_EINTR(rv)) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00194) "apr_socket_accept"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -290,13 +291,14 @@ void ap_mpm_child_main(apr_pool_t *pconf) void add_worker() { int thread_slot; + int stacksize = ap_thread_stacksize == 0 ? 128*1024 : ap_thread_stacksize; /* 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); + _beginthread(worker_main, NULL, stacksize, (void *)thread_slot); break; } } @@ -317,7 +319,7 @@ ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec, if (pReportRec->ExceptionNum == XCPT_ACCESS_VIOLATION || pReportRec->ExceptionNum == XCPT_INTEGER_DIVIDE_BY_ZERO) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00195) "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()) { @@ -340,6 +342,8 @@ ULONG APIENTRY thread_exception_handler(EXCEPTIONREPORTRECORD *pReportRec, static void worker_main(void *vpArg) { + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; long conn_id; conn_rec *current_conn; apr_pool_t *pconn; @@ -359,11 +363,14 @@ static void worker_main(void *vpArg) /* Trap exceptions in this thread so we don't take down the whole process */ DosSetExceptionHandler( ®_rec ); + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); + 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, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00196) "unable to open work queue, exiting"); ap_scoreboard_image->servers[child_slot][thread_slot].tid = 0; } @@ -385,6 +392,7 @@ static void worker_main(void *vpArg) sbh, bucket_alloc); if (current_conn) { + current_conn->current_thread = thd; ap_process_connection(current_conn, worker_args->conn_sd); ap_lingering_close(current_conn); } @@ -416,7 +424,7 @@ static void server_maintenance(void *vpArg) 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, + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(rc), ap_server_conf, APLOGNO(00197) "unable to open work queue in maintenance thread"); return; } @@ -471,10 +479,10 @@ static void set_signals() sa.sa_handler = sig_term; if (sigaction(SIGTERM, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00198) "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)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00199) "sigaction(SIGHUP)"); } diff --git a/server/mpm/netware/mpm.h b/server/mpm/netware/mpm.h deleted file mode 100644 index 106d62a5..00000000 --- a/server/mpm/netware/mpm.h +++ /dev/null @@ -1,58 +0,0 @@ -/* 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 index 96c5aa09..b8c3ad6f 100644 --- a/server/mpm/netware/mpm_default.h +++ b/server/mpm/netware/mpm_default.h @@ -24,27 +24,6 @@ #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. * @@ -81,23 +60,6 @@ #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. */ @@ -105,13 +67,6 @@ #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 diff --git a/server/mpm/netware/mpm_netware.c b/server/mpm/netware/mpm_netware.c index a8d0952f..77840492 100644 --- a/server/mpm/netware/mpm_netware.c +++ b/server/mpm/netware/mpm_netware.c @@ -61,8 +61,6 @@ #include <sys/select.h> #endif -#define CORE_PRIVATE - #include "ap_config.h" #include "httpd.h" #include "mpm_default.h" @@ -88,6 +86,8 @@ #include <library.h> #include <screen.h> +int nlmUnloadSignaled(int wait); + /* 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. @@ -108,9 +108,11 @@ #define WORKER_READY SERVER_READY #define WORKER_IDLE_KILL SERVER_IDLE_KILL -/* config globals */ +#define MPM_HARD_LIMITS_FILE "/mpm_default.h" -int ap_threads_per_child=0; /* Worker threads per child */ +/* *Non*-shared http_main globals... */ + +static 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; @@ -119,13 +121,10 @@ 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 + * to deal with MaxRequestWorkers 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... */ +static int ap_max_workers_limit = -1; int hold_screen_on_exit = 0; /* Indicates whether the screen should be held open */ @@ -171,7 +170,7 @@ 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; +static 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, @@ -199,52 +198,63 @@ static void mpm_main_cleanup(void) } } -AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +static int netware_query(int query_code, int *result, apr_status_t *rv) { + *rv = APR_SUCCESS; switch(query_code){ case AP_MPMQ_MAX_DAEMON_USED: *result = 1; - return APR_SUCCESS; + break; case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_DYNAMIC; - return APR_SUCCESS; + break; case AP_MPMQ_IS_FORKED: *result = AP_MPMQ_NOT_SUPPORTED; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_DAEMONS: *result = HARD_SERVER_LIMIT; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_THREADS: *result = HARD_THREAD_LIMIT; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_THREADS: *result = ap_threads_limit; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_THREADS: *result = ap_threads_min_free; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_THREADS: *result = ap_threads_max_free; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_REQUESTS_DAEMON: *result = ap_max_requests_per_child; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_DAEMONS: *result = 1; - return APR_SUCCESS; + break; case AP_MPMQ_MPM_STATE: *result = mpm_state; - return APR_SUCCESS; + break; + case AP_MPMQ_GENERATION: + *result = ap_my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; } - return APR_ENOTIMPL; + return OK; } +static const char *netware_get_name(void) +{ + return "NetWare"; +} /***************************************************************** * Connection structures and accounting... @@ -314,12 +324,6 @@ int nlmUnloadSignaled(int wait) */ -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; @@ -333,12 +337,13 @@ 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; + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; int my_worker_num = (int)arg; apr_socket_t *csd = NULL; @@ -351,6 +356,9 @@ void worker_main(void *arg) struct timeval tv; int wouldblock_retry; + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pmain); + tv.tv_sec = 1; tv.tv_usec = 0; @@ -397,7 +405,7 @@ void worker_main(void *arg) if (srv <= 0) { if (srv < 0) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00217) "select() failed on listen socket"); apr_thread_yield(); } @@ -496,14 +504,14 @@ void worker_main(void *arg) * Ben Hyde noted that temporary ENETDOWN situations * occur in mobile IP. */ - ap_log_error(APLOG_MARK, APLOG_EMERG, stat, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_EMERG, stat, ap_server_conf, APLOGNO(00218) "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, + ap_log_error(APLOG_MARK, APLOG_ERR, stat, ap_server_conf, APLOGNO(00219) "apr_socket_accept: (client socket)"); clean_child_exit(1, my_worker_num, ptrans, bucket_alloc); } @@ -519,6 +527,7 @@ void worker_main(void *arg) my_worker_num, sbh, bucket_alloc); if (current_conn) { + current_conn->current_thread = thd; ap_process_connection(current_conn, csd); ap_lingering_close(current_conn); } @@ -605,7 +614,6 @@ 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; @@ -616,7 +624,6 @@ static void perform_idle_server_maintenance(apr_pool_t *p) /* initialize the free_list */ free_length = 0; - to_kill = -1; idle_count = 0; last_non_dead = -1; total_non_dead = 0; @@ -648,13 +655,6 @@ static void perform_idle_server_maintenance(apr_pool_t *p) */ 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; @@ -680,16 +680,16 @@ static void perform_idle_server_maintenance(apr_pool_t *p) 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"); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00220) + "server reached MaxRequestWorkers setting, consider" + " raising the MaxRequestWorkers setting"); reported = 1; } idle_spawn_rate = 1; } else { if (idle_spawn_rate >= 8) { - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00221) "server seems busy, (you may need " "to increase StartServers, or Min/MaxSpareServers), " "spawning %d children, there are %d idle, and " @@ -717,7 +717,7 @@ static void perform_idle_server_maintenance(apr_pool_t *p) } } -static void display_settings () +static void display_settings() { int status_array[SERVER_NUM_STATUS]; int i, status, total=0; @@ -830,7 +830,7 @@ static int setup_listeners(server_rec *s) int sockdes; if (ap_setup_listeners(s) < 1 ) { - ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00222) "no listening sockets available, shutting down"); return -1; } @@ -862,7 +862,7 @@ static int shutdown_listeners() * Executive routines. */ -int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +static int netware_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) { apr_status_t status=0; @@ -870,7 +870,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) ap_server_conf = s; if (setup_listeners(s)) { - ap_log_error(APLOG_MARK, APLOG_ALERT, status, s, + ap_log_error(APLOG_MARK, APLOG_ALERT, status, s, APLOGNO(00223) "no listening sockets available, shutting down"); return -1; } @@ -886,6 +886,11 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) /* Only set slot 0 since that is all NetWare will ever have. */ ap_scoreboard_image->parent[0].pid = getpid(); + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[0].pid, + ap_my_generation, + 0, + MPM_CHILD_STARTED); set_signals(); @@ -905,17 +910,12 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) hold_screen_on_exit = 0; } - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00224) "%s configured -- resuming normal operations", ap_get_server_description()); - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00225) "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 + ap_log_command_line(plog, s); show_server_data(); mpm_state = AP_MPMQ_RUNNING; @@ -928,12 +928,17 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) } mpm_state = AP_MPMQ_STOPPING; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[0].pid, + ap_my_generation, + 0, + MPM_CHILD_EXITED); /* 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, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00226) "caught SIGTERM, shutting down"); while (worker_thread_count > 0) { @@ -953,7 +958,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) ++ap_my_generation; ap_scoreboard_image->global->running_generation = ap_my_generation; - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00227) "Graceful restart requested, doing restart"); /* Wait for all of the threads to terminate before initiating the restart */ @@ -971,13 +976,10 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) 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); @@ -995,22 +997,115 @@ static int netware_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp 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; + + /* override core's default thread stacksize */ 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 int netware_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + static int restart_num = 0; + int startup = 0; + + /* we want this only the first time around */ + if (restart_num++ == 0) { + startup = 1; + } + + if (ap_threads_limit > HARD_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00228) + "WARNING: MaxThreads of %d exceeds compile-time " + "limit of", ap_threads_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + HARD_THREAD_LIMIT, HARD_THREAD_LIMIT); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the HARD_THREAD_LIMIT" + "define in"); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " server/mpm/netware%s.", MPM_HARD_LIMITS_FILE); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00229) + "MaxThreads of %d exceeds compile-time limit " + "of %d, decreasing to match", + ap_threads_limit, HARD_THREAD_LIMIT); + } + ap_threads_limit = HARD_THREAD_LIMIT; + } + else if (ap_threads_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + APLOGNO(00230) "WARNING: MaxThreads of %d not allowed, " + "increasing to 1.", ap_threads_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, + "MaxThreads of %d not allowed, increasing to 1", + ap_threads_limit); + } + ap_threads_limit = 1; + } + + /* ap_threads_to_start > ap_threads_limit effectively checked in + * call to startup_workers(ap_threads_to_start) in ap_mpm_run() + */ + if (ap_threads_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00231) + "WARNING: StartThreads of %d not allowed, " + "increasing to 1.", ap_threads_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00232) + "StartThreads of %d not allowed, increasing to 1", + ap_threads_to_start); + } + ap_threads_to_start = 1; + } + + if (ap_threads_min_free < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00233) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1", ap_threads_min_free); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " to avoid almost certain server failure."); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " Please read the documentation."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00234) + "MinSpareThreads of %d not allowed, increasing to 1", + ap_threads_min_free); + } + ap_threads_min_free = 1; + } + + /* ap_threads_max_free < ap_threads_min_free + 1 checked in ap_mpm_run() */ return OK; } static void netware_mpm_hooks(apr_pool_t *p) { - ap_hook_pre_config(netware_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + /* Run the pre-config hook after core's so that it can override the + * default setting of ThreadStackSize for NetWare. + */ + static const char * const predecessors[] = {"core.c", NULL}; + + ap_hook_pre_config(netware_pre_config, predecessors, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(netware_check_config, NULL, NULL, APR_HOOK_MIDDLE); + //ap_hook_post_config(netware_post_config, NULL, NULL, 0); + //ap_hook_child_init(netware_child_init, NULL, NULL, APR_HOOK_MIDDLE); + //ap_hook_open_logs(netware_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(netware_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(netware_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(netware_get_name, NULL, NULL, APR_HOOK_MIDDLE); } -void netware_rewrite_args(process_rec *process) +static void netware_rewrite_args(process_rec *process) { char *def_server_root; char optbuf[3]; @@ -1058,7 +1153,7 @@ void netware_rewrite_args(process_rec *process) optbuf[0] = '-'; optbuf[2] = '\0'; - apr_getopt_init(&opt, process->pool, process->argc, (char**) process->argv); + apr_getopt_init(&opt, process->pool, process->argc, process->argv); while (apr_getopt(opt, AP_SERVER_BASEARGS"n:", optbuf + 1, &opt_arg) == APR_SUCCESS) { switch (optbuf[1]) { case 'n': @@ -1099,7 +1194,7 @@ static int CommandLineInterpreter(scr_t screenID, const char *commandLine) if (strlen(commandLine) <= strlen(szCommand)) return NOTMYCOMMAND; - strncpy (szcommandLine, commandLine, sizeof(szcommandLine)-1); + apr_cpystrn(szcommandLine, commandLine, sizeof(szcommandLine)); /* All added commands begin with "APACHE2 " */ @@ -1224,16 +1319,6 @@ static const char *set_min_free_threads(cmd_parms *cmd, void *dummy, const char } 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; } @@ -1256,23 +1341,6 @@ static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *ar } 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; } @@ -1289,7 +1357,7 @@ AP_INIT_TAKE1("MaxThreads", set_thread_limit, NULL, RSRC_CONF, { NULL } }; -module AP_MODULE_DECLARE_DATA mpm_netware_module = { +AP_DECLARE_MODULE(mpm_netware) = { MPM20_MODULE_STUFF, netware_rewrite_args, /* hook to run before apache parses args */ NULL, /* create per-directory config structure */ diff --git a/server/mpm/prefork/Makefile.in b/server/mpm/prefork/Makefile.in index 034bf5ce..f34af9cb 100644 --- a/server/mpm/prefork/Makefile.in +++ b/server/mpm/prefork/Makefile.in @@ -1,5 +1 @@ - -LTLIBRARY_NAME = libprefork.la -LTLIBRARY_SOURCES = prefork.c - -include $(top_srcdir)/build/ltlib.mk +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/prefork/config.m4 b/server/mpm/prefork/config.m4 index 9c189a86..296f834a 100644 --- a/server/mpm/prefork/config.m4 +++ b/server/mpm/prefork/config.m4 @@ -1,3 +1,7 @@ -if test "$MPM_NAME" = "prefork" ; then - APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile) +AC_MSG_CHECKING(if prefork MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(prefork, yes, no) fi diff --git a/server/mpm/prefork/config3.m4 b/server/mpm/prefork/config3.m4 new file mode 100644 index 00000000..25fd8df3 --- /dev/null +++ b/server/mpm/prefork/config3.m4 @@ -0,0 +1 @@ +APACHE_MPM_MODULE(prefork, $enable_mpm_prefork) diff --git a/server/mpm/prefork/mpm.h b/server/mpm/prefork/mpm.h deleted file mode 100644 index bf1fb949..00000000 --- a/server/mpm/prefork/mpm.h +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 index d548a7ec..5f131c5a 100644 --- a/server/mpm/prefork/mpm_default.h +++ b/server/mpm/prefork/mpm_default.h @@ -46,29 +46,5 @@ #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 index 108f961a..5de88e32 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -31,8 +31,6 @@ #include <sys/types.h> #endif -#define CORE_PRIVATE - #include "ap_config.h" #include "httpd.h" #include "mpm_default.h" @@ -43,15 +41,13 @@ #include "http_connection.h" #include "scoreboard.h" #include "ap_mpm.h" +#include "util_mutex.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 @@ -90,25 +86,46 @@ /* 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 ap_daemons_limit=0; /* MaxRequestWorkers */ +static int server_limit = 0; 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. +/* data retained by prefork across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook */ -int ap_max_daemons_limit = -1; -server_rec *ap_server_conf; +typedef struct prefork_retained_data { + int first_server_limit; + int module_loads; + ap_generation_t my_generation; + int volatile is_graceful; /* set from signal handler */ + int maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. We + * use this value to optimize routines that have to scan the entire scoreboard. + */ + int max_daemons_limit; + /* + * 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. + */ + int idle_spawn_rate; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif + int hold_off_on_exponential_spawning; +} prefork_retained_data; +static prefork_retained_data *retained; + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) /* 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 @@ -128,17 +145,7 @@ 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 /* @@ -150,7 +157,7 @@ static volatile int die_now = 0; static void chdir_for_gprof(void) { core_server_config *sconf = - ap_get_module_config(ap_server_conf->module_config, &core_module); + ap_get_core_module_config(ap_server_conf->module_config); char *dir = sconf->gprof_dir; const char *use_dir; @@ -168,7 +175,7 @@ static void chdir_for_gprof(void) 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, + ap_log_error(APLOG_MARK, APLOG_ERR, res, ap_server_conf, APLOGNO(00142) "gprof: error creating directory %s", dir); } } @@ -182,9 +189,24 @@ static void chdir_for_gprof(void) #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() +static void prefork_note_child_killed(int childnum, pid_t pid, + ap_generation_t gen) +{ + AP_DEBUG_ASSERT(childnum != -1); /* no scoreboard squatting with this MPM */ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[childnum].pid, + ap_scoreboard_image->parent[childnum].generation, + childnum, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[childnum].pid = 0; +} + +static void prefork_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + retained->my_generation, slot, MPM_CHILD_STARTED); +} /* a clean exit from a child with proper cleanup */ static void clean_child_exit(int code) __attribute__ ((noreturn)); @@ -195,6 +217,11 @@ static void clean_child_exit(int code) if (pchild) { apr_pool_destroy(pchild); } + + if (one_process) { + prefork_note_child_killed(/* slot */ 0, 0, 0); + } + ap_mpm_pod_close(pod); chdir_for_gprof(); exit(code); @@ -206,13 +233,13 @@ static void accept_mutex_on(void) if (rv != APR_SUCCESS) { const char *msg = "couldn't grab the accept mutex"; - if (ap_my_generation != + if (retained->my_generation != ap_scoreboard_image->global->running_generation) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, NULL, "%s", msg); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00143) "%s", msg); clean_child_exit(0); } else { - ap_log_error(APLOG_MARK, APLOG_EMERG, rv, NULL, "%s", msg); + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00144) "%s", msg); exit(APEXIT_CHILDFATAL); } } @@ -224,16 +251,16 @@ static void accept_mutex_off(void) if (rv != APR_SUCCESS) { const char *msg = "couldn't release the accept mutex"; - if (ap_my_generation != + if (retained->my_generation != ap_scoreboard_image->global->running_generation) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, NULL, "%s", msg); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00145) "%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); + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00146) "%s", msg); exit(APEXIT_CHILDFATAL); } } @@ -250,74 +277,63 @@ static void accept_mutex_off(void) #define SAFE_ACCEPT(stmt) do {stmt;} while(0) #endif -AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +static int prefork_query(int query_code, int *result, apr_status_t *rv) { + *rv = APR_SUCCESS; switch(query_code){ case AP_MPMQ_MAX_DAEMON_USED: *result = ap_daemons_limit; - return APR_SUCCESS; + break; case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_NOT_SUPPORTED; - return APR_SUCCESS; + break; case AP_MPMQ_IS_FORKED: *result = AP_MPMQ_DYNAMIC; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_DAEMONS: *result = server_limit; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_THREADS: *result = HARD_THREAD_LIMIT; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_THREADS: - *result = 0; - return APR_SUCCESS; + *result = 1; + break; case AP_MPMQ_MIN_SPARE_DAEMONS: *result = ap_daemons_min_free; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_THREADS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_DAEMONS: *result = ap_daemons_max_free; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_THREADS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_REQUESTS_DAEMON: *result = ap_max_requests_per_child; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_DAEMONS: - *result = server_limit; - return APR_SUCCESS; + *result = ap_daemons_limit; + break; case AP_MPMQ_MPM_STATE: *result = mpm_state; - return APR_SUCCESS; + break; + case AP_MPMQ_GENERATION: + *result = retained->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; } - return APR_ENOTIMPL; + return OK; } -#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) +static const char *prefork_get_name(void) { - 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; + return "prefork"; } -#endif /***************************************************************** * Connection structures and accounting... @@ -328,6 +344,11 @@ static void just_die(int sig) clean_child_exit(0); } +/* volatile because they're updated from a signal handler */ +static int volatile shutdown_pending; +static int volatile restart_pending; +static int volatile die_now = 0; + static void stop_listening(int sig) { mpm_state = AP_MPMQ_STOPPING; @@ -337,11 +358,6 @@ static void stop_listening(int sig) 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) { @@ -353,7 +369,7 @@ static void sig_term(int sig) } mpm_state = AP_MPMQ_STOPPING; shutdown_pending = 1; - is_graceful = (sig == AP_SIG_GRACEFUL_STOP); + retained->is_graceful = (sig == AP_SIG_GRACEFUL_STOP); } /* restart() is the signal handler for SIGHUP and AP_SIG_GRACEFUL @@ -367,7 +383,7 @@ static void restart(int sig) } mpm_state = AP_MPMQ_STOPPING; restart_pending = 1; - is_graceful = (sig == AP_SIG_GRACEFUL); + retained->is_graceful = (sig == AP_SIG_GRACEFUL); } static void set_signals(void) @@ -386,30 +402,33 @@ static void set_signals(void) sa.sa_handler = sig_term; if (sigaction(SIGTERM, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGTERM)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00147) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00148) "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)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00149) "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)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00150) "sigaction(SIGXCPU)"); #endif #ifdef SIGXFSZ - sa.sa_handler = SIG_DFL; + /* For systems following the LFS standard, ignoring SIGXFSZ allows + * a write() beyond the 2GB limit to fail gracefully with E2BIG + * rather than terminate the process. */ + sa.sa_handler = SIG_IGN; if (sigaction(SIGXFSZ, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "sigaction(SIGXFSZ)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00151) "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)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00152) "sigaction(SIGPIPE)"); #endif /* we want to ignore HUPs and AP_SIG_GRACEFUL while we're busy @@ -419,16 +438,16 @@ static void set_signals(void) 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)"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00153) "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 ")"); + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00154) "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); + apr_signal(SIGXFSZ, SIG_IGN); #endif /* SIGXFSZ */ } @@ -458,18 +477,12 @@ static void set_signals(void) static int requests_this_child; static int num_listensocks = 0; - -int ap_graceful_stop_signalled(void) -{ - /* Return true if the server is stopping for whatever reason; the - * function is used to initiate a fast exit from the connection - * processing loop. */ - return mpm_state == AP_MPMQ_STOPPING; -} - - static void child_main(int child_num_arg) { +#if APR_HAS_THREADS + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; +#endif apr_pool_t *ptrans; apr_allocator_t *allocator; apr_status_t status; @@ -479,6 +492,7 @@ static void child_main(int child_num_arg) ap_sb_handle_t *sbh; apr_bucket_alloc_t *bucket_alloc; int last_poll_idx = 0; + const char *lockfile; mpm_state = AP_MPMQ_STARTING; /* for benefit of any hooks that run as this * child initializes @@ -497,21 +511,32 @@ static void child_main(int child_num_arg) 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_tag(pchild, "pchild"); + +#if APR_HAS_THREADS + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); +#endif 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); + lockfile = apr_proc_mutex_lockfile(accept_mutex); + status = apr_proc_mutex_child_init(&accept_mutex, + lockfile, + pchild); if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00155) "Couldn't initialize cross-process lock in child " - "(%s) (%d)", ap_lock_fname, ap_accept_lock_mech); + "(%s) (%s)", + lockfile ? lockfile : "none", + apr_proc_mutex_name(accept_mutex)); clean_child_exit(APEXIT_CHILDFATAL); } - if (unixd_setup_child()) { + if (ap_run_drop_privileges(pchild, ap_server_conf)) { clean_child_exit(APEXIT_CHILDFATAL); } @@ -524,7 +549,7 @@ static void child_main(int child_num_arg) /* Set up the pollfd array */ status = apr_pollset_create(&pollset, num_listensocks, pchild, 0); if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00156) "Couldn't create pollset in child; check system or user limits"); clean_child_exit(APEXIT_CHILDSICK); /* assume temporary resource issue */ } @@ -537,8 +562,14 @@ static void child_main(int child_num_arg) pfd.reqevents = APR_POLLIN; pfd.client_data = lr; - /* ### check the status */ - (void) apr_pollset_add(pollset, &pfd); + status = apr_pollset_add(pollset, &pfd); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(00157) + "Couldn't add listener to pollset; check system or user limits"); + clean_child_exit(APEXIT_CHILDSICK); + } + + lr->accept_func = ap_unixd_accept; } mpm_state = AP_MPMQ_RUNNING; @@ -585,20 +616,16 @@ static void child_main(int child_num_arg) /* check for termination first so we don't sleep for a while in * poll if already signalled */ - if (one_process && shutdown_pending) { - SAFE_ACCEPT(accept_mutex_off()); - return; - } - else if (die_now) { - /* In graceful stop/restart; drop the mutex - * and terminate the child. */ + if (die_now /* in graceful stop/restart */ + || (one_process && shutdown_pending)) { SAFE_ACCEPT(accept_mutex_off()); clean_child_exit(0); } + /* timeout == 10 seconds to avoid a hang at graceful restart/stop * caused by the closing of sockets by the signal handler */ - status = apr_pollset_poll(pollset, apr_time_from_sec(10), + status = apr_pollset_poll(pollset, apr_time_from_sec(10), &numdesc, &pdesc); if (status != APR_SUCCESS) { if (APR_STATUS_IS_TIMEUP(status) || @@ -612,7 +639,7 @@ static void child_main(int child_num_arg) * occasionally, and we'd loop forever due to it. */ ap_log_error(APLOG_MARK, APLOG_ERR, status, - ap_server_conf, "apr_pollset_poll: (listen)"); + ap_server_conf, APLOGNO(00158) "apr_pollset_poll: (listen)"); SAFE_ACCEPT(accept_mutex_off()); clean_child_exit(1); } @@ -664,6 +691,9 @@ static void child_main(int child_num_arg) current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, my_child_num, sbh, bucket_alloc); if (current_conn) { +#if APR_HAS_THREADS + current_conn->current_thread = thd; +#endif ap_process_connection(current_conn, csd); ap_lingering_close(current_conn); } @@ -676,7 +706,7 @@ static void child_main(int child_num_arg) if (ap_mpm_pod_check(pod) == APR_SUCCESS) { /* selected as idle? */ die_now = 1; } - else if (ap_my_generation != + else if (retained->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? @@ -684,11 +714,7 @@ static void child_main(int child_num_arg) die_now = 1; } } - /* This apr_pool_clear call is redundant, should be redundant, but compensates - * a flaw in the apr reslist code. This should be removed once that flaw has - * been addressed. - */ - apr_pool_clear(ptrans); + apr_pool_clear(ptrans); /* kludge to avoid crash in APR reslist cleanup code */ clean_child_exit(0); } @@ -697,8 +723,8 @@ 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 (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; } if (one_process) { @@ -709,8 +735,9 @@ static int make_child(server_rec *s, int slot) apr_signal(SIGQUIT, SIG_DFL); #endif apr_signal(SIGTERM, sig_term); + prefork_note_child_started(slot, getpid()); child_main(slot); - return 0; + /* NOTREACHED */ } (void) ap_update_child_status_from_indexes(slot, 0, SERVER_STARTING, @@ -719,13 +746,11 @@ static int make_child(server_rec *s, int slot) #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) { + if ((pid = os_fork(ap_unixd_config.user_name)) == -1) { #else if ((pid = fork()) == -1) { #endif - ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, "fork: Unable to fork new process"); + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00159) "fork: Unable to fork new process"); /* fork didn't succeed. Fix the scoreboard or else * it will say SERVER_STARTING forever and ever @@ -750,8 +775,8 @@ static int make_child(server_rec *s, int slot) 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); + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00160) "processor unbind failed"); } #endif RAISE_SIGSTOP(MAKE_CHILD); @@ -768,7 +793,7 @@ static int make_child(server_rec *s, int slot) child_main(slot); } - ap_scoreboard_image->parent[slot].pid = pid; + prefork_note_child_started(slot, pid); return 0; } @@ -790,23 +815,9 @@ static void startup_children(int 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; @@ -817,7 +828,6 @@ static void perform_idle_server_maintenance(apr_pool_t *p) /* initialize the free_list */ free_length = 0; - to_kill = -1; idle_count = 0; last_non_dead = -1; total_non_dead = 0; @@ -825,13 +835,13 @@ static void perform_idle_server_maintenance(apr_pool_t *p) for (i = 0; i < ap_daemons_limit; ++i) { int status; - if (i >= ap_max_daemons_limit && free_length == idle_spawn_rate) + if (i >= retained->max_daemons_limit && free_length == retained->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) { + if (free_length < retained->idle_spawn_rate) { free_slots[free_length] = i; ++free_length; } @@ -845,77 +855,58 @@ static void perform_idle_server_maintenance(apr_pool_t *p) */ 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; + retained->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; + retained->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; + if (!retained->maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00161) + "server reached MaxRequestWorkers setting, consider" + " raising the MaxRequestWorkers setting"); + retained->maxclients_reported = 1; } - idle_spawn_rate = 1; + retained->idle_spawn_rate = 1; } else { - if (idle_spawn_rate >= 8) { - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + if (retained->idle_spawn_rate >= 8) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00162) "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, + "%d total children", retained->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; + if (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; } - else if (idle_spawn_rate < MAX_SPAWN_RATE) { - idle_spawn_rate *= 2; + else if (retained->idle_spawn_rate < MAX_SPAWN_RATE) { + retained->idle_spawn_rate *= 2; } } } else { - idle_spawn_rate = 1; + retained->idle_spawn_rate = 1; } } @@ -923,7 +914,7 @@ static void perform_idle_server_maintenance(apr_pool_t *p) * Executive routines. */ -int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) { int index; int remaining_children_to_start; @@ -931,61 +922,32 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) 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); + rv = ap_proc_mutex_create(&accept_mutex, NULL, AP_ACCEPT_MUTEX_TYPE, NULL, + s, _pconf, 0); 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; - } + return DONE; } - if (!is_graceful) { + if (!retained->is_graceful) { if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { mpm_state = AP_MPMQ_STOPPING; - return 1; + return DONE; } /* fix the generation number in the global score; we just got a new, * cleared scoreboard */ - ap_scoreboard_image->global->running_generation = ap_my_generation; + ap_scoreboard_image->global->running_generation = retained->my_generation; } + restart_pending = shutdown_pending = 0; set_signals(); if (one_process) { AP_MONCONTROL(1); make_child(ap_server_conf, 0); + /* NOTREACHED */ } else { if (ap_daemons_max_free < ap_daemons_min_free + 1) /* Don't thrash... */ @@ -1003,7 +965,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) if (remaining_children_to_start > ap_daemons_limit) { remaining_children_to_start = ap_daemons_limit; } - if (!is_graceful) { + if (!retained->is_graceful) { startup_children(remaining_children_to_start); remaining_children_to_start = 0; } @@ -1011,21 +973,19 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) /* give the system some time to recover before kicking into * exponential mode */ - hold_off_on_exponential_spawning = 10; + retained->hold_off_on_exponential_spawning = 10; } - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00163) "%s configured -- resuming normal operations", ap_get_server_description()); - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00164) "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)", + ap_log_command_line(plog, s); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00165) + "Accept mutex: %s (default: %s)", apr_proc_mutex_name(accept_mutex), apr_proc_mutex_defname()); -#endif - restart_pending = shutdown_pending = 0; mpm_state = AP_MPMQ_RUNNING; @@ -1036,7 +996,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) /* this is a memory leak, but I'll fix it later. */ apr_proc_t pid; - ap_wait_or_timeout(&exitwhy, &status, &pid, pconf); + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); /* 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 @@ -1044,21 +1004,37 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) */ if (pid.pid != -1) { processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); if (processed_status == APEXIT_CHILDFATAL) { - mpm_state = AP_MPMQ_STOPPING; - return 1; + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->my_generation) { + mpm_state = AP_MPMQ_STOPPING; + return DONE; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00166) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + } } /* 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); + prefork_note_child_killed(child_slot, 0, 0); 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; + retained->idle_spawn_rate = 1; } else if (remaining_children_to_start && child_slot < ap_daemons_limit) { @@ -1074,13 +1050,13 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) /* handled */ #endif } - else if (is_graceful) { + else if (retained->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, + 0, ap_server_conf, APLOGNO(00167) "long lost child came home! (pid %ld)", (long)pid.pid); } /* Don't perform idle maintenance when a child dies, @@ -1105,40 +1081,27 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) } 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) { + if (shutdown_pending && !retained->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"); + if (ap_unixd_killpg(getpgrp(), SIGTERM) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00168) "killpg SIGTERM"); } - ap_reclaim_child_processes(1); /* Start with SIGTERM */ + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + prefork_note_child_killed); /* 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, + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00169) "caught SIGTERM, shutting down"); - return 1; + return DONE; } else if (shutdown_pending) { /* Time to perform a graceful shut down: * Reap the inactive children, and ask the active ones @@ -1152,7 +1115,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) ap_close_listeners(); /* kill off the idle ones */ - ap_mpm_pod_killpg(pod, ap_max_daemons_limit); + ap_mpm_pod_killpg(pod, retained->max_daemons_limit); /* Send SIGUSR1 to the active children */ active_children = 0; @@ -1165,20 +1128,11 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) } /* Allow each child which actually finished to exit */ - ap_relieve_child_processes(); + ap_relieve_child_processes(prefork_note_child_killed); /* 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, + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00170) "caught " AP_SIG_GRACEFUL_STOP_STRING ", shutting down gracefully"); if (ap_graceful_shutdown_timeout) { @@ -1193,7 +1147,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) sleep(1); /* Relieve any children which have now exited */ - ap_relieve_child_processes(); + ap_relieve_child_processes(prefork_note_child_killed); active_children = 0; for (index = 0; index < ap_daemons_limit; ++index) { @@ -1210,9 +1164,9 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) * way, try and make sure that all of our processes are * really dead. */ - unixd_killpg(getpgrp(), SIGTERM); + ap_unixd_killpg(getpgrp(), SIGTERM); - return 1; + return DONE; } /* we've been told to restart */ @@ -1220,22 +1174,22 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) apr_signal(AP_SIG_GRACEFUL, SIG_IGN); if (one_process) { /* not worth thinking about */ - return 1; + return DONE; } /* 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; + ++retained->my_generation; + ap_scoreboard_image->global->running_generation = retained->my_generation; - if (is_graceful) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + if (retained->is_graceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00171) "Graceful restart requested, doing restart"); /* kill off the idle ones */ - ap_mpm_pod_killpg(pod, ap_max_daemons_limit); + ap_mpm_pod_killpg(pod, retained->max_daemons_limit); /* This is mostly for debugging... so that we know what is still * gracefully dealing with existing request. This will break @@ -1258,15 +1212,16 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) } else { /* Kill 'em off */ - if (unixd_killpg(getpgrp(), SIGHUP) < 0) { - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "killpg SIGHUP"); + if (ap_unixd_killpg(getpgrp(), SIGHUP) < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00172) "killpg SIGHUP"); } - ap_reclaim_child_processes(0); /* Not when just starting up */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_reclaim_child_processes(0, /* Not when just starting up */ + prefork_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00173) "SIGHUP received. Attempting to restart"); } - return 0; + return OK; } /* This really should be a post_config hook, but the error log is already @@ -1274,20 +1229,29 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) */ static int prefork_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { + int startup = 0; + int level_flags = 0; apr_status_t rv; pconf = p; - ap_server_conf = s; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } 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"); + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "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."); + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); return DONE; } return OK; @@ -1295,9 +1259,9 @@ static int prefork_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, 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; + const char *userdata_key = "mpm_prefork_module"; mpm_state = AP_MPMQ_STARTING; @@ -1314,59 +1278,182 @@ static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp foreground = ap_exists_config_define("FOREGROUND"); } - /* sigh, want this only the second time around */ - if (restart_num++ == 1) { - is_graceful = 0; + ap_mutex_register(p, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); + /* sigh, want this only the second time around */ + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->max_daemons_limit = -1; + retained->idle_spawn_rate = 1; + } + ++retained->module_loads; + if (retained->module_loads == 2) { if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00174) "apr_proc_detach failed"); return HTTP_INTERNAL_SERVER_ERROR; } } - - parent_pid = ap_my_pid = getpid(); } - unixd_pre_config(ptemp); + parent_pid = ap_my_pid = getpid(); + 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; + server_limit = DEFAULT_SERVER_LIMIT; 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 int prefork_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00175) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of", server_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d servers, decreasing to %d.", + MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00176) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00177) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00178) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00179) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00180) + "WARNING: MaxRequestWorkers of %d exceeds ServerLimit " + "value of", ap_daemons_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d servers, decreasing MaxRequestWorkers to %d.", + server_limit, server_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the ServerLimit " + "directive."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00181) + "MaxRequestWorkers of %d exceeds ServerLimit value " + "of %d, decreasing to match", + ap_daemons_limit, server_limit); + } + ap_daemons_limit = server_limit; + } + else if (ap_daemons_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00182) + "WARNING: MaxRequestWorkers of %d not allowed, " + "increasing to 1.", ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00183) + "MaxRequestWorkers of %d not allowed, increasing to 1", + ap_daemons_limit); + } + ap_daemons_limit = 1; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in prefork_run() */ + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00184) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00185) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (ap_daemons_min_free < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00186) + "WARNING: MinSpareServers of %d not allowed, " + "increasing to 1", ap_daemons_min_free); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " to avoid almost certain server failure."); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " Please read the documentation."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00187) + "MinSpareServers of %d not allowed, increasing to 1", + ap_daemons_min_free); + } + ap_daemons_min_free = 1; + } + + /* ap_daemons_max_free < ap_daemons_min_free + 1 checked in prefork_run() */ return OK; } static void prefork_hooks(apr_pool_t *p) { - /* The prefork open_logs phase must run before the core's, or stderr + /* Our open_logs hook function 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); + ap_hook_open_logs(prefork_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); /* 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); + ap_hook_check_config(prefork_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(prefork_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(prefork_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(prefork_get_name, NULL, NULL, APR_HOOK_MIDDLE); } static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, const char *arg) @@ -1388,16 +1475,6 @@ static const char *set_min_free_servers(cmd_parms *cmd, void *dummy, const char } 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; } @@ -1418,69 +1495,27 @@ static const char *set_max_clients (cmd_parms *cmd, void *dummy, const char *arg 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; + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00188) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); } + ap_daemons_limit = atoi(arg); 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; - } + server_limit = atoi(arg); 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"), @@ -1489,16 +1524,18 @@ AP_INIT_TAKE1("MinSpareServers", set_min_free_servers, NULL, RSRC_CONF, 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, + "Deprecated name of MaxRequestWorkers"), +AP_INIT_TAKE1("MaxRequestWorkers", 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"), + "Maximum value of MaxRequestWorkers for this run of Apache"), AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, { NULL } }; -module AP_MODULE_DECLARE_DATA mpm_prefork_module = { +AP_DECLARE_MODULE(mpm_prefork) = { MPM20_MODULE_STUFF, - ap_mpm_rewrite_args, /* hook to run before apache parses args */ + 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 */ diff --git a/server/mpm/winnt/Makefile.in b/server/mpm/winnt/Makefile.in index 38957c87..f34af9cb 100644 --- a/server/mpm/winnt/Makefile.in +++ b/server/mpm/winnt/Makefile.in @@ -1,5 +1 @@ - -LTLIBRARY_NAME = libwinnt.la -LTLIBRARY_SOURCES = mpm_winnt.c child.c nt_eventlog.c service.c - -include $(top_srcdir)/build/ltlib.mk +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/winnt/Win9xConHook.c b/server/mpm/winnt/Win9xConHook.c deleted file mode 100644 index 0dbcf80d..00000000 --- a/server/mpm/winnt/Win9xConHook.c +++ /dev/null @@ -1,697 +0,0 @@ -/* 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 deleted file mode 100644 index 85ec1664..00000000 --- a/server/mpm/winnt/Win9xConHook.def +++ /dev/null @@ -1,10 +0,0 @@ -LIBRARY Win9xConHook - -EXETYPE WINDOWS - -EXPORTS - DllMain - GetMsgProc - CallWndProc - FixConsoleCtrlHandler - Windows9xServiceCtrlHandler diff --git a/server/mpm/winnt/Win9xConHook.dsp b/server/mpm/winnt/Win9xConHook.dsp deleted file mode 100644 index 55bee020..00000000 --- a/server/mpm/winnt/Win9xConHook.dsp +++ /dev/null @@ -1,115 +0,0 @@ -# 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 -# Begin Special Build Tool -TargetPath=.\Release\Win9xConHook.dll -SOURCE="$(InputPath)" -PostBuild_Desc=Embed .manifest -PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 -# End Special Build Tool - -!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" -# Begin Special Build Tool -TargetPath=.\Debug\Win9xConHook.dll -SOURCE="$(InputPath)" -PostBuild_Desc=Embed .manifest -PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 -# End Special Build Tool - -!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 deleted file mode 100644 index 8b42da57..00000000 --- a/server/mpm/winnt/Win9xConHook.h +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 index 82cec95f..ab4fddc3 100644 --- a/server/mpm/winnt/child.c +++ b/server/mpm/winnt/child.c @@ -16,13 +16,15 @@ #ifdef WIN32 -#define CORE_PRIVATE +#include "apr.h" +#include <process.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 "http_vhost.h" /* for ap_update_vhost_given_ip */ #include "apr_portable.h" #include "apr_thread_proc.h" #include "apr_getopt.h" @@ -38,27 +40,62 @@ #include "mpm_common.h" #include <malloc.h> #include "apr_atomic.h" - -#include <process.h> +#include "apr_buckets.h" +#include "scoreboard.h" #ifdef __MINGW32__ #include <mswsock.h> -#endif - -/* shared with mpm_winnt.c */ -extern DWORD my_pid; +#endif -/* 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; +/* + * The Windows 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 *)( \ + (char *)(address) - \ + (char *)(&((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 -/* child_main() should never need to modify is_graceful!?! */ -extern int volatile is_graceful; +APLOG_USE_MODULE(mpm_winnt); -/* Queue for managing the passing of COMP_CONTEXTs between +/* Queue for managing the passing of winnt_conn_ctx_t between * the accept and worker threads. */ +typedef struct winnt_conn_ctx_t_s { + struct winnt_conn_ctx_t_s *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; + apr_bucket *data; +#if APR_HAVE_IPV6 + short socket_family; +#endif +} winnt_conn_ctx_t; + +typedef enum { + IOCP_CONNECTION_ACCEPTED = 1, + IOCP_WAIT_FOR_RECEIVE = 2, + IOCP_WAIT_FOR_TRANSMITFILE = 3, + IOCP_SHUTDOWN = 4 +} io_state_e; + static apr_pool_t *pchild; static int shutdown_in_progress = 0; static int workers_may_exit = 0; @@ -67,15 +104,14 @@ 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 winnt_conn_ctx_t *qhead = NULL; +static winnt_conn_ctx_t *qtail = NULL; static apr_uint32_t num_completion_contexts = 0; static apr_uint32_t max_num_completion_contexts = 0; static HANDLE ThreadDispatchIOCP = NULL; static HANDLE qwait_event = NULL; - -void mpm_recycle_completion_context(PCOMP_CONTEXT context) +static void mpm_recycle_completion_context(winnt_conn_ctx_t *context) { /* Recycle the completion context. * - clear the ptrans pool @@ -88,7 +124,7 @@ void mpm_recycle_completion_context(PCOMP_CONTEXT context) apr_pool_clear(context->ptrans); context->ba = apr_bucket_alloc_create(context->ptrans); context->next = NULL; - ResetEvent(context->Overlapped.hEvent); + ResetEvent(context->overlapped.hEvent); apr_thread_mutex_lock(qlock); if (qtail) { qtail->next = context; @@ -101,11 +137,12 @@ void mpm_recycle_completion_context(PCOMP_CONTEXT context) } } -PCOMP_CONTEXT mpm_get_completion_context(void) +static winnt_conn_ctx_t *mpm_get_completion_context(int *timeout) { apr_status_t rv; - PCOMP_CONTEXT context = NULL; + winnt_conn_ctx_t *context = NULL; + *timeout = 0; while (1) { /* Grab a context off the queue */ apr_thread_mutex_lock(qlock); @@ -129,51 +166,74 @@ PCOMP_CONTEXT mpm_get_completion_context(void) /* 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"); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00326) + "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. + * 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 */ + else { + if (rv == WAIT_TIMEOUT) { + /* somewhat-normal condition where threads are busy */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00327) + "mpm_get_completion_context: Failed to get a " + "free context within 1 second"); + *timeout = 1; + } + else { + /* should be the unexpected, generic WAIT_FAILED */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00328) + "mpm_get_completion_context: " + "WaitForSingleObject failed to get free context"); + } 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... + * 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 = (winnt_conn_ctx_t *)apr_pcalloc(pchild, + sizeof(winnt_conn_ctx_t)); - context->Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (context->Overlapped.hEvent == NULL) { + + 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."); + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00329) + "mpm_get_completion_context: " + "CreateEvent failed."); apr_thread_mutex_unlock(child_lock); return NULL; } - /* Create the tranaction pool */ + /* Create the transaction 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); + 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); + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00330) + "mpm_get_completion_context: Failed " + "to create the transaction pool."); + CloseHandle(context->overlapped.hEvent); apr_thread_mutex_unlock(child_lock); return NULL; @@ -197,285 +257,6 @@ PCOMP_CONTEXT mpm_get_completion_context(void) 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 %lu: 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"); - apr_thread_mutex_unlock(child_lock); - } - - while (1) { - apr_pool_clear(context->ptrans); - context->ba = apr_bucket_alloc_create(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 @@ -491,203 +272,424 @@ static PCOMP_CONTEXT win9x_get_connection(PCOMP_CONTEXT context) * Worker threads block on the ThreadDispatch IOCompletionPort awaiting * connections to service. */ -#define MAX_ACCEPTEX_ERR_COUNT 100 +#define MAX_ACCEPTEX_ERR_COUNT 10 + 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; + winnt_conn_ctx_t *context = NULL; DWORD BytesRead; SOCKET nlsd; - int rv, err_count = 0; + core_server_config *core_sconf; + const char *accf_name; + int rv; + int accf; + int err_count = 0; + HANDLE events[3]; #if APR_HAVE_IPV6 SOCKADDR_STORAGE ss_listen; int namelen = sizeof(ss_listen); #endif + u_long zero = 0; + + core_sconf = ap_get_core_module_config(ap_server_conf->module_config); + accf_name = apr_table_get(core_sconf->accf_map, lr->protocol); + + if (strcmp(accf_name, "data") == 0) + accf = 2; + else if (strcmp(accf_name, "connect") == 0) + accf = 1; + else if (strcmp(accf_name, "none") == 0) + accf = 0; + else { + accf = 0; + accf_name = "none"; + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00331) + "winnt_accept: unrecognized AcceptFilter '%s', " + "only 'data', 'connect' or 'none' are valid. " + "Using 'none' instead", accf_name); + } 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?"); + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(00332) + "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 %lu: Starting thread to listen on port %d.", my_pid, lr->bind_addr->port); + if (accf > 0) /* 'data' or 'connect' */ + { + /* first, high priority event is an already accepted connection */ + events[1] = exit_event; + events[2] = max_requests_per_child_event; + } + else /* accf == 0, 'none' */ + { +reinit: /* target of data or connect upon too many AcceptEx failures */ + + /* last, low priority event is a not yet accepted connection */ + events[0] = exit_event; + events[1] = max_requests_per_child_event; + events[2] = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* The event needs to be removed from the accepted socket, + * if not removed from the listen socket prior to accept(), + */ + rv = WSAEventSelect(nlsd, events[2], FD_ACCEPT); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, + apr_get_netos_error(), ap_server_conf, APLOGNO(00333) + "WSAEventSelect() failed."); + CloseHandle(events[2]); + return 1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00334) + "Child: Accept thread listening on %s:%d using AcceptFilter %s", + lr->bind_addr->hostname ? lr->bind_addr->hostname : "*", + lr->bind_addr->port, accf_name); + while (!shutdown_in_progress) { if (!context) { - context = mpm_get_completion_context(); + int timeout; + + context = mpm_get_completion_context(&timeout); if (!context) { - /* Temporary resource constraint? */ - Sleep(0); + if (!timeout) { + /* Hopefully a temporary condition in the provider? */ + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00335) + "winnt_accept: Too many failures grabbing a " + "connection ctx. Aborting."); + break; + } + } + Sleep(100); 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 (accf > 0) /* Either 'connect' or 'data' */ + { + DWORD len; + char *buf; - 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; - } + /* 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; + } #else - if (context->accept_socket == INVALID_SOCKET) { - context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (context->accept_socket == INVALID_SOCKET) + context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + if (context->accept_socket == INVALID_SOCKET) { - /* Another temporary condition? */ - ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00336) "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 %lu: 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; + + if (accf == 2) { /* 'data' */ + len = APR_BUCKET_BUFF_SIZE; + buf = apr_bucket_alloc(len, context->ba); + len -= PADDED_ADDR_SIZE * 2; } - 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 %lu: 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; + else /* (accf == 1) 'connect' */ { + len = 0; + buf = context->buff; } - 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 + /* AcceptEx on the completion context. The completion context will be + * signaled when a connection is accepted. */ - while (1) { - rv = WaitForSingleObject(context->Overlapped.hEvent, 1000); - if (rv == WAIT_OBJECT_0) { - if (context->accept_socket == INVALID_SOCKET) { - /* socket already closed */ - break; + if (!AcceptEx(nlsd, context->accept_socket, buf, len, + PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, &BytesRead, + &context->overlapped)) { + rv = apr_get_netos_error(); + if ((rv == APR_FROM_OS_ERROR(WSAECONNRESET)) || + (rv == APR_FROM_OS_ERROR(WSAEACCES))) { + /* We can get here when: + * 1) the client disconnects early + * 2) handshake was incomplete + */ + if (accf == 2) + apr_bucket_free(buf); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + continue; + } + else if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) || + (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) { + /* We can get here when: + * 1) TransmitFile does not properly recycle the accept socket (typically + * because the client disconnected) + * 2) there is VPN or Firewall software installed with + * buggy WSAAccept or WSADuplicateSocket implementation + * 3) the dynamic address / adapter has changed + * Give five chances, then fall back on AcceptFilter 'none' + */ + if (accf == 2) + apr_bucket_free(buf); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00337) + "Child: Encountered too many AcceptEx " + "faults accepting client connections. " + "Possible causes: dynamic address renewal, " + "or incompatible VPN or firewall software. "); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00338) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = 0; } - if (!GetOverlappedResult((HANDLE)context->accept_socket, - &context->Overlapped, + continue; + } + else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) && + (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) { + if (accf == 2) + apr_bucket_free(buf); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00339) + "Child: Encountered too many AcceptEx " + "faults accepting client connections."); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00340) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = 0; + goto reinit; + } + continue; + } + + err_count = 0; + events[0] = context->overlapped.hEvent; + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + if (rv == WAIT_OBJECT_0) { + if ((context->accept_socket != INVALID_SOCKET) && + !GetOverlappedResult((HANDLE)context->accept_socket, + &context->overlapped, &BytesRead, FALSE)) { ap_log_error(APLOG_MARK, APLOG_WARNING, - apr_get_os_error(), ap_server_conf, + apr_get_os_error(), ap_server_conf, APLOGNO(00341) "winnt_accept: Asynchronous AcceptEx failed."); closesocket(context->accept_socket); context->accept_socket = INVALID_SOCKET; } - break; } - /* WAIT_TIMEOUT */ - if (shutdown_in_progress) { + else { + /* exit_event triggered or event handle was closed */ closesocket(context->accept_socket); context->accept_socket = INVALID_SOCKET; + if (accf == 2) + apr_bucket_free(buf); break; } + + if (context->accept_socket == INVALID_SOCKET) { + if (accf == 2) + apr_bucket_free(buf); + continue; + } + } + err_count = 0; + + /* Potential optimization; consider handing off to the worker */ + + /* 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, APLOGNO(00342) + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed."); + /* Not a failure condition. Keep running. */ + } + + /* Get the local & remote address + * TODO; error check + */ + GetAcceptExSockaddrs(buf, len, PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, + &context->sa_server, &context->sa_server_len, + &context->sa_client, &context->sa_client_len); + + /* For 'data', craft a bucket for our data result + * and pass to worker_main as context->overlapped.Pointer + */ + if (accf == 2 && BytesRead) + { + apr_bucket *b; + b = apr_bucket_heap_create(buf, APR_BUCKET_BUFF_SIZE, + apr_bucket_free, context->ba); + /* Adjust the bucket to refer to the actual bytes read */ + b->length = BytesRead; + context->overlapped.Pointer = b; } + else + context->overlapped.Pointer = NULL; + } + else /* (accf = 0) e.g. 'none' */ + { + /* There is no socket reuse without AcceptEx() */ + if (context->accept_socket != INVALID_SOCKET) + closesocket(context->accept_socket); + + /* This could be a persistent event per-listener rather than + * per-accept. However, the event needs to be removed from + * the target socket if not removed from the listen socket + * prior to accept(), or the event select is inherited. + * and must be removed from the accepted socket. + */ + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + + if (rv != WAIT_OBJECT_0 + 2) { + /* not FD_ACCEPT; + * exit_event triggered or event handle was closed + */ + break; + } + + context->sa_server = (void *) context->buff; + context->sa_server_len = sizeof(context->buff) / 2; + context->sa_client_len = context->sa_server_len; + context->sa_client = (void *) (context->buff + + context->sa_server_len); + + context->accept_socket = accept(nlsd, context->sa_server, + &context->sa_server_len); + if (context->accept_socket == INVALID_SOCKET) { + + rv = apr_get_netos_error(); + if ( rv == APR_FROM_OS_ERROR(WSAECONNRESET) + || rv == APR_FROM_OS_ERROR(WSAEINPROGRESS) + || rv == APR_FROM_OS_ERROR(WSAEWOULDBLOCK) ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, + rv, ap_server_conf, APLOGNO(00343) + "accept() failed, retrying."); + continue; + } + + /* A more serious error than 'retry', log it */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + rv, ap_server_conf, APLOGNO(00344) + "accept() failed."); + + if ( rv == APR_FROM_OS_ERROR(WSAEMFILE) + || rv == APR_FROM_OS_ERROR(WSAENOBUFS) ) { + /* Hopefully a temporary condition in the provider? */ + Sleep(100); + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00345) + "Child: Encountered too many accept() " + "resource faults, aborting."); + break; + } + continue; + } + break; + } + /* Per MSDN, cancel the inherited association of this socket + * to the WSAEventSelect API, and restore the state corresponding + * to apr_os_sock_make's default assumptions (really, a flaw within + * os_sock_make and os_sock_put that it does not query). + */ + WSAEventSelect(context->accept_socket, 0, 0); + context->overlapped.Pointer = NULL; + err_count = 0; + + context->sa_server_len = sizeof(context->buff) / 2; + if (getsockname(context->accept_socket, context->sa_server, + &context->sa_server_len) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00346) + "getsockname failed"); continue; } + if ((getpeername(context->accept_socket, context->sa_client, + &context->sa_client_len)) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00347) + "getpeername failed"); + memset(&context->sa_client, '\0', sizeof(context->sa_client)); + } } - 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; + /* Restore the state corresponding to apr_os_sock_make's default + * assumption of timeout -1 (really, a flaw of os_sock_make and + * os_sock_put that it does not query to determine ->timeout). + * XXX: Upon a fix to APR, these three statements should disappear. + */ + ioctlsocket(context->accept_socket, FIONBIO, &zero); + setsockopt(context->accept_socket, SOL_SOCKET, SO_RCVTIMEO, + (char *) &zero, sizeof(zero)); + setsockopt(context->accept_socket, SOL_SOCKET, SO_SNDTIMEO, + (char *) &zero, sizeof(zero)); 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... + /* When a connection is received, send an io completion notification + * to the ThreadDispatchIOCP. */ - PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_CONNECTION_ACCEPTED, - &context->Overlapped); + PostQueuedCompletionStatus(ThreadDispatchIOCP, BytesRead, + IOCP_CONNECTION_ACCEPTED, + &context->overlapped); context = NULL; } + if (!accf) + CloseHandle(events[2]); + 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 %lu: Accept thread exiting.", my_pid); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00348) + "Child: Accept thread exiting."); return 0; } -static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context) +static winnt_conn_ctx_t *winnt_get_connection(winnt_conn_ctx_t *context) { int rc; DWORD BytesRead; @@ -706,18 +708,19 @@ static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context) apr_atomic_dec32(&g_blocked_threads); return NULL; } - rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, &CompKey, - &pol, INFINITE); + 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 %lu: GetQueuedComplationStatus returned %d", my_pid, rc); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, ap_server_conf, APLOGNO(00349) + "Child: GetQueuedComplationStatus returned %d", + rc); continue; } switch (CompKey) { case IOCP_CONNECTION_ACCEPTED: - context = CONTAINING_RECORD(pol, COMP_CONTEXT, Overlapped); + context = CONTAINING_RECORD(pol, winnt_conn_ctx_t, overlapped); break; case IOCP_SHUTDOWN: apr_atomic_dec32(&g_blocked_threads); @@ -739,33 +742,35 @@ static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context) * 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 DWORD __stdcall worker_main(void *thread_num_val) { + apr_thread_t *thd = NULL; + apr_os_thread_t osthd; static int requests_this_child = 0; - PCOMP_CONTEXT context = NULL; + winnt_conn_ctx_t *context = NULL; int thread_num = (int)thread_num_val; ap_sb_handle_t *sbh; + apr_bucket *e; + int rc; + conn_rec *c; + apr_int32_t disconnected; + + osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); 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); - } + context = winnt_get_connection(context); if (!context) { /* Time for the thread to exit */ break; } - /* Have we hit MaxRequestPerChild connections? */ + /* Have we hit MaxConnectionsPerChild connections? */ if (ap_max_requests_per_child) { requests_this_child++; if (requests_this_child > ap_max_requests_per_child) { @@ -773,33 +778,81 @@ static unsigned int __stdcall worker_main(void *thread_num_val) } } + e = context->overlapped.Pointer; + 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); + if (!c) + { + /* ap_run_create_connection closes the socket on failure */ + context->accept_socket = INVALID_SOCKET; + if (e) + apr_bucket_free(e); + continue; + } + + c->current_thread = thd; + + /* follow ap_process_connection(c, context->sock) logic + * as it left us no chance to reinject our first data bucket. + */ + ap_update_vhost_given_ip(c); + + rc = ap_run_pre_connection(c, context->sock); + if (rc != OK && rc != DONE) { + c->aborted = 1; + } + + if (e && c->aborted) + { + apr_bucket_free(e); + } + else if (e) + { + core_ctx_t *ctx; + core_net_rec *net; + ap_filter_t *filt; + + filt = c->input_filters; + while ((strcmp(filt->frec->name, "core_in") != 0) && filt->next) + filt = filt->next; + net = filt->ctx; + ctx = net->in_ctx; + + if (net->in_ctx) + ctx = net->in_ctx; + else + { + ctx = apr_pcalloc(c->pool, sizeof(*ctx)); + ctx->b = apr_brigade_create(c->pool, c->bucket_alloc); + ctx->tmpbb = apr_brigade_create(c->pool, c->bucket_alloc); + + /* seed the brigade with AcceptEx read heap bucket */ + e = context->overlapped.Pointer; + APR_BRIGADE_INSERT_HEAD(ctx->b, e); + + /* also seed the brigade with the client socket. */ + e = apr_bucket_socket_create(net->client_socket, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(ctx->b, e); + net->in_ctx = ctx; + } + } + + if (!c->aborted) + { + ap_run_process_connection(c); + 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; } } @@ -810,7 +863,8 @@ static unsigned int __stdcall worker_main(void *thread_num_val) } -static void cleanup_thread(HANDLE *handles, int *thread_cnt, int thread_to_clean) +static void cleanup_thread(HANDLE *handles, int *thread_cnt, + int thread_to_clean) { int i; @@ -832,33 +886,32 @@ static void create_listener_thread(void) { unsigned 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; + /* Start an accept thread per listener + * XXX: Why would we have a NULL sd in our listeners? + */ + ap_listen_rec *lr; - /* 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); - } + /* 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) { + /* A smaller stack is sufficient. + * To convert to CreateThread, the returned handle cannot be + * ignored, it must be closed/joined. + */ + _beginthreadex(NULL, 65536, winnt_accept, + (void *) lr, stack_res_flag, &tid); } } } @@ -876,7 +929,7 @@ void child_main(apr_pool_t *pconf) int watch_thread; int time_remains; int cld; - unsigned tid; + DWORD tid; int rv; int i; @@ -889,17 +942,13 @@ void child_main(apr_pool_t *pconf) /* 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 %lu: Failed to create a max_requests event.", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00350) + "Child: Failed to create a max_requests event."); 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 @@ -907,38 +956,36 @@ void child_main(apr_pool_t *pconf) */ status = apr_proc_mutex_lock(start_mutex); if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK,APLOG_ERR, status, ap_server_conf, - "Child %lu: Failed to acquire the start_mutex. Process will exit.", my_pid); + ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(00351) + "Child: Failed to acquire the start_mutex. " + "Process will exit."); exit(APEXIT_CHILDINIT); } - ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Acquired the start mutex.", my_pid); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00352) + "Child: Acquired the start mutex."); /* * 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 %lu: Failed to create a qwait event.", my_pid); - exit(APEXIT_CHILDINIT); - } + /* Create the worker thread dispatch IOCP */ + ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, 0, 0); + 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, APLOGNO(00353) + "Child: Failed to create a qwait event."); + exit(APEXIT_CHILDINIT); } /* * Create the pool of worker threads */ - ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Starting %d worker threads.", my_pid, ap_threads_per_child); - child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child * sizeof(HANDLE)); + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00354) + "Child: Starting %d worker threads.", 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) { @@ -949,19 +996,24 @@ void child_main(apr_pool_t *pconf) 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); + + child_handles[i] = CreateThread(NULL, ap_thread_stacksize, + worker_main, (void *) i, + stack_res_flag, &tid); if (child_handles[i] == 0) { - ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, - "Child %lu: _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_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00355) + "Child: CreateThread failed. Unable to " + "create all worker threads. Created %d of the %d " + "threads requested with the ThreadsPerChild " + "configuration directive.", + 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... + /* 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)); @@ -982,7 +1034,8 @@ void child_main(apr_pool_t *pconf) if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { break; } - /* wait for previous generation to clean up an entry in the scoreboard */ + /* wait for previous generation to clean up an entry in the scoreboard + */ apr_sleep(1 * APR_USEC_PER_SEC); } @@ -993,7 +1046,7 @@ void child_main(apr_pool_t *pconf) * * max_requests_per_child_event: * This event is signaled by the worker threads to indicate that - * the process has handled MaxRequestsPerChild connections. + * the process has handled MaxConnectionsPerChild connections. * * TIMEOUT: * To do periodic maintenance on the server (check for thread exits, @@ -1012,10 +1065,10 @@ void child_main(apr_pool_t *pconf) */ while (1) { #if !APR_HAS_OTHER_CHILD - rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, INFINITE); + rv = WaitForMultipleObjects(2, (HANDLE *)child_events, FALSE, INFINITE); cld = rv - WAIT_OBJECT_0; #else - rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, 1000); + 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); @@ -1024,24 +1077,26 @@ void child_main(apr_pool_t *pconf) #endif if (rv == WAIT_FAILED) { /* Something serious is wrong */ - ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, - "Child %lu: WAIT_FAILED -- shutting down server", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00356) + "Child: WAIT_FAILED -- shutting down server"); break; } else if (cld == 0) { /* Exit event was signaled */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Exit event signaled. Child process is ending.", my_pid); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00357) + "Child: Exit event signaled. Child process is " + "ending."); break; } else { - /* MaxRequestsPerChild event set by the worker threads. + /* MaxConnectionsPerChild event set by the worker threads. * Signal the parent to restart */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Process exiting because it reached " - "MaxRequestsPerChild. Signaling the parent to " - "restart a new child process.", my_pid); + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00358) + "Child: Process exiting because it reached " + "MaxConnectionsPerChild. Signaling the parent to " + "restart a new child process."); ap_signal_parent(SIGNAL_PARENT_RESTART); break; } @@ -1054,10 +1109,6 @@ void child_main(apr_pool_t *pconf) 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 @@ -1067,7 +1118,7 @@ void child_main(apr_pool_t *pconf) apr_socket_close(lr->sd); } - /* Shutdown listener threads and pending AcceptEx socksts + /* Shutdown listener threads and pending AcceptEx sockets * but allow the worker threads to continue consuming from * the queue of accepted connections. */ @@ -1083,42 +1134,38 @@ void child_main(apr_pool_t *pconf) */ rv = apr_proc_mutex_unlock(start_mutex); if (rv == APR_SUCCESS) { - ap_log_error(APLOG_MARK,APLOG_NOTICE, rv, ap_server_conf, - "Child %lu: Released the start mutex", my_pid); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00359) + "Child: Released the start mutex"); } else { - ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, - "Child %lu: Failure releasing the start mutex", my_pid); + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00360) + "Child: Failure releasing the start mutex"); } - /* Shutdown the worker threads */ - if (!use_acceptex) { - for (i = 0; i < threads_created; i++) { - add_job(INVALID_SOCKET); + /* Shutdown the worker threads + * Post worker threads blocked on the ThreadDispatch IOCompletion port + */ + while (g_blocked_threads > 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00361) + "Child: %d threads blocked on the completion port", + g_blocked_threads); + for (i=g_blocked_threads; i > 0; i--) { + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, + IOCP_SHUTDOWN, NULL); } + Sleep(1000); } - 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 %lu: %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); + /* 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 threads a chance to service their connections, - * (no more than the global server timeout period which + /* Give busy threads a chance to service their connections + * (no more than the global server timeout period which * we track in msec remaining). */ watch_thread = 0; @@ -1139,11 +1186,11 @@ void child_main(apr_pool_t *pconf) /* Every 30 seconds give an update */ if ((time_remains % 30000) == 0) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, - ap_server_conf, - "Child %lu: Waiting %d more seconds " - "for %d worker threads to finish.", - my_pid, time_remains / 1000, threads_created); + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, + ap_server_conf, APLOGNO(00362) + "Child: Waiting %d more seconds " + "for %d worker threads to finish.", + time_remains / 1000, threads_created); } /* We'll poll from the top, 10 times per second */ Sleep(100); @@ -1181,12 +1228,12 @@ void child_main(apr_pool_t *pconf) cleanup_thread(child_handles, &threads_created, watch_thread); } } - + /* Kill remaining threads off the hard way */ if (threads_created) { - ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Terminating %d threads that failed to exit.", - my_pid, threads_created); + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363) + "Child: Terminating %d threads that failed to exit.", + threads_created); } for (i = 0; i < threads_created; i++) { int *score_idx; @@ -1195,21 +1242,15 @@ void child_main(apr_pool_t *pconf) /* Reset the scoreboard entry for the thread we just whacked */ score_idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE)); if (score_idx) { - ap_update_child_status_from_indexes(0, *score_idx, - SERVER_DEAD, NULL); + 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 %lu: All worker threads have exited.", my_pid); + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00364) + "Child: All worker threads have exited."); - 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_thread_mutex_destroy(qlock); + CloseHandle(qwait_event); apr_pool_destroy(pchild); CloseHandle(exit_event); diff --git a/server/mpm/winnt/config.m4 b/server/mpm/winnt/config.m4 index 181322b0..5c1fd42d 100644 --- a/server/mpm/winnt/config.m4 +++ b/server/mpm/winnt/config.m4 @@ -1,3 +1,10 @@ -if test "$MPM_NAME" = "winnt" ; then - APACHE_FAST_OUTPUT(server/mpm/$MPM_NAME/Makefile) -fi +AC_MSG_CHECKING(if WinNT MPM supports this platform) +case $host in + *mingw32*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(winnt, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/winnt/config3.m4 b/server/mpm/winnt/config3.m4 new file mode 100644 index 00000000..f937e404 --- /dev/null +++ b/server/mpm/winnt/config3.m4 @@ -0,0 +1,2 @@ +winnt_objects="child.lo mpm_winnt.lo nt_eventlog.lo service.lo" +APACHE_MPM_MODULE(winnt, $enable_mpm_winnt, $winnt_objects) diff --git a/server/mpm/winnt/mpm.h b/server/mpm/winnt/mpm.h deleted file mode 100644 index 536ee6aa..00000000 --- a/server/mpm/winnt/mpm.h +++ /dev/null @@ -1,49 +0,0 @@ -/* 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 index 990eb47a..a0e38efe 100644 --- a/server/mpm/winnt/mpm_default.h +++ b/server/mpm/winnt/mpm_default.h @@ -55,35 +55,5 @@ */ #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 index d9513175..cdf7cac6 100644 --- a/server/mpm/winnt/mpm_winnt.c +++ b/server/mpm/winnt/mpm_winnt.c @@ -16,7 +16,6 @@ #ifdef WIN32 -#define CORE_PRIVATE #include "httpd.h" #include "http_main.h" #include "http_log.h" @@ -31,6 +30,7 @@ #include "apr_shm.h" #include "apr_thread_mutex.h" #include "ap_mpm.h" +#include "apr_general.h" #include "ap_config.h" #include "ap_listen.h" #include "mpm_default.h" @@ -38,6 +38,15 @@ #include "mpm_common.h" #include <malloc.h> #include "apr_atomic.h" +#include "scoreboard.h" + +#ifdef __WATCOMC__ +#define _environ environ +#endif + +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION /* missing on MinGW */ +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 +#endif /* Because ap_setup_listeners() is skipped in the child, any merging * of [::]:80 and 0.0.0.0:80 for AP_ENABLE_V4_MAPPED in the parent @@ -53,49 +62,43 @@ * 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; + +/* my_generation is returned to the scoreboard code */ +static volatile ap_generation_t my_generation=0; /* 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 */ -char ap_coredump_dir[MAX_STRING_LEN]; - static int one_process = 0; static char const* signal_arg = NULL; OSVERSIONINFO osver; /* VER_PLATFORM_WIN32_NT */ +/* set by child_main to STACK_SIZE_PARAM_IS_A_RESERVATION for NT >= 5.1 (XP/2003) */ +DWORD stack_res_flag; + static DWORD parent_pid; DWORD my_pid; +/* used by parent to signal the child to start and exit */ +apr_proc_mutex_t *start_mutex; +HANDLE exit_event; + int ap_threads_per_child = 0; -int use_acceptex = 1; -static int thread_limit = DEFAULT_THREAD_LIMIT; +static int thread_limit = 0; 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; - -/* Only one of these, the pipe from our parent, ment only for +/* Only one of these, the pipe from our parent, meant only for * one child worker's consumption (not to be inherited!) * XXX: decorate this name for the trunk branch, was left simplified * only to make the 2.2 patch trivial to read. @@ -114,75 +117,16 @@ static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, const cha } 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) -{ - 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"); - } + thread_limit = atoi(arg); return NULL; } @@ -192,12 +136,25 @@ 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 } }; +static void winnt_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + my_generation, slot, MPM_CHILD_STARTED); +} + +static void winnt_note_child_killed(int slot) +{ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[slot].pid = 0; +} /* * Signalling Apache on NT. @@ -244,13 +201,6 @@ void setup_signal_names(char *prefix) "%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; @@ -267,7 +217,6 @@ AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) case SIGNAL_PARENT_RESTART: case SIGNAL_PARENT_RESTART_GRACEFUL: { - is_graceful = 1; SetEvent(restart_event); break; } @@ -286,7 +235,6 @@ AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) case SIGNAL_PARENT_RESTART_GRACEFUL: { signal_name = signal_restart_name; - is_graceful = 1; break; } default: @@ -298,13 +246,13 @@ AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) /* 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, + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00382) "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, + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00383) "SetEvent on %s event", signal_name); CloseHandle(e); return; @@ -338,8 +286,8 @@ static void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event, 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 %lu: Unable to retrieve the ready event from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00384) + "Child: Unable to retrieve the ready event from the parent"); exit(APEXIT_CHILDINIT); } @@ -349,45 +297,45 @@ static void get_handles_from_parent(server_rec *s, HANDLE *child_exit_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 %lu: Unable to retrieve the exit event from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00385) + "Child: Unable to retrieve the exit event from the parent"); 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 %lu: Unable to retrieve the start_mutex from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00386) + "Child: Unable to retrieve the start_mutex from the parent"); 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 %lu: Unable to access the start_mutex from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00387) + "Child: Unable to access the start_mutex from the parent"); 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 %lu: Unable to retrieve the scoreboard from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00388) + "Child: Unable to retrieve the scoreboard from the parent"); 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 %lu: Unable to access the scoreboard from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00389) + "Child: Unable to access the scoreboard from the parent"); 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 %lu: Unable to reopen the scoreboard from the parent", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00390) + "Child: Unable to reopen the scoreboard from the parent"); exit(APEXIT_CHILDINIT); } /* We must 'initialize' the scoreboard to relink all the @@ -395,8 +343,8 @@ static void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event, */ ap_init_scoreboard(sb_shared); - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, - "Child %lu: Retrieved our scoreboard from the parent.", my_pid); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00391) + "Child: Retrieved our scoreboard from the parent."); } @@ -417,64 +365,64 @@ static int send_handles_to_child(apr_pool_t *p, 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00392) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00393) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00394) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00395) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00396) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00397) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00398) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00399) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00400) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00401) "Parent: Unable to send the scoreboard handle to the child"); return -1; } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00402) "Parent: Sent the scoreboard to the child"); return 0; } @@ -510,49 +458,33 @@ static void get_listeners_from_parent(server_rec *s) * pipe = GetStdHandle(STD_INPUT_HANDLE); */ for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00403) + "Child: Waiting for data for listening socket %pI", + lr->bind_addr); 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"); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00404) + "Child: 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 %lu: setup_inherited_listeners(), WSASocket failed to open the inherited socket.", my_pid); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00405) + "Child: WSASocket failed to open the inherited socket"); 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."); - } + if (!SetHandleInformation((HANDLE)nsd, HANDLE_FLAG_INHERIT, 0)) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00406) + "Child: SetHandleInformation failed"); } apr_os_sock_put(&lr->sd, &nsd, s->process->pool); } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, - "Child %lu: retrieved %d listeners from parent", my_pid, lcnt); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00407) + "Child: retrieved %d listeners from parent", lcnt); } @@ -573,12 +505,12 @@ static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId, 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 %lu", - nsd, dwProcessId); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00408) + "Parent: Duplicating socket %d (%pI) and sending it to child process %lu", + nsd, lr->bind_addr, dwProcessId); if (WSADuplicateSocket(nsd, dwProcessId, lpWSAProtocolInfo) == SOCKET_ERROR) { - ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00409) "Parent: WSADuplicateSocket failed for socket %d. Check the FAQ.", nsd); return -1; } @@ -586,13 +518,13 @@ static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId, 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00410) "Parent: Unable to write duplicated socket %d to the child.", nsd); return -1; } } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00411) "Parent: Sent %d listeners to child %lu", lcnt, dwProcessId); return 0; } @@ -614,7 +546,6 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ apr_pool_t *ptemp; apr_procattr_t *attr; apr_proc_t new_child; - apr_file_t *child_out, *child_err; HANDLE hExitEvent; HANDLE waitlist[2]; /* see waitlist_e */ char *cmd; @@ -633,7 +564,7 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00412) "Parent: Failed to get the current path"); } @@ -643,7 +574,7 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ */ if ((rv = ap_os_proc_filepath(&cmd, ptemp)) != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf, APLOGNO(00413) "Parent: Failed to get full path of %s", ap_server_conf->process->argv[0]); apr_pool_destroy(ptemp); @@ -664,33 +595,16 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ /* 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00414) "Parent: Unable to create child stdin pipe."); apr_pool_destroy(ptemp); return -1; } - /* httpd-2.0/2.2 specific to work around apr_proc_create bugs */ - /* set "NUL" as sysout for the child */ - if (((rv = apr_file_open(&child_out, "NUL", APR_WRITE | APR_READ, APR_OS_DEFAULT,p)) - != APR_SUCCESS) || - ((rv = apr_procattr_child_out_set(attr, child_out, NULL)) - != APR_SUCCESS)) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, - "Parent: Could not set child process stdout"); - } - if (((rv = apr_file_open_stderr(&child_err, p)) - != APR_SUCCESS) || - ((rv = apr_procattr_child_err_set(attr, child_err, NULL)) - != APR_SUCCESS)) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, - "Parent: Could not set child process stderr"); - } - /* 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00415) "Parent: Could not create ready event for child process"); apr_pool_destroy (ptemp); return -1; @@ -699,7 +613,7 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ /* 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00416) "Parent: Could not create exit event for child process"); apr_pool_destroy(ptemp); CloseHandle(waitlist[waitlist_ready]); @@ -710,16 +624,16 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ for (envc = 0; _environ[envc]; ++envc) { ; } - env = apr_palloc(ptemp, (envc + 2) * sizeof (char*)); + env = apr_palloc(ptemp, (envc + 2) * sizeof (char*)); memcpy(env, _environ, envc * sizeof (char*)); apr_snprintf(pidbuf, sizeof(pidbuf), "AP_PARENT_PID=%lu", parent_pid); env[envc] = pidbuf; env[envc + 1] = NULL; - rv = apr_proc_create(&new_child, cmd, (const char * const *)args, + rv = apr_proc_create(&new_child, cmd, (const char * const *)args, (const char * const *)env, attr, ptemp); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00417) "Parent: Failed to create the child process."); apr_pool_destroy(ptemp); CloseHandle(hExitEvent); @@ -727,8 +641,8 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ CloseHandle(new_child.hproc); return -1; } - apr_file_close(child_out); - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00418) "Parent: Created child process %d", new_child.pid); if (send_handles_to_child(ptemp, waitlist[waitlist_ready], hExitEvent, @@ -805,7 +719,7 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ * 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. + * as a result of hitting MaxConnectionsPerChild. * * shutdown_event * -------------- @@ -853,7 +767,7 @@ static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_even &child_exit_event, &child_pid); if (rv < 0) { - ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00419) "master_main: create child process failed. Exiting."); shutdown_pending = 1; goto die_now; @@ -869,7 +783,7 @@ static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_even * child at once. */ ap_scoreboard_image->parent[0].quiescing = 0; - ap_scoreboard_image->parent[0].pid = child_pid; + winnt_note_child_started(/* slot */ 0, child_pid); /* Wait for shutdown or restart events or for child death */ winnt_mpm_state = AP_MPMQ_RUNNING; @@ -877,23 +791,23 @@ static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_even 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"); + ap_log_error(APLOG_MARK,APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00420) + "master_main: WaitForMultipleObjects 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"); + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00421) + "master_main: WaitForMultipleObjects 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, + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, s, APLOGNO(00422) "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, + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00423) "ResetEvent(shutdown_event)"); } } @@ -902,14 +816,14 @@ static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_even * then signal the child process to exit. */ restart_pending = 1; - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00424) "Parent: Received restart signal -- Restarting the server."); if (ResetEvent(restart_event) == 0) { - ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00425) "Parent: ResetEvent(restart_event) failed."); } if (SetEvent(child_exit_event) == 0) { - ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00426) "Parent: SetEvent for child process %pp failed.", event_handles[CHILD_HANDLE]); } @@ -929,14 +843,14 @@ static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_even if ( exitcode == APEXIT_CHILDFATAL || exitcode == APEXIT_CHILDINIT || exitcode == APEXIT_INIT) { - ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00427) "Parent: child process exited with status %lu -- Aborting.", exitcode); shutdown_pending = 1; } else { int i; restart_pending = 1; - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00428) "Parent: child process exited with status %lu -- Restarting.", exitcode); for (i = 0; i < ap_threads_per_child; i++) { ap_update_child_status_from_indexes(0, i, SERVER_DEAD, NULL); @@ -945,9 +859,12 @@ static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_even CloseHandle(event_handles[CHILD_HANDLE]); event_handles[CHILD_HANDLE] = NULL; } + + winnt_note_child_killed(/* slot */ 0); + if (restart_pending) { - ++ap_my_generation; - ap_scoreboard_image->global->running_generation = ap_my_generation; + ++my_generation; + ap_scoreboard_image->global->running_generation = my_generation; } die_now: if (shutdown_pending) @@ -968,20 +885,20 @@ die_now: } /* 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, + ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00429) "Parent: SetEvent for child process %pp 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, + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00430) "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, + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00431) "Parent: Forcing termination of child process %pp", event_handles[CHILD_HANDLE]); TerminateProcess(event_handles[CHILD_HANDLE], 1); @@ -1007,50 +924,62 @@ apr_array_header_t *mpm_new_argv; * service after we preflight the config. */ -AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +static int winnt_query(int query_code, int *result, apr_status_t *rv) { - switch(query_code){ + *rv = APR_SUCCESS; + switch (query_code) { case AP_MPMQ_MAX_DAEMON_USED: *result = MAXIMUM_WAIT_OBJECTS; - return APR_SUCCESS; + break; case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_STATIC; - return APR_SUCCESS; + break; case AP_MPMQ_IS_FORKED: *result = AP_MPMQ_NOT_SUPPORTED; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_DAEMONS: *result = HARD_SERVER_LIMIT; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_THREADS: *result = thread_limit; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_THREADS: *result = ap_threads_per_child; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_THREADS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_THREADS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_REQUESTS_DAEMON: *result = ap_max_requests_per_child; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_DAEMONS: - *result = 0; - return APR_SUCCESS; + *result = 1; + break; case AP_MPMQ_MPM_STATE: *result = winnt_mpm_state; - return APR_SUCCESS; + break; + case AP_MPMQ_GENERATION: + *result = my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; } - return APR_ENOTIMPL; + return OK; +} + +static const char *winnt_get_name(void) +{ + return "WinNT"; } #define SERVICE_UNSET (-1) @@ -1064,7 +993,7 @@ static void winnt_rewrite_args(process_rec *process) { /* Handle the following SCM aspects in this phase: * - * -k runservice [transition for WinNT, nothing for Win9x] + * -k runservice [transition in service context only] * -k install * -k config * -k uninstall @@ -1081,18 +1010,28 @@ static void winnt_rewrite_args(process_rec *process) char *def_server_root; char *binpath; char optbuf[3]; - const char *optarg; + const char *opt_arg; int fixed_args; char *pid; apr_getopt_t *opt; int running_as_service = 1; int errout = 0; + apr_file_t *nullfile; pconf = process->pconf; osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osver); + /* We wish this was *always* a reservation, but sadly it wasn't so and + * we couldn't break a hard limit prior to NT Kernel 5.1 + */ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT + && ((osver.dwMajorVersion > 5) + || ((osver.dwMajorVersion == 5) && (osver.dwMinorVersion > 0)))) { + stack_res_flag = STACK_SIZE_PARAM_IS_A_RESERVATION; + } + /* AP_PARENT_PID is only valid in the child */ pid = getenv("AP_PARENT_PID"); if (pid) @@ -1133,9 +1072,10 @@ static void winnt_rewrite_args(process_rec *process) } /* This child needs the existing stderr opened for logging, - * already + * already */ + /* The parent is responsible for providing the * COMPLETE ARGUMENTS REQUIRED to the child. * @@ -1168,7 +1108,7 @@ static void winnt_rewrite_args(process_rec *process) */ if ((rv = ap_os_proc_filepath(&binpath, process->pconf)) != APR_SUCCESS) { - ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL, + ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL, APLOGNO(00432) "Failed to get the full path of %s", process->argv[0]); exit(APEXIT_INIT); } @@ -1203,7 +1143,7 @@ static void winnt_rewrite_args(process_rec *process) apr_getopt_init(&opt, process->pool, process->argc, process->argv); opt->errfn = NULL; while ((rv = apr_getopt(opt, "wn:k:" AP_SERVER_BASEARGS, - optbuf + 1, &optarg)) == APR_SUCCESS) { + optbuf + 1, &opt_arg)) == APR_SUCCESS) { switch (optbuf[1]) { /* Shortcuts; include the -w option to hold the window open on error. @@ -1216,11 +1156,11 @@ static void winnt_rewrite_args(process_rec *process) case 'n': service_set = mpm_service_set_name(process->pool, &service_name, - optarg); + opt_arg); break; case 'k': - signal_arg = optarg; + signal_arg = opt_arg; break; case 'E': @@ -1230,8 +1170,8 @@ static void winnt_rewrite_args(process_rec *process) *(const char **)apr_array_push(mpm_new_argv) = apr_pstrdup(process->pool, optbuf); - if (optarg) { - *(const char **)apr_array_push(mpm_new_argv) = optarg; + if (opt_arg) { + *(const char **)apr_array_push(mpm_new_argv) = opt_arg; } break; } @@ -1280,33 +1220,28 @@ static void winnt_rewrite_args(process_rec *process) * 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) - { - apr_file_t *nullfile; - - 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; - } + 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; + } - /* Open a null handle to soak stdout in this process. - * Windows service processes are missing any file handle - * usable for stdin/out/err. This was the cause of later - * trouble with invocations of apr_file_open_stdout() - */ - if ((rv = apr_file_open(&nullfile, "NUL", - APR_READ | APR_WRITE, APR_OS_DEFAULT, - process->pool)) == APR_SUCCESS) { - apr_file_t *nullstdout; - if (apr_file_open_stdout(&nullstdout, process->pool) - == APR_SUCCESS) - apr_file_dup2(nullstdout, nullfile, process->pool); - apr_file_close(nullfile); - } + /* Open a null handle to soak stdout in this process. + * Windows service processes are missing any file handle + * usable for stdin/out/err. This was the cause of later + * trouble with invocations of apr_file_open_stdout() + */ + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + process->pool)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, process->pool) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, process->pool); + apr_file_close(nullfile); } } @@ -1320,7 +1255,7 @@ static void winnt_rewrite_args(process_rec *process) { if (service_set == APR_SUCCESS) { - ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL, + ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL, APLOGNO(00433) "%s: Service is already installed.", service_name); exit(APEXIT_INIT); } @@ -1346,26 +1281,26 @@ static void winnt_rewrite_args(process_rec *process) 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, + ap_log_error(APLOG_MARK,APLOG_INFO, 0, NULL, APLOGNO(00434) "Using ConfigArgs of the installed service " "\"%s\".", service_name); } else { - ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL, + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL, APLOGNO(00435) "No installed ConfigArgs for the service " "\"%s\", using Apache defaults.", service_name); } } else { - ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00436) "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, + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00437) "No installed service named \"%s\".", service_name); exit(APEXIT_INIT); } @@ -1424,34 +1359,137 @@ static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *pt ap_exists_config_define("DEBUG")) one_process = -1; + /* XXX: presume proper privilages; one nice thing would be + * a loud emit if running as "LocalSystem"/"SYSTEM" to indicate + * they should change to a user with write access to logs/ alone. + */ + ap_sys_privileges_handlers(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, + ap_log_error(APLOG_MARK,APLOG_CRIT, service_to_start_success, NULL, APLOGNO(00438) "%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; + else if (!one_process && !my_generation) { + /* Open a null handle to soak stdout in this process. + * We need to emulate apr_proc_detach, unix performs this + * same check in the pre_config hook (although it is + * arguably premature). Services already fixed this. + */ + apr_file_t *nullfile; + apr_status_t rv; + apr_pool_t *pproc = apr_pool_parent_get(pconf); + + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + pproc)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, pproc) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, pproc); + apr_file_close(nullfile); + } } ap_listen_pre_config(); + thread_limit = DEFAULT_THREAD_LIMIT; 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. + + return OK; +} + +static int winnt_check_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec* s) +{ + int is_parent; + static int restart_num = 0; + int startup = 0; + + /* We want this only in the parent and only the first time around */ + is_parent = (parent_pid == my_pid); + if (is_parent && restart_num++ == 0) { + startup = 1; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00439) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of", thread_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00440) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00441) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00442) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* You cannot change ThreadLimit across a restart; ignore + * any such attempts. */ - if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { - use_acceptex = 0; + if (!first_thread_limit) { + first_thread_limit = thread_limit; + } + else if (thread_limit != first_thread_limit) { + /* Don't need a startup console version here */ + if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00443) + "changing ThreadLimit to %d from original value " + "of %d not allowed during restart", + thread_limit, first_thread_limit); + } + thread_limit = first_thread_limit; } - apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir)); + if (ap_threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00444) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of", ap_threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + thread_limit, thread_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the ThreadLimit " + "directive."); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00445) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + ap_threads_per_child, thread_limit); + } + ap_threads_per_child = thread_limit; + } + else if (ap_threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00446) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", ap_threads_per_child); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00447) + "ThreadsPerChild of %d not allowed, increasing to 1", + ap_threads_per_child); + } + ap_threads_per_child = 1; + } return OK; } @@ -1524,7 +1562,7 @@ static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pt * across a restart */ PSECURITY_ATTRIBUTES sa = GetNullACL(); /* returns NULL if invalid (Win95?) */ - setup_signal_names(apr_psprintf(pconf,"ap%lu", parent_pid)); + setup_signal_names(apr_psprintf(pconf, "ap%lu", parent_pid)); ap_log_pid(pconf, ap_pid_fname); @@ -1533,7 +1571,7 @@ static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pt */ 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00448) "Parent: Cannot create shutdown event %s", signal_shutdown_name); CleanNullACL((void *)sa); return HTTP_INTERNAL_SERVER_ERROR; @@ -1545,33 +1583,13 @@ static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pt 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00449) "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; - } - } - } - /* 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. @@ -1580,14 +1598,14 @@ static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pt APR_LOCK_DEFAULT, ap_server_conf->process->pool); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00450) "%s: Unable to create the start_mutex.", service_name); return HTTP_INTERNAL_SERVER_ERROR; } } /* Always reset our console handler to be the first, even on a restart - * because some modules (e.g. mod_perl) might have set a console + * because some modules (e.g. mod_perl) might have set a console * handler to terminate the process. */ if (strcasecmp(signal_arg, "runservice")) @@ -1607,8 +1625,6 @@ static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, s { /* Initialize shared static objects. */ - ap_server_conf = s; - if (parent_pid != my_pid) { return OK; } @@ -1625,7 +1641,7 @@ static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, s if (ap_setup_listeners(s) < 1) { ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, - NULL, "no listening sockets available, shutting down"); + NULL, APLOGNO(00451) "no listening sockets available, shutting down"); return DONE; } @@ -1636,7 +1652,7 @@ static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) { apr_status_t rv; - setup_signal_names(apr_psprintf(pchild,"ap%lu", parent_pid)); + setup_signal_names(apr_psprintf(pchild, "ap%lu", parent_pid)); /* This is a child process, not in single process mode */ if (!one_process) { @@ -1650,16 +1666,16 @@ static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) /* Done reading from the parent, close that channel */ CloseHandle(pipe); - ap_my_generation = ap_scoreboard_image->global->running_generation; + 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 %lu: Unable to init the start_mutex.", - service_name, my_pid); + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00452) + "%s child: Unable to init the start_mutex.", + service_name); exit(APEXIT_CHILDINIT); } @@ -1669,21 +1685,10 @@ static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) } -AP_DECLARE(int) ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +static int winnt_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 @@ -1692,7 +1697,7 @@ AP_DECLARE(int) ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) 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; + return DONE; } } @@ -1700,63 +1705,62 @@ AP_DECLARE(int) ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) { /* The child process or in one_process (debug) mode */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Child process is running", my_pid); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00453) + "Child process is running"); child_main(pconf); - ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, - "Child %lu: Child process is exiting", my_pid); - return 1; + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00454) + "Child process is exiting"); + return DONE; } else { /* A real-honest to goodness parent */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00455) "%s configured -- resuming normal operations", ap_get_server_description()); - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00456) "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); 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()); - } + ap_remove_pid(pconf, ap_pid_fname); apr_proc_mutex_destroy(start_mutex); CloseHandle(restart_event); CloseHandle(shutdown_event); - return 1; + return DONE; } } - return 0; /* Restart */ + return OK; /* Restart */ } static void winnt_hooks(apr_pool_t *p) { - /* The prefork open_logs phase must run before the core's, or stderr + /* Our open_logs hook function 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_check_config(winnt_check_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_hook_open_logs(winnt_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(winnt_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(winnt_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(winnt_get_name, NULL, NULL, APR_HOOK_MIDDLE); } -AP_MODULE_DECLARE_DATA module mpm_winnt_module = { +AP_DECLARE_MODULE(mpm_winnt) = { MPM20_MODULE_STUFF, winnt_rewrite_args, /* hook to run before apache parses args */ NULL, /* create per-directory config structure */ diff --git a/server/mpm/winnt/mpm_winnt.h b/server/mpm/winnt/mpm_winnt.h index e21a6f43..62340d22 100644 --- a/server/mpm/winnt/mpm_winnt.h +++ b/server/mpm/winnt/mpm_winnt.h @@ -16,7 +16,7 @@ /** * @file mpm_winnt.h - * @brief WinNT MPM specific + * @brief WinNT MPM specific * * @addtogroup APACHE_MPM_WINNT * @{ @@ -25,6 +25,7 @@ #ifndef APACHE_MPM_WINNT_H #define APACHE_MPM_WINNT_H +#include "apr_proc_mutex.h" #include "ap_listen.h" /* From service.c: */ @@ -32,25 +33,24 @@ #define SERVICE_APACHE_RESTART 128 #ifndef AP_DEFAULT_SERVICE_NAME -#define AP_DEFAULT_SERVICE_NAME "Apache2.2" +#define AP_DEFAULT_SERVICE_NAME "Apache2.4" #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, +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, +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, +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, +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); @@ -65,11 +65,18 @@ void mpm_start_child_console_handler(void); void mpm_nt_eventlog_stderr_open(const char *display_name, apr_pool_t *p); void mpm_nt_eventlog_stderr_flush(void); -/* From winnt.c: */ +/* From mpm_winnt.c: */ + +extern int ap_threads_per_child; + +extern DWORD my_pid; +extern apr_proc_mutex_t *start_mutex; +extern HANDLE exit_event; -extern int use_acceptex; extern int winnt_mpm_state; extern OSVERSIONINFO osver; +extern DWORD stack_res_flag; + extern void clean_child_exit(int); void setup_signal_names(char *prefix); @@ -81,50 +88,6 @@ typedef enum { } 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); /* From child.c: */ diff --git a/server/mpm/winnt/nt_eventlog.c b/server/mpm/winnt/nt_eventlog.c index d8d758bb..e7e80d0d 100644 --- a/server/mpm/winnt/nt_eventlog.c +++ b/server/mpm/winnt/nt_eventlog.c @@ -14,8 +14,6 @@ * limitations under the License. */ -#define CORE_PRIVATE - #include "httpd.h" #include "http_log.h" #include "mpm_winnt.h" @@ -153,13 +151,13 @@ void mpm_nt_eventlog_stderr_open(const char *argv0, apr_pool_t *p) ap_assert(hPipeRead && hPipeWrite); stderr_ready = CreateEvent(NULL, FALSE, FALSE, NULL); - stderr_thread = CreateThread(NULL, 0, service_stderr_thread, - (LPVOID) hPipeRead, 0, &threadid); + stderr_thread = CreateThread(NULL, 65536, service_stderr_thread, + (LPVOID)hPipeRead, stack_res_flag, &threadid); ap_assert(stderr_ready && stderr_thread); WaitForSingleObject(stderr_ready, INFINITE); - if ((apr_file_open_stderr(&stderr_file, p) + if ((apr_file_open_stderr(&stderr_file, p) == APR_SUCCESS) && (apr_os_file_put(&eventlog_file, &hPipeWrite, APR_WRITE, p) == APR_SUCCESS)) diff --git a/server/mpm/winnt/service.c b/server/mpm/winnt/service.c index aa5291d2..bf5f021a 100644 --- a/server/mpm/winnt/service.c +++ b/server/mpm/winnt/service.c @@ -19,7 +19,6 @@ * preload the API symbols now... */ -#define CORE_PRIVATE #define _WINUSER_ #include "httpd.h" @@ -36,10 +35,13 @@ #include <winuser.h> #include <time.h> +APLOG_USE_MODULE(mpm_winnt); + +/* Todo; clear up statics */ static char *mpm_service_name = NULL; static char *mpm_display_name = NULL; -static struct +typedef struct nt_service_ctx_t { HANDLE mpm_thread; /* primary thread handle of the apache server */ HANDLE service_thread; /* thread service/monitor handle */ @@ -48,24 +50,12 @@ static struct HANDLE service_term; /* NT service thread kill signal */ SERVICE_STATUS ssStatus; SERVICE_STATUS_HANDLE hServiceStatus; -} globdat; +} nt_service_ctx_t; -static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint); - - -/* 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] - */ +static nt_service_ctx_t globdat; +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx); /* exit() for Win32 is macro mapped (horrible, we agree) that allows us * to catch the non-zero conditions and inform the console process that @@ -133,103 +123,6 @@ void hold_console_open_on_error(void) 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) @@ -254,7 +147,6 @@ static BOOL CALLBACK console_control_handler(DWORD ctrl_type) * 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); @@ -280,55 +172,9 @@ void mpm_start_console_handler(void) } -/* 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); - } + FreeConsole(); } @@ -336,59 +182,51 @@ void mpm_start_child_console_handler(void) WinNT service control management **********************************/ -static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint) +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx) { - static int checkPoint = 1; int rv = APR_SUCCESS; - if (globdat.hServiceStatus) + if (ctx->hServiceStatus) { if (currentState == SERVICE_RUNNING) { - globdat.ssStatus.dwWaitHint = 0; - globdat.ssStatus.dwCheckPoint = 0; - globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + ctx->ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP + | SERVICE_ACCEPT_SHUTDOWN; } 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; - } + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + /* An unexpected exit? Better to error! */ + if (ctx->ssStatus.dwCurrentState != SERVICE_STOP_PENDING + && !ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwServiceSpecificExitCode = 1; + if (ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; } else { - globdat.ssStatus.dwCheckPoint = ++checkPoint; - globdat.ssStatus.dwControlsAccepted = 0; + ++ctx->ssStatus.dwCheckPoint; + ctx->ssStatus.dwControlsAccepted = 0; if(waitHint) - globdat.ssStatus.dwWaitHint = waitHint; + ctx->ssStatus.dwWaitHint = waitHint; } - globdat.ssStatus.dwCurrentState = currentState; + ctx->ssStatus.dwCurrentState = currentState; - rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus); + rv = SetServiceStatus(ctx->hServiceStatus, &ctx->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. +/* Note this works on Win2000 and later due to ChangeServiceConfig2 + * Continue to test its existence, but at least drop the feature + * of revising service description tags prior to Win2000. */ /* 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; @@ -403,10 +241,8 @@ static void set_service_description(void) */ full_description = ap_get_server_description(); - if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT) - && (osver.dwMajorVersion > 4) - && (ChangeServiceConfig2) - && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT))) + if ((ChangeServiceConfig2) && + (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT))) { SC_HANDLE schService = OpenService(schSCManager, mpm_service_name, SERVICE_CHANGE_CONFIG); @@ -415,55 +251,42 @@ static void set_service_description(void) * object types, some volatile, some not. */ /* ###: utf-ize */ - if (ChangeServiceConfig2(schService, - 1 /* SERVICE_CONFIG_DESCRIPTION */, - (LPVOID) &full_description)) { - full_description = NULL; - } + ChangeServiceConfig2(schService, + 1 /* SERVICE_CONFIG_DESCRIPTION */, + (LPVOID) &full_description); 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) +static DWORD WINAPI service_nt_ctrl(DWORD dwCtrlCode, DWORD dwEventType, + LPVOID lpEventData, LPVOID lpContext) { - if (dwCtrlCode == SERVICE_CONTROL_STOP) + nt_service_ctx_t *ctx = lpContext; + + /* SHUTDOWN is offered before STOP, accept the first opportunity */ + if ((dwCtrlCode == SERVICE_CONTROL_STOP) + || (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN)) { ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); - ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000); - return; + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, ctx); + return (NO_ERROR); } if (dwCtrlCode == SERVICE_APACHE_RESTART) { ap_signal_parent(SIGNAL_PARENT_RESTART); - ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000); - return; + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_CONTROL_INTERROGATE) { + ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, 0, ctx); + return (NO_ERROR); } - ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0); + return (ERROR_CALL_NOT_IMPLEMENTED); } @@ -477,25 +300,26 @@ extern apr_array_header_t *mpm_new_argv; static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv) { const char *ignored; + nt_service_ctx_t *ctx = &globdat; /* 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; + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; /* ###: utf-ize */ - if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl))) + if (!(ctx->hServiceStatus = RegisterServiceCtrlHandlerEx(argv[0], service_nt_ctrl, ctx))) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), - NULL, "Failure registering service handler"); + NULL, APLOGNO(00365) "Failure registering service handler"); return; } /* Report status, no errors, and buy 3 more seconds */ - ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000); + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); /* We need to append all the command arguments passed via StartService() * to our running service... which just got here via the SCM... @@ -524,9 +348,9 @@ static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv) /* Let the main thread continue now... but hang on to the * signal_monitor event so we can take further action */ - SetEvent(globdat.service_init); + SetEvent(ctx->service_init); - WaitForSingleObject(globdat.service_term, INFINITE); + WaitForSingleObject(ctx->service_term, INFINITE); } @@ -545,7 +369,7 @@ static DWORD WINAPI service_nt_dispatch_thread(LPVOID nada) { /* This is a genuine failure of the SCM. */ rv = apr_get_os_error(); - ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(00366) "Error starting service control dispatcher"); } @@ -553,6 +377,16 @@ static DWORD WINAPI service_nt_dispatch_thread(LPVOID nada) } +/* The service configuration's is stored under the following trees: + * + * HKLM\System\CurrentControlSet\Services\[service name] + * + * \DisplayName + * \ImagePath + * \Parameters\ConfigArgs + */ + + apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, const char *set_name) { @@ -599,7 +433,7 @@ apr_status_t mpm_merge_service_args(apr_pool_t *p, } if (rv != APR_SUCCESS) { if (rv == ERROR_FILE_NOT_FOUND) { - ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00367) "No ConfigArgs registered for %s, perhaps " "this service is not installed?", mpm_service_name); @@ -645,23 +479,13 @@ static 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(); + /* Stop logging to the event log */ + mpm_nt_eventlog_stderr_flush(); - /* Cause the service_nt_main_fn to complete */ - ReleaseMutex(globdat.service_term); + /* 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); - } + ReportStatusToSCMgr(SERVICE_STOPPED, 0, &globdat); WaitForSingleObject(globdat.service_thread, 5000); CloseHandle(globdat.service_thread); @@ -686,31 +510,16 @@ apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p) 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); + 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; } - 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); - } + globdat.service_thread = CreateThread(NULL, 65536, + service_nt_dispatch_thread, + NULL, stack_res_flag, + &globdat.service_thread_id); if (!globdat.service_thread) { return APR_ENOTHREAD; @@ -733,22 +542,14 @@ apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p) 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 - } + ReportStatusToSCMgr(SERVICE_RUNNING, 0, &globdat); 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 + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, &globdat); } @@ -760,6 +561,8 @@ apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, char *launch_cmd; ap_regkey_t *key; apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; fprintf(stderr,reconfig ? "Reconfiguring the %s service\n" : "Installing the %s service\n", mpm_display_name); @@ -768,137 +571,87 @@ apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, 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, + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(00368) "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); - } + 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, APLOGNO(00369) + "Failed to open the WinNT service manager, perhaps " + "you forgot to log in as Adminstrator?"); + return (rv); + } - launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path); + 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; - } + 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"); } - 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); + /* ###: 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; } } - - 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); + 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, APLOGNO(00370) + "Failed to create WinNT Service Profile"); + CloseServiceHandle(schSCManager); return (rv); } } + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + set_service_description(); - /* For both WinNT & Win9x store the service ConfigArgs in the registry... + /* 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, @@ -908,7 +661,7 @@ apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, ap_regkey_close(key); } if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(00371) "%s: Failed to store the ConfigArgs in the registry.", mpm_display_name); return (rv); @@ -920,90 +673,52 @@ apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, apr_status_t mpm_service_uninstall(void) { - char key_name[MAX_PATH]; apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; - if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) - { - SC_HANDLE schService; - SC_HANDLE schSCManager; - - fprintf(stderr,"Removing the %s service\n", mpm_display_name); + 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); - } + 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, APLOGNO(00372) + "Failed to open the WinNT service manager."); + return (rv); + } - /* ###: utf-ize */ - schService = OpenService(schSCManager, mpm_service_name, DELETE); + /* ###: 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, + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(00373) "%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); + return (rv); + } - 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); - } + /* 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); + */ - CloseServiceHandle(schService); - CloseServiceHandle(schSCManager); + if (DeleteService(schService) == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(00374) + "%s: Failed to delete the service.", mpm_display_name); + return (rv); } - 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); - } + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); - /* 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; } @@ -1035,137 +750,62 @@ apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, const char * const * argv) { apr_status_t rv; + CHAR **start_argv; + SC_HANDLE schService; + SC_HANDLE schSCManager; 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; + 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, APLOGNO(00375) + "Failed to open the WinNT service manager"); + return (rv); + } - if (rv != APR_SUCCESS) - rv = apr_get_os_error(); + /* ###: 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, APLOGNO(00376) + "%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, APLOGNO(00377) + "Service %s is already started!", mpm_display_name); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); + return 0; } - 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 */ + start_argv = malloc((argc + 1) * sizeof(const char **)); + memcpy(start_argv, argv, argc * sizeof(const char **)); + start_argv[argc] = NULL; - 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(); + 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(); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00378) "%s: Failed to start the service process.", mpm_display_name); @@ -1178,129 +818,69 @@ apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, void mpm_signal_service(apr_pool_t *ptemp, int signal) { int success = FALSE; + SC_HANDLE schService; + SC_HANDLE schSCManager; - if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) - { - SC_HANDLE schService; - SC_HANDLE schSCManager; - - schSCManager = OpenSCManager(NULL, NULL, // default machine & database - SC_MANAGER_CONNECT); + 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 (!schSCManager) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL, APLOGNO(00379) + "Failed to open the NT Service Manager"); + return; + } - 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); + /* ###: 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, APLOGNO(00380) + "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, APLOGNO(00381) + "Query of Service %s failed", mpm_display_name); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); + return; } - 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"); + if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) { + fprintf(stderr,"The %s service is not started.\n", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } - apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid); - setup_signal_names(prefix); + fprintf(stderr,"The %s service is %s.\n", mpm_display_name, + signal ? "restarting" : "stopping"); - 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 (!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); if (success) fprintf(stderr,"The %s service has %s.\n", mpm_display_name, diff --git a/server/mpm/worker/Makefile.in b/server/mpm/worker/Makefile.in index b45b8483..e32210f1 100644 --- a/server/mpm/worker/Makefile.in +++ b/server/mpm/worker/Makefile.in @@ -1,5 +1,2 @@ -LTLIBRARY_NAME = libworker.la -LTLIBRARY_SOURCES = worker.c fdqueue.c pod.c - -include $(top_srcdir)/build/ltlib.mk +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/worker/config.m4 b/server/mpm/worker/config.m4 new file mode 100644 index 00000000..1a500264 --- /dev/null +++ b/server/mpm/worker/config.m4 @@ -0,0 +1,11 @@ +AC_MSG_CHECKING(if worker MPM supports this platform) +if test $forking_mpms_supported != yes; then + AC_MSG_RESULT(no - This is not a forking platform) +elif test $ac_cv_define_APR_HAS_THREADS != yes; then + AC_MSG_RESULT(no - APR does not support threads) +elif test $have_threaded_sig_graceful != yes; then + AC_MSG_RESULT(no - SIG_GRACEFUL cannot be used with a threaded MPM) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(worker, yes, yes) +fi diff --git a/server/mpm/worker/config5.m4 b/server/mpm/worker/config3.m4 index cc131348..dc2bccb5 100644 --- a/server/mpm/worker/config5.m4 +++ b/server/mpm/worker/config3.m4 @@ -1,6 +1,5 @@ dnl ## XXX - Need a more thorough check of the proper flags to use -if test "$MPM_NAME" = "worker" ; then +APACHE_MPM_MODULE(worker, $enable_mpm_worker, worker.lo fdqueue.lo pod.lo,[ 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 index dfbe8c47..fe5881b4 100644 --- a/server/mpm/worker/fdqueue.c +++ b/server/mpm/worker/fdqueue.c @@ -43,7 +43,7 @@ static apr_status_t queue_info_cleanup(void *data_) if (first_pool == NULL) { break; } - if (apr_atomic_casptr((volatile void**)&(qi->recycled_pools), first_pool->next, + if (apr_atomic_casptr((void*)&(qi->recycled_pools), first_pool->next, first_pool) == first_pool) { apr_pool_destroy(first_pool->pool); } @@ -100,7 +100,7 @@ apr_status_t ap_queue_info_set_idle(fd_queue_info_t *queue_info, */ struct recycled_pool *next = queue_info->recycled_pools; new_recycle->next = next; - if (apr_atomic_casptr((volatile void**)&(queue_info->recycled_pools), + if (apr_atomic_casptr((void*)&(queue_info->recycled_pools), new_recycle, next) == next) { break; } @@ -209,7 +209,7 @@ apr_status_t ap_queue_info_wait_for_idler(fd_queue_info_t *queue_info, if (first_pool == NULL) { break; } - if (apr_atomic_casptr((volatile void**)&(queue_info->recycled_pools), first_pool->next, + if (apr_atomic_casptr((void*)&(queue_info->recycled_pools), first_pool->next, first_pool) == first_pool) { *recycled_pool = first_pool->pool; break; @@ -284,6 +284,8 @@ apr_status_t ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a) queue->data = apr_palloc(a, queue_capacity * sizeof(fd_queue_elem_t)); queue->bounds = queue_capacity; queue->nelts = 0; + queue->in = 0; + queue->out = 0; /* Set all the sockets in the queue to NULL */ for (i = 0; i < queue_capacity; ++i) @@ -312,7 +314,10 @@ apr_status_t ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p) AP_DEBUG_ASSERT(!queue->terminated); AP_DEBUG_ASSERT(!ap_queue_full(queue)); - elem = &queue->data[queue->nelts]; + elem = &queue->data[queue->in]; + queue->in++; + if (queue->in >= queue->bounds) + queue->in -= queue->bounds; elem->sd = sd; elem->p = p; queue->nelts++; @@ -361,7 +366,11 @@ apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p) } } - elem = &queue->data[--queue->nelts]; + elem = &queue->data[queue->out]; + queue->out++; + if (queue->out >= queue->bounds) + queue->out -= queue->bounds; + queue->nelts--; *sd = elem->sd; *p = elem->p; #ifdef AP_DEBUG diff --git a/server/mpm/worker/fdqueue.h b/server/mpm/worker/fdqueue.h index 7de082a0..1d48a1a8 100644 --- a/server/mpm/worker/fdqueue.h +++ b/server/mpm/worker/fdqueue.h @@ -55,8 +55,10 @@ typedef struct fd_queue_elem_t fd_queue_elem_t; struct fd_queue_t { fd_queue_elem_t *data; - int nelts; - int bounds; + unsigned int nelts; + unsigned int bounds; + unsigned int in; + unsigned int out; apr_thread_mutex_t *one_big_mutex; apr_thread_cond_t *not_empty; int terminated; diff --git a/server/mpm/worker/mpm.h b/server/mpm/worker/mpm.h deleted file mode 100644 index 335f4b6a..00000000 --- a/server/mpm/worker/mpm.h +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 index a48f6725..244a0bb1 100644 --- a/server/mpm/worker/mpm_default.h +++ b/server/mpm/worker/mpm_default.h @@ -50,29 +50,5 @@ #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 index 77ad1c90..86f7b39d 100644 --- a/server/mpm/worker/pod.c +++ b/server/mpm/worker/pod.c @@ -14,13 +14,16 @@ * limitations under the License. */ +#include "apr_portable.h" #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) +APLOG_USE_MODULE(mpm_worker); + +AP_DECLARE(apr_status_t) ap_worker_pod_open(apr_pool_t *p, ap_worker_pod_t **pod) { apr_status_t rv; @@ -41,7 +44,7 @@ AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod) return APR_SUCCESS; } -AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t *pod) +AP_DECLARE(int) ap_worker_pod_check(ap_worker_pod_t *pod) { char c; apr_os_file_t fd; @@ -63,7 +66,7 @@ AP_DECLARE(int) ap_mpm_pod_check(ap_pod_t *pod) return AP_NORESTART; } -AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod) +AP_DECLARE(apr_status_t) ap_worker_pod_close(ap_worker_pod_t *pod) { apr_status_t rv; @@ -79,7 +82,7 @@ AP_DECLARE(apr_status_t) ap_mpm_pod_close(ap_pod_t *pod) return rv; } -static apr_status_t pod_signal_internal(ap_pod_t *pod, int graceful) +static apr_status_t pod_signal_internal(ap_worker_pod_t *pod, int graceful) { apr_status_t rv; char char_of_death = graceful ? GRACEFUL_CHAR : RESTART_CHAR; @@ -87,18 +90,18 @@ static apr_status_t pod_signal_internal(ap_pod_t *pod, int graceful) 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, + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00325) "write pipe_of_death"); } return rv; } -AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod, int graceful) +AP_DECLARE(apr_status_t) ap_worker_pod_signal(ap_worker_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) +AP_DECLARE(void) ap_worker_pod_killpg(ap_worker_pod_t *pod, int num, int graceful) { int i; apr_status_t rv = APR_SUCCESS; diff --git a/server/mpm/worker/pod.h b/server/mpm/worker/pod.h index 55cad819..ccb9cf9d 100644 --- a/server/mpm/worker/pod.h +++ b/server/mpm/worker/pod.h @@ -31,7 +31,6 @@ #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" @@ -43,17 +42,17 @@ #define AP_RESTART 0 #define AP_GRACEFUL 1 -typedef struct ap_pod_t ap_pod_t; +typedef struct ap_worker_pod_t ap_worker_pod_t; -struct ap_pod_t { +struct ap_worker_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); +AP_DECLARE(apr_status_t) ap_worker_pod_open(apr_pool_t *p, ap_worker_pod_t **pod); +AP_DECLARE(int) ap_worker_pod_check(ap_worker_pod_t *pod); +AP_DECLARE(apr_status_t) ap_worker_pod_close(ap_worker_pod_t *pod); +AP_DECLARE(apr_status_t) ap_worker_pod_signal(ap_worker_pod_t *pod, int graceful); +AP_DECLARE(void) ap_worker_pod_killpg(ap_worker_pod_t *pod, int num, int graceful); /** @} */ diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index 11c7d89c..75f15ebd 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -50,8 +50,6 @@ #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" @@ -66,6 +64,8 @@ #include "scoreboard.h" #include "fdqueue.h" #include "mpm_default.h" +#include "util_mutex.h" +#include "unixd.h" #include <signal.h> #include <limits.h> /* for INT_MAX */ @@ -114,16 +114,14 @@ * Actual definitions of config globals */ -int ap_threads_per_child = 0; /* Worker threads per child */ +static int 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 max_workers = 0; +static int server_limit = 0; +static int thread_limit = 0; static int dying = 0; static int workers_may_exit = 0; static int start_thread_may_exit = 0; @@ -134,7 +132,42 @@ 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; + +/* data retained by worker across load/unload of the module + * allocated on first call to pre-config hook; located on + * subsequent calls to pre-config hook + */ +typedef struct worker_retained_data { + int first_server_limit; + int first_thread_limit; + int module_loads; + int sick_child_detected; + ap_generation_t my_generation; + int volatile is_graceful; /* set from signal handler */ + int maxclients_reported; + int near_maxclients_reported; + /* + * The max child slot ever assigned, preserved across restarts. Necessary + * to deal with MaxRequestWorkers changes across AP_SIG_GRACEFUL restarts. + * We use this value to optimize routines that have to scan the entire + * scoreboard. + */ + int max_daemons_limit; + /* + * 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. + */ + int idle_spawn_rate; +#ifndef MAX_SPAWN_RATE +#define MAX_SPAWN_RATE (32) +#endif + int hold_off_on_exponential_spawning; +} worker_retained_data; +static worker_retained_data *retained; + +#define MPM_CHILD_PID(i) (ap_scoreboard_image->parent[i].pid) /* The structure used to pass unique initialization info to each thread */ typedef struct { @@ -155,19 +188,7 @@ typedef struct { #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; +static ap_worker_pod_t *pod; /* The worker MPM respects a couple of runtime flags that can aid * in debugging. Setting the -DNO_DETACH flag will prevent the root process @@ -233,7 +254,7 @@ static apr_socket_t **worker_sockets; static void close_worker_sockets(void) { int i; - for (i = 0; i < ap_threads_per_child; i++) { + for (i = 0; i < threads_per_child; i++) { if (worker_sockets[i]) { apr_socket_close(worker_sockets[i]); worker_sockets[i] = NULL; @@ -252,6 +273,10 @@ static void wakeup_listener(void) */ return; } + + /* unblock the listener if it's waiting for a worker */ + ap_queue_info_term(worker_queue_info); + /* * 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 @@ -290,55 +315,109 @@ static void signal_threads(int mode) 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) +static int worker_query(int query_code, int *result, apr_status_t *rv) { - switch(query_code){ + *rv = APR_SUCCESS; + switch (query_code) { case AP_MPMQ_MAX_DAEMON_USED: - *result = ap_max_daemons_limit; - return APR_SUCCESS; + *result = retained->max_daemons_limit; + break; case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_STATIC; - return APR_SUCCESS; + break; case AP_MPMQ_IS_FORKED: *result = AP_MPMQ_DYNAMIC; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_DAEMONS: *result = server_limit; - return APR_SUCCESS; + break; case AP_MPMQ_HARD_LIMIT_THREADS: *result = thread_limit; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_THREADS: - *result = ap_threads_per_child; - return APR_SUCCESS; + *result = threads_per_child; + break; case AP_MPMQ_MIN_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MIN_SPARE_THREADS: *result = min_spare_threads; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_DAEMONS: *result = 0; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_SPARE_THREADS: *result = max_spare_threads; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_REQUESTS_DAEMON: *result = ap_max_requests_per_child; - return APR_SUCCESS; + break; case AP_MPMQ_MAX_DAEMONS: *result = ap_daemons_limit; - return APR_SUCCESS; + break; case AP_MPMQ_MPM_STATE: *result = mpm_state; - return APR_SUCCESS; + break; + case AP_MPMQ_GENERATION: + *result = retained->my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; } - return APR_ENOTIMPL; + return OK; +} + +static void worker_note_child_killed(int childnum, pid_t pid, ap_generation_t gen) +{ + if (childnum != -1) { /* child had a scoreboard slot? */ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[childnum].pid, + ap_scoreboard_image->parent[childnum].generation, + childnum, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[childnum].pid = 0; + } + else { + ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED); + } +} + +static void worker_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + retained->my_generation, slot, MPM_CHILD_STARTED); +} + +static void worker_note_child_lost_slot(int slot, pid_t newpid) +{ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00263) + "pid %" APR_PID_T_FMT " taking over scoreboard slot from " + "%" APR_PID_T_FMT "%s", + newpid, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].quiescing ? + " (quiescing)" : ""); + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_LOST_SLOT); + /* 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_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation); +} + +static const char *worker_get_name(void) +{ + return "worker"; } /* a clean exit from a child with proper cleanup */ @@ -349,6 +428,11 @@ static void clean_child_exit(int code) if (pchild) { apr_pool_destroy(pchild); } + + if (one_process) { + worker_note_child_killed(/* slot */ 0, 0, 0); + } + exit(code); } @@ -361,12 +445,11 @@ static void just_die(int sig) * Connection structures and accounting... */ -/* volatile just in case */ +static int child_fatal; + +/* volatile because they're updated from a signal handler */ 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 @@ -398,7 +481,7 @@ static void ap_start_shutdown(int graceful) return; } shutdown_pending = 1; - is_graceful = graceful; + retained->is_graceful = graceful; } /* do a graceful restart if graceful == 1 */ @@ -410,7 +493,7 @@ static void ap_start_restart(int graceful) return; } restart_pending = 1; - is_graceful = graceful; + retained->is_graceful = graceful; } static void sig_term(int sig) @@ -439,34 +522,37 @@ static void set_signals(void) sa.sa_handler = sig_term; if (sigaction(SIGTERM, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00264) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00265) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00266) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00267) "sigaction(SIGXCPU)"); #endif #ifdef SIGXFSZ - sa.sa_handler = SIG_DFL; + /* For systems following the LFS standard, ignoring SIGXFSZ allows + * a write() beyond the 2GB limit to fail gracefully with E2BIG + * rather than terminate the process. */ + sa.sa_handler = SIG_IGN; if (sigaction(SIGXFSZ, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00268) "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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00269) "sigaction(SIGPIPE)"); #endif @@ -476,10 +562,10 @@ static void set_signals(void) 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, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00270) "sigaction(SIGHUP)"); if (sigaction(AP_SIG_GRACEFUL, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, APLOGNO(00271) "sigaction(" AP_SIG_GRACEFUL_STRING ")"); #else if (!one_process) { @@ -487,7 +573,7 @@ static void set_signals(void) apr_signal(SIGXCPU, SIG_DFL); #endif /* SIGXCPU */ #ifdef SIGXFSZ - apr_signal(SIGXFSZ, SIG_DFL); + apr_signal(SIGXFSZ, SIG_IGN); #endif /* SIGXFSZ */ } @@ -512,42 +598,31 @@ static void set_signals(void) * 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, +static void process_socket(apr_thread_t *thd, 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) { + current_conn->current_thread = thd; 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 + "MaxConnectionsPerChild 0", and keep going in that case. Doing it this way simplifies the hot path in worker_thread */ static void check_infinite_requests(void) { @@ -555,17 +630,6 @@ static void check_infinite_requests(void) 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 */ } } @@ -590,7 +654,30 @@ static void dummy_signal_handler(int sig) */ } -static void *listener_thread(apr_thread_t *thd, void * dummy) +static void accept_mutex_error(const char *func, apr_status_t rv, int process_slot) +{ + 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 */ + } + else if (requests_this_child == INT_MAX + || ((requests_this_child == ap_max_requests_per_child) + && ap_max_requests_per_child)) { + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00272) + "apr_proc_mutex_%s failed " + "before this child process served any requests.", + func); + clean_child_exit(APEXIT_CHILDSICK); + } + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00273) + "apr_proc_mutex_%s failed. Attempting to " + "shutdown process gracefully.", func); + signal_threads(ST_GRACEFUL); +} + +static void * APR_THREAD_FUNC listener_thread(apr_thread_t *thd, void * dummy) { proc_info * ti = dummy; int process_slot = ti->pid; @@ -605,8 +692,14 @@ static void *listener_thread(apr_thread_t *thd, void * dummy) free(ti); - /* ### check the status */ - (void) apr_pollset_create(&pollset, num_listensocks, tpool, 0); + rv = apr_pollset_create(&pollset, num_listensocks, tpool, 0); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "Couldn't create pollset in thread;" + " check system or user limits"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } for (lr = ap_listeners; lr != NULL; lr = lr->next) { apr_pollfd_t pfd = { 0 }; @@ -616,8 +709,16 @@ static void *listener_thread(apr_thread_t *thd, void * dummy) pfd.reqevents = APR_POLLIN; pfd.client_data = lr; - /* ### check the status */ - (void) apr_pollset_add(pollset, &pfd); + rv = apr_pollset_add(pollset, &pfd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + "Couldn't create add listener to pollset;" + " check system or user limits"); + /* let the parent decide how bad this really is */ + clean_child_exit(APEXIT_CHILDSICK); + } + + lr->accept_func = ap_unixd_accept; } /* Unblock the signal used to wake this thread up, and set a handler for @@ -659,19 +760,10 @@ static void *listener_thread(apr_thread_t *thd, void * dummy) 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 */ + if (!listener_may_exit) { + accept_mutex_error("lock", rv, process_slot); } - 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 */ } @@ -692,8 +784,7 @@ static void *listener_thread(apr_thread_t *thd, void * dummy) /* 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, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, "apr_pollset_poll: (listen)"); signal_threads(ST_GRACEFUL); } @@ -750,19 +841,11 @@ static void *listener_thread(apr_thread_t *thd, void * dummy) } 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); + accept_mutex_error("unlock", rv, process_slot); } if (csd != NULL) { rv = ap_queue_push(worker_queue, csd, ptrans); @@ -788,7 +871,7 @@ static void *listener_thread(apr_thread_t *thd, void * dummy) 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, + ap_log_error(APLOG_MARK, level, rv, ap_server_conf, APLOGNO(00274) "apr_proc_mutex_unlock failed. Attempting to " "shutdown process gracefully."); signal_threads(ST_GRACEFUL); @@ -831,7 +914,8 @@ static void * APR_THREAD_FUNC worker_thread(apr_thread_t *thd, void * dummy) 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_scoreboard_image->servers[process_slot][thread_slot].tid = apr_os_thread_current(); + ap_scoreboard_image->servers[process_slot][thread_slot].generation = retained->my_generation; ap_update_child_status_from_indexes(process_slot, thread_slot, SERVER_STARTING, NULL); #ifdef HAVE_PTHREAD_KILL @@ -891,9 +975,9 @@ worker_pop: 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); + process_socket(thd, ptrans, csd, process_slot, thread_slot, bucket_alloc); worker_sockets[thread_slot] = NULL; - requests_this_child--; /* FIXME: should be synchronized - aaron */ + requests_this_child--; apr_pool_clear(ptrans); last_ptrans = ptrans; } @@ -922,14 +1006,14 @@ static void create_listener_thread(thread_starter *ts) proc_info *my_info; apr_status_t rv; - my_info = (proc_info *)malloc(sizeof(proc_info)); + my_info = (proc_info *)ap_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, + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00275) "apr_thread_create: unable to create listener thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); @@ -960,7 +1044,7 @@ static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) /* 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); + rv = ap_queue_init(worker_queue, threads_per_child, pchild); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, "ap_queue_init() failed"); @@ -968,32 +1052,27 @@ static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) } rv = ap_queue_info_create(&worker_queue_info, pchild, - ap_threads_per_child); + 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 + worker_sockets = apr_pcalloc(pchild, 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++) { + /* threads_per_child does not include the listener thread */ + for (i = 0; i < 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 = (proc_info *)ap_malloc(sizeof(proc_info)); my_info->pid = my_child_num; my_info->tid = i; my_info->sd = 0; @@ -1019,7 +1098,7 @@ static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) create_listener_thread(ts); listener_started = 1; } - if (start_thread_may_exit || threads_created == ap_threads_per_child) { + if (start_thread_may_exit || threads_created == threads_per_child) { break; } /* wait for previous generation to clean up an entry */ @@ -1030,7 +1109,7 @@ static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) 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); + ap_my_pid, threads_created, threads_per_child); } prev_threads_created = threads_created; } @@ -1080,19 +1159,19 @@ static void join_workers(apr_thread_t *listener, apr_thread_t **threads) ++iter; } if (iter >= 10) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00276) "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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00277) "apr_thread_join: unable to join listener thread"); } } } - for (i = 0; i < ap_threads_per_child; i++) { + for (i = 0; i < threads_per_child; i++) { if (threads[i]) { /* if we ever created this thread */ #ifdef HAVE_PTHREAD_KILL apr_os_thread_t *worker_os_thread; @@ -1103,7 +1182,7 @@ static void join_workers(apr_thread_t *listener, apr_thread_t **threads) rv = apr_thread_join(&thread_rv, threads[i]); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00278) "apr_thread_join: unable to join worker " "thread %d", i); @@ -1122,7 +1201,7 @@ static void join_start_thread(apr_thread_t *start_thread_id) */ rv = apr_thread_join(&thread_rv, start_thread_id); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00279) "apr_thread_join: unable to join the start " "thread"); } @@ -1146,15 +1225,16 @@ static void child_main(int child_num_arg) /*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, + rv = SAFE_ACCEPT(apr_proc_mutex_child_init(&accept_mutex, + apr_proc_mutex_lockfile(accept_mutex), pchild)); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00280) "Couldn't initialize cross-process lock in child"); clean_child_exit(APEXIT_CHILDFATAL); } - if (unixd_setup_child()) { + if (ap_run_drop_privileges(pchild, ap_server_conf)) { clean_child_exit(APEXIT_CHILDFATAL); } @@ -1168,7 +1248,7 @@ static void child_main(int child_num_arg) */ rv = apr_setup_signal_thread(); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(00281) "Couldn't initialize signal thread"); clean_child_exit(APEXIT_CHILDFATAL); } @@ -1186,14 +1266,8 @@ static void child_main(int child_num_arg) /* 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); - } - + threads = (apr_thread_t **)ap_calloc(1, + sizeof(apr_thread_t *) * threads_per_child); ts = (thread_starter *)apr_palloc(pchild, sizeof(*ts)); apr_threadattr_create(&thread_attr, pchild); @@ -1212,7 +1286,7 @@ static void child_main(int child_num_arg) 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, + ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00282) "apr_thread_create: unable to create worker thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); @@ -1249,13 +1323,13 @@ static void child_main(int child_num_arg) 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 + * (e.g., for MaxConnectionsPerChild) 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); + rv = ap_worker_pod_check(pod); if (rv == AP_NORESTART) { /* see if termination was triggered while we slept */ switch(terminate_mode) { @@ -1296,24 +1370,26 @@ 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 (slot + 1 > retained->max_daemons_limit) { + retained->max_daemons_limit = slot + 1; } if (one_process) { set_signals(); - ap_scoreboard_image->parent[slot].pid = getpid(); + worker_note_child_started(slot, getpid()); child_main(slot); + /* NOTREACHED */ } if ((pid = fork()) == -1) { - ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, APLOGNO(00283) "fork: Unable to fork new process"); - - /* fork didn't succeed. Fix the scoreboard or else - * it will say SERVER_STARTING forever and ever + /* fork didn't succeed. There's no need to touch the scoreboard; + * if we were trying to replace a failed child process, then + * server_main_loop() marked its workers SERVER_DEAD, and if + * we were trying to replace a child process that exited normally, + * its worker_thread()s left SERVER_DEAD or SERVER_GRACEFUL behind. */ - 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 @@ -1331,35 +1407,26 @@ static int make_child(server_rec *s, int slot) 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); + ap_log_error(APLOG_MARK, APLOG_DEBUG, errno, + ap_server_conf, APLOGNO(00284) + "processor unbind failed"); #endif RAISE_SIGSTOP(MAKE_CHILD); apr_signal(SIGTERM, just_die); child_main(slot); - - clean_child_exit(0); + /* NOTREACHED */ } /* 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); + worker_note_child_lost_slot(slot, pid); } ap_scoreboard_image->parent[slot].quiescing = 0; - ap_scoreboard_image->parent[slot].pid = pid; + worker_note_child_started(slot, pid); return 0; } @@ -1379,19 +1446,6 @@ static void startup_children(int 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; @@ -1414,16 +1468,19 @@ static void perform_idle_server_maintenance(void) 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 */ + * that 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) + if (i >= retained->max_daemons_limit && totally_free_length == retained->idle_spawn_rate) + /* short cut if all active processes have been examined and + * enough empty scoreboard slots have been found + */ break; ps = &ap_scoreboard_image->parent[i]; - for (j = 0; j < ap_threads_per_child; j++) { + for (j = 0; j < threads_per_child; j++) { ws = &ap_scoreboard_image->servers[i][j]; status = ws->status; @@ -1443,9 +1500,9 @@ static void perform_idle_server_maintenance(void) */ 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 && + if (status <= SERVER_READY && !ps->quiescing && - ps->generation == ap_my_generation) { + ps->generation == retained->my_generation) { ++idle_thread_count; } if (status >= SERVER_READY && status < SERVER_GRACEFUL) { @@ -1453,7 +1510,7 @@ static void perform_idle_server_maintenance(void) } } } - if (any_dead_threads && totally_free_length < idle_spawn_rate + if (any_dead_threads && totally_free_length < retained->idle_spawn_rate && free_length < MAX_SPAWN_RATE && (!ps->pid /* no process in the slot */ || ps->quiescing)) { /* or at least one is going away */ @@ -1482,12 +1539,12 @@ static void perform_idle_server_maintenance(void) } } - if (sick_child_detected) { + if (retained->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; + retained->sick_child_detected = 0; } else { /* looks like a basket case. give up. @@ -1495,7 +1552,7 @@ static void perform_idle_server_maintenance(void) shutdown_pending = 1; child_fatal = 1; ap_log_error(APLOG_MARK, APLOG_ALERT, 0, - ap_server_conf, + ap_server_conf, APLOGNO(00285) "No active workers found..." " Apache is exiting!"); /* the child already logged the failure details */ @@ -1503,52 +1560,53 @@ static void perform_idle_server_maintenance(void) } } - ap_max_daemons_limit = last_non_dead + 1; + retained->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; + ap_worker_pod_signal(pod, TRUE); + retained->idle_spawn_rate = 1; } else if (idle_thread_count < min_spare_threads) { /* terminate the free list */ - if (free_length == 0) { - /* No room for more children, might warn about configuration */ - if (active_thread_count >= ap_daemons_limit * ap_threads_per_child) { - /* no threads are "inactive" - starting, stopping, etc. - which would confuse matters */ - /* Are all threads in use? Then we're really at MaxClients */ + if (free_length == 0) { /* scoreboard is full, can't fork */ + + if (active_thread_count >= ap_daemons_limit * threads_per_child) { + /* no threads are "inactive" - starting, stopping, etc. */ + /* have we reached MaxRequestWorkers, or just getting close? */ if (0 == idle_thread_count) { - /* 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; + if (!retained->maxclients_reported) { + /* only report this condition once */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00286) + "server reached MaxRequestWorkers " + "setting, consider raising the " + "MaxRequestWorkers setting"); + retained->maxclients_reported = 1; } } else { - static int reported = 0; - - if (!reported) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, - ap_server_conf, - "server is within MinSpareThreads of MaxClients, consider" - " raising the MaxClients setting"); - reported = 1; + if (!retained->near_maxclients_reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00287) + "server is within MinSpareThreads of " + "MaxRequestWorkers, consider raising the " + "MaxRequestWorkers setting"); + retained->near_maxclients_reported = 1; } } } - idle_spawn_rate = 1; + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, + ap_server_conf, APLOGNO(00288) + "scoreboard is full, not at MaxRequestWorkers"); + } + retained->idle_spawn_rate = 1; } else { - if (free_length > idle_spawn_rate) { - free_length = idle_spawn_rate; + if (free_length > retained->idle_spawn_rate) { + free_length = retained->idle_spawn_rate; } - if (idle_spawn_rate >= 8) { + if (retained->idle_spawn_rate >= 8) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, - ap_server_conf, + ap_server_conf, APLOGNO(00289) "server seems busy, (you may need " "to increase StartServers, ThreadsPerChild " "or Min/MaxSpareThreads), " @@ -1562,21 +1620,22 @@ static void perform_idle_server_maintenance(void) /* 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; + if (retained->hold_off_on_exponential_spawning) { + --retained->hold_off_on_exponential_spawning; } - else if (idle_spawn_rate < MAX_SPAWN_RATE) { - idle_spawn_rate *= 2; + else if (retained->idle_spawn_rate < MAX_SPAWN_RATE) { + retained->idle_spawn_rate *= 2; } } } else { - idle_spawn_rate = 1; + retained->idle_spawn_rate = 1; } } static void server_main_loop(int remaining_children_to_start) { + ap_generation_t old_gen; int child_slot; apr_exit_why_e exitwhy; int status, processed_status; @@ -1584,33 +1643,49 @@ static void server_main_loop(int remaining_children_to_start) int i; while (!restart_pending && !shutdown_pending) { - ap_wait_or_timeout(&exitwhy, &status, &pid, pconf); + ap_wait_or_timeout(&exitwhy, &status, &pid, pconf, ap_server_conf); if (pid.pid != -1) { processed_status = ap_process_child_status(&pid, exitwhy, status); + child_slot = ap_find_child_by_pid(&pid); if (processed_status == APEXIT_CHILDFATAL) { - shutdown_pending = 1; - child_fatal = 1; - return; + /* fix race condition found in PR 39311 + * A child created at the same time as a graceful happens + * can find the lock missing and create a fatal error. + * It is not fatal for the last generation to be in this state. + */ + if (child_slot < 0 + || ap_get_scoreboard_process(child_slot)->generation + == retained->my_generation) { + shutdown_pending = 1; + child_fatal = 1; + return; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00290) + "Ignoring fatal error in child of previous " + "generation (pid %ld).", + (long)pid.pid); + retained->sick_child_detected = 1; + } } else if (processed_status == APEXIT_CHILDSICK) { /* tell perform_idle_server_maintenance to check into this * on the next timer pop */ - sick_child_detected = 1; + retained->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++) + for (i = 0; i < 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; + worker_note_child_killed(child_slot, 0, 0); ap_scoreboard_image->parent[child_slot].quiescing = 0; if (processed_status == APEXIT_CHILDSICK) { /* resource shortage, minimize the fork rate */ - idle_spawn_rate = 1; + retained->idle_spawn_rate = 1; } else if (remaining_children_to_start && child_slot < ap_daemons_limit) { @@ -1621,8 +1696,9 @@ static void server_main_loop(int remaining_children_to_start) --remaining_children_to_start; } } - else if (ap_unregister_extra_mpm_process(pid.pid) == 1) { - /* handled */ + else if (ap_unregister_extra_mpm_process(pid.pid, &old_gen) == 1) { + worker_note_child_killed(-1, /* already out of the scoreboard */ + pid.pid, old_gen); #if APR_HAS_OTHER_CHILD } else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH, @@ -1630,12 +1706,12 @@ static void server_main_loop(int remaining_children_to_start) /* handled */ #endif } - else if (is_graceful) { + else if (retained->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, + ap_server_conf, APLOGNO(00291) "long lost child came home! (pid %ld)", (long)pid.pid); } @@ -1664,128 +1740,91 @@ static void server_main_loop(int remaining_children_to_start) } } -int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +static int worker_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); + rv = ap_proc_mutex_create(&accept_mutex, NULL, AP_ACCEPT_MUTEX_TYPE, NULL, + s, _pconf, 0); 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; - } + return DONE; } - if (!is_graceful) { + if (!retained->is_graceful) { if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { mpm_state = AP_MPMQ_STOPPING; - return 1; + return DONE; } /* fix the generation number in the global score; we just got a new, * cleared scoreboard */ - ap_scoreboard_image->global->running_generation = ap_my_generation; + ap_scoreboard_image->global->running_generation = retained->my_generation; } + restart_pending = shutdown_pending = 0; 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 (max_spare_threads < min_spare_threads + threads_per_child) + max_spare_threads = min_spare_threads + 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 + * rapidly... and for each one that exits we may start a new one, until + * there are at least min_spare_threads idle threads, counting across + * all children. But we may be permitted to start more children 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) { + if (!retained->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; + retained->hold_off_on_exponential_spawning = 10; } - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00292) "%s configured -- resuming normal operations", ap_get_server_description()); - ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00293) "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)", + ap_log_command_line(plog, s); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00294) + "Accept mutex: %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) { + if (shutdown_pending && !retained->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 */ + ap_worker_pod_killpg(pod, ap_daemons_limit, FALSE); + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + worker_note_child_killed); 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_remove_pid(pconf, ap_pid_fname); ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, - ap_server_conf, "caught SIGTERM, shutting down"); + ap_server_conf, APLOGNO(00295) "caught SIGTERM, shutting down"); } - return 1; + return DONE; } else if (shutdown_pending) { /* Time to gracefully shut down: * Kill child processes, tell them to call child_exit, etc... @@ -1796,20 +1835,13 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) /* 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(); + ap_worker_pod_killpg(pod, ap_daemons_limit, TRUE); + ap_relieve_child_processes(worker_note_child_killed); 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, + ap_remove_pid(pconf, ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00296) "caught " AP_SIG_GRACEFUL_STOP_STRING ", shutting down gracefully"); } @@ -1826,7 +1858,7 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) apr_sleep(apr_time_from_sec(1)); /* Relieve any children which have now exited */ - ap_relieve_child_processes(); + ap_relieve_child_processes(worker_note_child_killed); active_children = 0; for (index = 0; index < ap_daemons_limit; ++index) { @@ -1843,10 +1875,10 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) * 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); + ap_worker_pod_killpg(pod, ap_daemons_limit, FALSE); + ap_reclaim_child_processes(1, worker_note_child_killed); - return 1; + return DONE; } /* we've been told to restart */ @@ -1854,21 +1886,21 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) if (one_process) { /* not worth thinking about */ - return 1; + return DONE; } /* 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; + ++retained->my_generation; + ap_scoreboard_image->global->running_generation = retained->my_generation; - if (is_graceful) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + if (retained->is_graceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00297) 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); + ap_worker_pod_killpg(pod, ap_daemons_limit, TRUE); /* This is mostly for debugging... so that we know what is still @@ -1881,14 +1913,15 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) * 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_worker_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, + ap_reclaim_child_processes(1, /* Start with SIGTERM */ + worker_note_child_killed); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00298) "SIGHUP received. Attempting to restart"); } - return 0; + return OK; } /* This really should be a post_config hook, but the error log is already @@ -1896,21 +1929,30 @@ int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) */ static int worker_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { + int startup = 0; + int level_flags = 0; apr_status_t rv; pconf = p; - ap_server_conf = s; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->module_loads == 1) { + startup = 1; + level_flags |= APLOG_STARTUP; + } 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"); + ap_log_error(APLOG_MARK, APLOG_ALERT | level_flags, 0, + (startup ? NULL : s), + "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."); + if ((rv = ap_worker_pod_open(pconf, &pod))) { + ap_log_error(APLOG_MARK, APLOG_CRIT | level_flags, rv, + (startup ? NULL : s), + "could not open pipe-of-death"); return DONE; } } @@ -1920,50 +1962,12 @@ static int worker_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, 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; + const char *userdata_key = "mpm_worker_module"; 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) { @@ -1976,56 +1980,299 @@ static int worker_pre_config(apr_pool_t *pconf, apr_pool_t *plog, foreground = ap_exists_config_define("FOREGROUND"); } - /* sigh, want this only the second time around */ - if (restart_num++ == 1) { - is_graceful = 0; + ap_mutex_register(pconf, AP_ACCEPT_MUTEX_TYPE, NULL, APR_LOCK_DEFAULT, 0); + /* sigh, want this only the second time around */ + retained = ap_retained_data_get(userdata_key); + if (!retained) { + retained = ap_retained_data_create(userdata_key, sizeof(*retained)); + retained->max_daemons_limit = -1; + retained->idle_spawn_rate = 1; + } + ++retained->module_loads; + if (retained->module_loads == 2) { if (!one_process && !foreground) { + /* before we detach, setup crash handlers to log to errorlog */ + ap_fatal_signal_setup(ap_server_conf, pconf); 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00299) "apr_proc_detach failed"); return HTTP_INTERNAL_SERVER_ERROR; } } - parent_pid = ap_my_pid = getpid(); } - unixd_pre_config(ptemp); + parent_pid = ap_my_pid = getpid(); + 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; + server_limit = DEFAULT_SERVER_LIMIT; + thread_limit = DEFAULT_THREAD_LIMIT; 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; + threads_per_child = DEFAULT_THREADS_PER_CHILD; + max_workers = ap_daemons_limit * threads_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 int worker_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int startup = 0; + + /* the reverse of pre_config, we want this only the first time around */ + if (retained->module_loads == 1) { + startup = 1; + } + + if (server_limit > MAX_SERVER_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00300) + "WARNING: ServerLimit of %d exceeds compile-time " + "limit of", server_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d servers, decreasing to %d.", + MAX_SERVER_LIMIT, MAX_SERVER_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00301) + "ServerLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + server_limit, MAX_SERVER_LIMIT); + } + server_limit = MAX_SERVER_LIMIT; + } + else if (server_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00302) + "WARNING: ServerLimit of %d not allowed, " + "increasing to 1.", server_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00303) + "ServerLimit of %d not allowed, increasing to 1", + server_limit); + } + server_limit = 1; + } + + /* you cannot change ServerLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_server_limit) { + retained->first_server_limit = server_limit; + } + else if (server_limit != retained->first_server_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00304) + "changing ServerLimit to %d from original value of %d " + "not allowed during restart", + server_limit, retained->first_server_limit); + server_limit = retained->first_server_limit; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00305) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of", thread_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00306) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00307) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00308) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (!retained->first_thread_limit) { + retained->first_thread_limit = thread_limit; + } + else if (thread_limit != retained->first_thread_limit) { + /* don't need a startup console version here */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00309) + "changing ThreadLimit to %d from original value of %d " + "not allowed during restart", + thread_limit, retained->first_thread_limit); + thread_limit = retained->first_thread_limit; + } + + if (threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00310) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of", threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d threads, decreasing to %d.", + thread_limit, thread_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the ThreadLimit " + "directive."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00311) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + threads_per_child, thread_limit); + } + threads_per_child = thread_limit; + } + else if (threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00312) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", threads_per_child); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00313) + "ThreadsPerChild of %d not allowed, increasing to 1", + threads_per_child); + } + threads_per_child = 1; + } + + if (max_workers < threads_per_child) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00314) + "WARNING: MaxRequestWorkers of %d is less than " + "ThreadsPerChild of", max_workers); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " %d, increasing to %d. MaxRequestWorkers must be at " + "least as large", + threads_per_child, threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " as the number of threads in a single server."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00315) + "MaxRequestWorkers of %d is less than ThreadsPerChild " + "of %d, increasing to match", + max_workers, threads_per_child); + } + max_workers = threads_per_child; + } + + ap_daemons_limit = max_workers / threads_per_child; + + if (max_workers % threads_per_child) { + int tmp_max_workers = ap_daemons_limit * threads_per_child; + + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00316) + "WARNING: MaxRequestWorkers of %d is not an integer " + "multiple of", max_workers); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " ThreadsPerChild of %d, decreasing to nearest " + "multiple %d,", threads_per_child, + tmp_max_workers); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " for a maximum of %d servers.", + ap_daemons_limit); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00317) + "MaxRequestWorkers of %d is not an integer multiple of " + "ThreadsPerChild of %d, decreasing to nearest " + "multiple %d", max_workers, threads_per_child, + tmp_max_workers); + } + max_workers = tmp_max_workers; + } + + if (ap_daemons_limit > server_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00318) + "WARNING: MaxRequestWorkers of %d would require %d " + "servers and ", max_workers, ap_daemons_limit); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " would exceed ServerLimit of %d, decreasing to %d.", + server_limit, server_limit * threads_per_child); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " To increase, please see the ServerLimit " + "directive."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00319) + "MaxRequestWorkers of %d would require %d servers and " + "exceed ServerLimit of %d, decreasing to %d", + max_workers, ap_daemons_limit, server_limit, + server_limit * threads_per_child); + } + ap_daemons_limit = server_limit; + } + + /* ap_daemons_to_start > ap_daemons_limit checked in worker_run() */ + if (ap_daemons_to_start < 0) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00320) + "WARNING: StartServers of %d not allowed, " + "increasing to 1.", ap_daemons_to_start); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00321) + "StartServers of %d not allowed, increasing to 1", + ap_daemons_to_start); + } + ap_daemons_to_start = 1; + } + + if (min_spare_threads < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00322) + "WARNING: MinSpareThreads of %d not allowed, " + "increasing to 1", min_spare_threads); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " to avoid almost certain server failure."); + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, + " Please read the documentation."); + } else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00323) + "MinSpareThreads of %d not allowed, increasing to 1", + min_spare_threads); + } + min_spare_threads = 1; + } + + /* max_spare_threads < min_spare_threads + threads_per_child + * checked in worker_run() + */ return OK; } static void worker_hooks(apr_pool_t *p) { - /* The worker open_logs phase must run before the core's, or stderr + /* Our open_logs hook function 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); + ap_hook_open_logs(worker_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); /* 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); + ap_hook_check_config(worker_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm(worker_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(worker_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(worker_get_name, NULL, NULL, APR_HOOK_MIDDLE); } static const char *set_daemons_to_start(cmd_parms *cmd, void *dummy, @@ -2049,16 +2296,6 @@ static const char *set_min_spare_threads(cmd_parms *cmd, void *dummy, } 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; } @@ -2074,63 +2311,19 @@ static const char *set_max_spare_threads(cmd_parms *cmd, void *dummy, return NULL; } -static const char *set_max_clients (cmd_parms *cmd, void *dummy, +static const char *set_max_workers (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; + if (!strcasecmp(cmd->cmd->name, "MaxClients")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00324) + "MaxClients is deprecated, use MaxRequestWorkers " + "instead."); } + max_workers = atoi(arg); return NULL; } @@ -2142,109 +2335,33 @@ static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, 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; - } + threads_per_child = atoi(arg); 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; - } + server_limit = atoi(arg); 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; - } + thread_limit = atoi(arg); 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"), @@ -2252,8 +2369,10 @@ 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, +AP_INIT_TAKE1("MaxRequestWorkers", set_max_workers, NULL, RSRC_CONF, "Maximum number of threads alive at the same time"), +AP_INIT_TAKE1("MaxClients", set_max_workers, NULL, RSRC_CONF, + "Deprecated name of MaxRequestWorkers"), 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, @@ -2264,9 +2383,9 @@ AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, { NULL } }; -module AP_MODULE_DECLARE_DATA mpm_worker_module = { +AP_DECLARE_MODULE(mpm_worker) = { MPM20_MODULE_STUFF, - ap_mpm_rewrite_args, /* hook to run before apache parses args */ + 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 */ diff --git a/server/mpm_common.c b/server/mpm_common.c index 754fc643..0cd65e5c 100644 --- a/server/mpm_common.c +++ b/server/mpm_common.c @@ -37,17 +37,16 @@ #include "httpd.h" #include "http_config.h" +#include "http_core.h" #include "http_log.h" #include "http_main.h" -#include "mpm.h" #include "mpm_common.h" +#include "mod_core.h" #include "ap_mpm.h" #include "ap_listen.h" -#include "mpm_default.h" +#include "util_mutex.h" -#ifdef AP_MPM_WANT_SET_SCOREBOARD #include "scoreboard.h" -#endif #ifdef HAVE_PWD_H #include <pwd.h> @@ -59,320 +58,111 @@ #include <unistd.h> #endif +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + #if AP_ENABLE_EXCEPTION_HOOK APR_HOOK_STRUCT( APR_HOOK_LINK(fatal_exception) APR_HOOK_LINK(monitor) + APR_HOOK_LINK(drop_privileges) + APR_HOOK_LINK(mpm) + APR_HOOK_LINK(mpm_query) + APR_HOOK_LINK(mpm_register_timed_callback) + APR_HOOK_LINK(mpm_get_name) + APR_HOOK_LINK(end_generation) + APR_HOOK_LINK(child_status) ) AP_IMPLEMENT_HOOK_RUN_ALL(int, fatal_exception, (ap_exception_info_t *ei), (ei), OK, DECLINED) #else APR_HOOK_STRUCT( APR_HOOK_LINK(monitor) + APR_HOOK_LINK(drop_privileges) + APR_HOOK_LINK(mpm) + APR_HOOK_LINK(mpm_query) + APR_HOOK_LINK(mpm_register_timed_callback) + APR_HOOK_LINK(mpm_get_name) + APR_HOOK_LINK(end_generation) + APR_HOOK_LINK(child_status) ) #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; - - /* Ensure pid sanity. */ - if (pid < 1) { - return 1; - } - - 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; + (apr_pool_t *p, server_rec *s), (p, s), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, drop_privileges, + (apr_pool_t * pchild, server_rec * s), + (pchild, s), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm, + (apr_pool_t *pconf, apr_pool_t *plog, server_rec *s), + (pconf, plog, s), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm_query, + (int query_code, int *result, apr_status_t *_rv), + (query_code, result, _rv), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, mpm_register_timed_callback, + (apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton), + (t, cbfn, baton), APR_ENOTIMPL) +AP_IMPLEMENT_HOOK_VOID(end_generation, + (server_rec *s, ap_generation_t gen), + (s, gen)) +AP_IMPLEMENT_HOOK_VOID(child_status, + (server_rec *s, pid_t pid, ap_generation_t gen, int slot, mpm_child_status status), + (s,pid,gen,slot,status)) + +/* hooks with no args are implemented last, after disabling APR hook probes */ +#if defined(APR_HOOK_PROBES_ENABLED) +#undef APR_HOOK_PROBES_ENABLED +#undef APR_HOOK_PROBE_ENTRY +#define APR_HOOK_PROBE_ENTRY(ud,ns,name,args) +#undef APR_HOOK_PROBE_RETURN +#define APR_HOOK_PROBE_RETURN(ud,ns,name,rv,args) +#undef APR_HOOK_PROBE_INVOKE +#define APR_HOOK_PROBE_INVOKE(ud,ns,name,src,args) +#undef APR_HOOK_PROBE_COMPLETE +#define APR_HOOK_PROBE_COMPLETE(ud,ns,name,src,rv,args) +#undef APR_HOOK_INT_DCL_UD +#define APR_HOOK_INT_DCL_UD +#endif +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *, mpm_get_name, + (void), + (), NULL) + +typedef struct mpm_gen_info_t { + APR_RING_ENTRY(mpm_gen_info_t) link; + int gen; /* which gen? */ + int active; /* number of active processes */ + int done; /* gen finished? (whether or not active processes) */ +} mpm_gen_info_t; + +APR_RING_HEAD(mpm_gen_info_head_t, mpm_gen_info_t); +static struct mpm_gen_info_head_t *geninfo, *unused_geninfo; +static int gen_head_init; /* yuck */ + +/* variables representing config directives implemented here */ +const char *ap_pid_fname; +int ap_max_requests_per_child; +char ap_coredump_dir[MAX_STRING_LEN]; +int ap_coredumpdir_configured; +int ap_graceful_shutdown_timeout; +AP_DECLARE_DATA apr_uint32_t ap_max_mem_free; +apr_size_t ap_thread_stacksize; - if (reclaim_one_pid(cur_extra->pid, DO_NOTHING)) { - AP_DEBUG_ASSERT(1 == ap_unregister_extra_mpm_process(cur_extra->pid)); - } - cur_extra = next; - } -} +#define ALLOCATOR_MAX_FREE_DEFAULT (2048*1024) -/* Before sending the signal to the pid this function verifies that - * the pid is a member of the current process group; either using - * apr_proc_wait(), where waitpid() guarantees to fail for non-child - * processes; or by using getpgid() directly, if available. */ -apr_status_t ap_mpm_safe_kill(pid_t pid, int sig) +/* Set defaults for config directives implemented here. This is + * called from core's pre-config hook, so MPMs which need to override + * one of these should run their pre-config hook after that of core. + */ +void mpm_common_pre_config(apr_pool_t *pconf) { -#ifndef HAVE_GETPGID - apr_proc_t proc; - apr_status_t rv; - apr_exit_why_e why; - int status; - - /* Ensure pid sanity */ - if (pid < 1) { - return APR_EINVAL; - } - - proc.pid = pid; - rv = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); - if (rv == APR_CHILD_DONE) { -#ifdef AP_MPM_WANT_PROCESS_CHILD_STATUS - /* The child already died - log the termination status if - * necessary: */ - ap_process_child_status(&proc, why, status); -#endif - return APR_EINVAL; - } - else if (rv != APR_CHILD_NOTDONE) { - /* The child is already dead and reaped, or was a bogus pid - - * log this either way. */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, - "cannot send signal %d to pid %ld (non-child or " - "already dead)", sig, (long)pid); - return APR_EINVAL; - } -#else - pid_t pg; - - /* Ensure pid sanity. */ - if (pid < 1) { - return APR_EINVAL; - } - - pg = getpgid(pid); - if (pg == -1) { - /* Process already dead... */ - return errno; - } - - if (pg != getpgrp()) { - ap_log_error(APLOG_MARK, APLOG_ALERT, 0, ap_server_conf, - "refusing to send signal %d to pid %ld outside " - "process group", sig, (long)pid); - return APR_EINVAL; - } -#endif - - return kill(pid, sig) ? errno : APR_SUCCESS; + ap_pid_fname = DEFAULT_PIDLOG; + ap_max_requests_per_child = 0; /* unlimited */ + apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir)); + ap_coredumpdir_configured = 0; + ap_graceful_shutdown_timeout = 0; /* unlimited */ + ap_max_mem_free = ALLOCATOR_MAX_FREE_DEFAULT; + ap_thread_stacksize = 0; /* use system default */ } -#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 @@ -381,14 +171,14 @@ apr_status_t ap_mpm_safe_kill(pid_t pid, int sig) 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_pool_t *p, server_rec *s) { 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); + ap_run_monitor(p, s); } rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p); @@ -401,79 +191,12 @@ void ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, apr_proc_t *ret, return; } -#ifdef NEED_WAITPID - if ((ret = reap_children(exitcode, status)) > 0) { - return; - } -#endif - - apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); + apr_sleep(apr_time_from_sec(1)); 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) +#if defined(TCP_NODELAY) void ap_sock_disable_nagle(apr_socket_t *s) { /* The Nagle algorithm says that we should delay sending partial @@ -488,7 +211,7 @@ void ap_sock_disable_nagle(apr_socket_t *s) 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, + ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf, APLOGNO(00542) "apr_socket_opt_set: (TCP_NODELAY)"); } } @@ -503,7 +226,7 @@ AP_DECLARE(uid_t) ap_uname2id(const char *name) return (atoi(&name[1])); if (!(ent = getpwnam(name))) { - ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00543) "%s: bad user name %s", ap_server_argv0, name); exit(1); } @@ -521,7 +244,7 @@ AP_DECLARE(gid_t) ap_gname2id(const char *name) return (atoi(&name[1])); if (!(ent = getgrnam(name))) { - ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00544) "%s: bad group name %s", ap_server_argv0, name); exit(1); } @@ -533,10 +256,9 @@ AP_DECLARE(gid_t) ap_gname2id(const char *name) #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. */ +#if defined(_OSD_POSIX) || defined(OS2) || defined(WIN32) || defined(NETWARE) return 0; -#else /* ndef QNX */ +#else gid_t groups[NGROUPS_MAX]; struct group *g; int index = 0; @@ -559,205 +281,11 @@ int initgroups(const char *name, gid_t basegid) endgrent(); return setgroups(index, groups); -#endif /* def QNX */ +#endif } #endif /* def HAVE_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, "OPTIONS * HTTP/1.0\r\nUser-Agent: ", - ap_get_server_banner(), - " (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) @@ -774,40 +302,12 @@ const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy, 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) +void ap_mpm_dump_pidfile(apr_pool_t *p, apr_file_t *out) { - const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); - if (err != NULL) { - return err; - } - - ap_lock_fname = arg; - return NULL; + apr_file_printf(out, "PidFile: \"%s\"\n", + ap_server_root_relative(p, ap_pid_fname)); } -#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) @@ -817,20 +317,20 @@ const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy, return err; } + if (!strcasecmp(cmd->cmd->name, "MaxRequestsPerChild")) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00545) + "MaxRequestsPerChild is deprecated, use " + "MaxConnectionsPerChild instead."); + } + 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); @@ -843,7 +343,7 @@ const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy, return apr_pstrcat(cmd->pool, "Invalid CoreDumpDirectory path ", arg, NULL); } - if ((rv = apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool)) != APR_SUCCESS) { + if (apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool) != APR_SUCCESS) { return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, " does not exist", NULL); } @@ -855,10 +355,6 @@ const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy, 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) @@ -870,244 +366,6 @@ const char * ap_mpm_set_graceful_shutdown(cmd_parms *cmd, void *dummy, 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; - 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 { - 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; - - mpm_new_argv = apr_array_make(process->pool, process->argc, - sizeof(const char **)); - *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; - 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) @@ -1118,7 +376,7 @@ const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy, return err; } - value = strtol(arg, NULL, 0); + value = strtol(arg, NULL, 10); if (value < 0 || errno == ERANGE) return apr_pstrcat(cmd->pool, "Invalid MaxMemFree value: ", arg, NULL); @@ -1128,11 +386,6 @@ const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy, 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) { @@ -1142,7 +395,7 @@ const char *ap_mpm_set_thread_stacksize(cmd_parms *cmd, void *dummy, return err; } - value = strtol(arg, NULL, 0); + value = strtol(arg, NULL, 10); if (value < 0 || errno == ERANGE) return apr_pstrcat(cmd->pool, "Invalid ThreadStackSize value: ", arg, NULL); @@ -1152,162 +405,160 @@ const char *ap_mpm_set_thread_stacksize(cmd_parms *cmd, void *dummy, return NULL; } -#endif /* AP_MPM_WANT_SET_STACKSIZE */ - -#ifdef AP_MPM_WANT_FATAL_SIGNAL_HANDLER +AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +{ + apr_status_t rv; -static pid_t parent_pid, my_pid; -apr_pool_t *pconf; + if (ap_run_mpm_query(query_code, result, &rv) == DECLINED) { + rv = APR_EGENERAL; + } -#if AP_ENABLE_EXCEPTION_HOOK + return rv; +} -static int exception_hook_enabled; +static void end_gen(mpm_gen_info_t *gi) +{ + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "end of generation %d", gi->gen); + ap_run_end_generation(ap_server_conf, gi->gen); + APR_RING_REMOVE(gi, link); + APR_RING_INSERT_HEAD(unused_geninfo, gi, mpm_gen_info_t, link); +} -const char *ap_mpm_set_exception_hook(cmd_parms *cmd, void *dummy, - const char *arg) +apr_status_t ap_mpm_end_gen_helper(void *unused) /* cleanup on pconf */ { - const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); - if (err != NULL) { - return err; - } + int gen = ap_config_generation - 1; /* differs from MPM generation */ + mpm_gen_info_t *cur; - if (cmd->server->is_virtual) { - return "EnableExceptionHook directive not allowed in <VirtualHost>"; + if (geninfo == NULL) { + /* initial pconf teardown, MPM hasn't run */ + return APR_SUCCESS; } - if (strcasecmp(arg, "on") == 0) { - exception_hook_enabled = 1; + cur = APR_RING_FIRST(geninfo); + while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && + cur->gen != gen) { + cur = APR_RING_NEXT(cur, link); } - else if (strcasecmp(arg, "off") == 0) { - exception_hook_enabled = 0; + + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + /* last child of generation already exited */ + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, + "no record of generation %d", gen); } else { - return "parameter must be 'on' or 'off'"; + cur->done = 1; + if (cur->active == 0) { + end_gen(cur); + } } - return NULL; + return APR_SUCCESS; } -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); +/* core's child-status hook + * tracks number of remaining children per generation and + * runs the end-generation hook when the last child of + * a generation exits + */ +void ap_core_child_status(server_rec *s, pid_t pid, + ap_generation_t gen, int slot, + mpm_child_status status) +{ + mpm_gen_info_t *cur; + const char *status_msg = "unknown status"; + + if (!gen_head_init) { /* where to run this? */ + gen_head_init = 1; + geninfo = apr_pcalloc(s->process->pool, sizeof *geninfo); + unused_geninfo = apr_pcalloc(s->process->pool, sizeof *unused_geninfo); + APR_RING_INIT(geninfo, mpm_gen_info_t, link); + APR_RING_INIT(unused_geninfo, mpm_gen_info_t, link); + } + + cur = APR_RING_FIRST(geninfo); + while (cur != APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link) && + cur->gen != gen) { + cur = APR_RING_NEXT(cur, link); + } + + switch(status) { + case MPM_CHILD_STARTED: + status_msg = "started"; + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + /* first child for this generation */ + if (!APR_RING_EMPTY(unused_geninfo, mpm_gen_info_t, link)) { + cur = APR_RING_FIRST(unused_geninfo); + APR_RING_REMOVE(cur, link); + cur->active = cur->done = 0; + } + else { + cur = apr_pcalloc(s->process->pool, sizeof *cur); + } + cur->gen = gen; + APR_RING_ELEM_INIT(cur, link); + APR_RING_INSERT_HEAD(geninfo, cur, mpm_gen_info_t, link); + } + ap_random_parent_after_fork(); + ++cur->active; + break; + case MPM_CHILD_EXITED: + status_msg = "exited"; + if (cur == APR_RING_SENTINEL(geninfo, mpm_gen_info_t, link)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00546) + "no record of generation %d of exiting child %" APR_PID_T_FMT, + gen, pid); + } + else { + --cur->active; + if (!cur->active && cur->done) { /* no children, server has stopped/restarted */ + end_gen(cur); + } + } + break; + case MPM_CHILD_LOST_SLOT: + status_msg = "lost slot"; + /* we don't track by slot, so it doesn't matter */ + break; } + ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, + "mpm child %" APR_PID_T_FMT " (gen %d/slot %d) %s", + pid, gen, slot, status_msg); } -#endif /* AP_ENABLE_EXCEPTION_HOOK */ -/* handle all varieties of core dumping signals */ -static void sig_coredump(int sig) +AP_DECLARE(apr_status_t) ap_mpm_register_timed_callback(apr_time_t t, ap_mpm_callback_fn_t *cbfn, void *baton) { - 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. - */ + return ap_run_mpm_register_timed_callback(t, cbfn, baton); } -apr_status_t ap_fatal_signal_child_setup(server_rec *s) +AP_DECLARE(const char *)ap_show_mpm(void) { - my_pid = getpid(); - return APR_SUCCESS; + const char *name = ap_run_mpm_get_name(); + + if (!name) { + name = ""; + } + + return name; } -apr_status_t ap_fatal_signal_setup(server_rec *s, apr_pool_t *in_pconf) +AP_DECLARE(const char *)ap_check_mpm(void) { -#ifndef NO_USE_SIGACTION - struct sigaction sa; - - sigemptyset(&sa.sa_mask); + static const char *last_mpm_name = NULL; -#if defined(SA_ONESHOT) - sa.sa_flags = SA_ONESHOT; -#elif defined(SA_RESETHAND) - sa.sa_flags = SA_RESETHAND; -#else - sa.sa_flags = 0; -#endif + if (!_hooks.link_mpm || _hooks.link_mpm->nelts == 0) + return "No MPM loaded."; + else if (_hooks.link_mpm->nelts > 1) + return "More than one MPM loaded."; - 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 -#ifdef SIGFPE - if (sigaction(SIGFPE, &sa, NULL) < 0) - ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, "sigaction(SIGFPE)"); -#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 */ -#ifdef SIGFPE - apr_signal(SIGFPE, sig_coredump); -#endif /* SIGFPE */ - -#endif /* NO_USE_SIGACTION */ - - pconf = in_pconf; - parent_pid = my_pid = getpid(); + if (last_mpm_name) { + if (strcmp(last_mpm_name, ap_show_mpm())) { + return "The MPM cannot be changed during restart."; + } + } + else { + last_mpm_name = apr_pstrdup(ap_pglobal, ap_show_mpm()); + } - return APR_SUCCESS; + return NULL; } - -#endif /* AP_MPM_WANT_FATAL_SIGNAL_HANDLER */ diff --git a/server/mpm_unix.c b/server/mpm_unix.c new file mode 100644 index 00000000..6b1d6ee1 --- /dev/null +++ b/server/mpm_unix.c @@ -0,0 +1,942 @@ +/* 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 + */ + +#ifndef WIN32 + +#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_core.h" +#include "http_log.h" +#include "http_main.h" +#include "mpm_common.h" +#include "ap_mpm.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "util_mutex.h" + +#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 + + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +typedef enum {DO_NOTHING, SEND_SIGTERM, SEND_SIGKILL, GIVEUP} action_t; + +typedef struct extra_process_t { + struct extra_process_t *next; + pid_t pid; + ap_generation_t gen; +} extra_process_t; + +static extra_process_t *extras; + +void ap_register_extra_mpm_process(pid_t pid, ap_generation_t gen) +{ + extra_process_t *p = (extra_process_t *)ap_malloc(sizeof(extra_process_t)); + + p->next = extras; + p->pid = pid; + p->gen = gen; + extras = p; +} + +int ap_unregister_extra_mpm_process(pid_t pid, ap_generation_t *gen) +{ + 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; + } + *gen = cur->gen; + 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; + apr_exit_why_e why; + int status; + + /* Ensure pid sanity. */ + if (pid < 1) { + return 1; + } + + proc.pid = pid; + waitret = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); + if (waitret != APR_CHILD_NOTDONE) { + if (waitret == APR_CHILD_DONE) + ap_process_child_status(&proc, why, status); + 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, APLOGNO(00045) + "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, APLOGNO(00046) + "child process %" APR_PID_T_FMT + " still did not exit, " + "sending a SIGKILL", + pid); + kill(pid, SIGKILL); + 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, APLOGNO(00047) + "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, + ap_reclaim_callback_fn_t *mpm_callback) +{ + 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) { + process_score *ps = ap_get_scoreboard_process(i); + pid_t pid = ps->pid; + + if (pid == 0) { + continue; /* not every scoreboard entry is in use */ + } + + if (reclaim_one_pid(pid, action_table[cur_action].action)) { + mpm_callback(i, 0, 0); + } + else { + ++not_dead_yet; + } + } + + cur_extra = extras; + while (cur_extra) { + ap_generation_t old_gen; + extra_process_t *next = cur_extra->next; + + if (reclaim_one_pid(cur_extra->pid, action_table[cur_action].action)) { + if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { + mpm_callback(-1, cur_extra->pid, old_gen); + } + else { + AP_DEBUG_ASSERT(1 == 0); + } + } + 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(ap_reclaim_callback_fn_t *mpm_callback) +{ + 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) { + process_score *ps = ap_get_scoreboard_process(i); + pid_t pid = ps->pid; + + if (pid == 0) { + continue; /* not every scoreboard entry is in use */ + } + + if (reclaim_one_pid(pid, DO_NOTHING)) { + mpm_callback(i, 0, 0); + } + } + + cur_extra = extras; + while (cur_extra) { + ap_generation_t old_gen; + extra_process_t *next = cur_extra->next; + + if (reclaim_one_pid(cur_extra->pid, DO_NOTHING)) { + if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { + mpm_callback(-1, cur_extra->pid, old_gen); + } + else { + AP_DEBUG_ASSERT(1 == 0); + } + } + cur_extra = next; + } +} + +/* Before sending the signal to the pid this function verifies that + * the pid is a member of the current process group; either using + * apr_proc_wait(), where waitpid() guarantees to fail for non-child + * processes; or by using getpgid() directly, if available. */ +apr_status_t ap_mpm_safe_kill(pid_t pid, int sig) +{ +#ifndef HAVE_GETPGID + apr_proc_t proc; + apr_status_t rv; + apr_exit_why_e why; + int status; + + /* Ensure pid sanity */ + if (pid < 1) { + return APR_EINVAL; + } + + proc.pid = pid; + rv = apr_proc_wait(&proc, &status, &why, APR_NOWAIT); + if (rv == APR_CHILD_DONE) { + /* The child already died - log the termination status if + * necessary: */ + ap_process_child_status(&proc, why, status); + return APR_EINVAL; + } + else if (rv != APR_CHILD_NOTDONE) { + /* The child is already dead and reaped, or was a bogus pid - + * log this either way. */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00048) + "cannot send signal %d to pid %ld (non-child or " + "already dead)", sig, (long)pid); + return APR_EINVAL; + } +#else + pid_t pg; + + /* Ensure pid sanity. */ + if (pid < 1) { + return APR_EINVAL; + } + + pg = getpgid(pid); + if (pg == -1) { + /* Process already dead... */ + return errno; + } + + if (pg != getpgrp()) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, ap_server_conf, APLOGNO(00049) + "refusing to send signal %d to pid %ld outside " + "process group", sig, (long)pid); + return APR_EINVAL; + } +#endif + + return kill(pid, sig) ? errno : APR_SUCCESS; +} + + +int ap_process_child_status(apr_proc_t *pid, apr_exit_why_e why, int status) +{ + int signum = status; + const char *sigdesc; + + /* 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, APLOGNO(00050) + "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)) { + sigdesc = apr_signal_description_get(signum); + + 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, APLOGNO(00051) + "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, APLOGNO(00052) + "child pid %ld exit signal %s (%d)", + (long)pid->pid, sigdesc, signum); + } + } + } + return 0; +} + +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_ex(&((*pod)->pod_in), &((*pod)->pod_out), + APR_WRITE_BLOCK, 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, APLOGNO(00053) + "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; + ap_listen_rec *lp; + + /* 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; + } + + /* If possible, find a listener which is configured for + * plain-HTTP, not SSL; using an SSL port would either be + * expensive to do correctly (performing a complete SSL handshake) + * or cause log spam by doing incorrectly (simply sending EOF). */ + lp = ap_listeners; + while (lp && lp->protocol && strcasecmp(lp->protocol, "http") != 0) { + lp = lp->next; + } + if (!lp) { + lp = ap_listeners; + } + + rv = apr_socket_create(&sock, lp->bind_addr->family, SOCK_STREAM, 0, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00054) + "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, APLOGNO(00055) + "set timeout on socket to connect to listener"); + apr_socket_close(sock); + apr_pool_destroy(p); + return rv; + } + + rv = apr_socket_connect(sock, lp->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, APLOGNO(00056) + "connect to listener on %pI", lp->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, "OPTIONS * HTTP/1.0\r\nUser-Agent: ", + ap_get_server_description(), + " (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); + } +} + +static const char *dash_k_arg = NULL; +static const char *dash_k_arg_noarg = "noarg"; + +static int send_signal(pid_t pid, int sig) +{ + if (kill(pid, sig) < 0) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, errno, NULL, APLOGNO(00057) + "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; + const char *status; + + *exit_status = 0; + + rv = ap_read_pid(pconf, ap_pid_fname, &otherpid); + if (rv != APR_SUCCESS) { + if (!APR_STATUS_IS_ENOENT(rv)) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL, APLOGNO(00058) + "Error retrieving pid file %s", ap_pid_fname); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00059) + "Remove it before continuing if it is corrupted."); + *exit_status = 1; + return 1; + } + status = "httpd (no pid file) not running"; + } + else { + 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") || dash_k_arg == dash_k_arg_noarg) { + 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")) { + if (!running) { + printf("%s\n", status); + } + else { + *exit_status = send_signal(otherpid, AP_SIG_GRACEFUL_STOP); + } + 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; + + mpm_new_argv = apr_array_make(process->pool, process->argc, + sizeof(const char **)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + 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 (NULL == dash_k_arg) { + dash_k_arg = dash_k_arg_noarg; + } + + APR_REGISTER_OPTIONAL_FN(ap_signal_server); +} + +static pid_t parent_pid, my_pid; +static 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, APLOGNO(00060) + "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, APLOGNO(00061) "sigaction(SIGSEGV)"); +#ifdef SIGBUS + if (sigaction(SIGBUS, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00062) "sigaction(SIGBUS)"); +#endif +#ifdef SIGABORT + if (sigaction(SIGABORT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00063) "sigaction(SIGABORT)"); +#endif +#ifdef SIGABRT + if (sigaction(SIGABRT, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00064) "sigaction(SIGABRT)"); +#endif +#ifdef SIGILL + if (sigaction(SIGILL, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00065) "sigaction(SIGILL)"); +#endif +#ifdef SIGFPE + if (sigaction(SIGFPE, &sa, NULL) < 0) + ap_log_error(APLOG_MARK, APLOG_WARNING, errno, s, APLOGNO(00066) "sigaction(SIGFPE)"); +#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 */ +#ifdef SIGFPE + apr_signal(SIGFPE, sig_coredump); +#endif /* SIGFPE */ + +#endif /* NO_USE_SIGACTION */ + + pconf = in_pconf; + parent_pid = my_pid = getpid(); + + return APR_SUCCESS; +} + +#endif /* WIN32 */ diff --git a/server/protocol.c b/server/protocol.c index 55468fc1..733cdaac 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -33,7 +33,6 @@ #define APR_WANT_MEMFUNC #include "apr_want.h" -#define CORE_PRIVATE #include "util_filter.h" #include "ap_config.h" #include "httpd.h" @@ -57,12 +56,17 @@ #include <unistd.h> #endif +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX APR_HOOK_STRUCT( + APR_HOOK_LINK(pre_read_request) APR_HOOK_LINK(post_read_request) APR_HOOK_LINK(log_transaction) APR_HOOK_LINK(http_scheme) APR_HOOK_LINK(default_port) + APR_HOOK_LINK(note_auth_failure) ) AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL; @@ -95,7 +99,7 @@ AP_DECLARE(void) ap_setup_make_content_type(apr_pool_t *pool) /* * 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 type is NULL or "", return NULL (do not set content-type). * - if charset adding is disabled, stop processing and return type. * - then, if there are no parameters on type, add the default charset * - return type @@ -104,21 +108,19 @@ 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_dir_config *)ap_get_core_module_config(r->per_dir_config); core_request_config *request_conf; apr_size_t type_len; - if (!type) { - type = ap_default_type(r); + if (!type || *type == '\0') { + return NULL; } if (conf->add_default_charset != ADD_DEFAULT_CHARSET_ON) { return type; } - request_conf = - ap_get_module_config(r->request_config, &core_module); + request_conf = ap_get_core_module_config(r->request_config); if (request_conf->suppress_charset) { return type; } @@ -222,9 +224,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, * against APR_ASCII_LF at the end of the loop if bb only contains * zero-length buckets. */ - if (last_char) { + if (last_char) *last_char = '\0'; - } for (;;) { apr_brigade_cleanup(bb); @@ -433,8 +434,13 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, } } } - *read = bytes_handled; + + /* PR#43039: We shouldn't accept NULL bytes within the line */ + if (strlen(*s) < bytes_handled) { + return APR_EINVAL; + } + return APR_SUCCESS; } @@ -603,7 +609,7 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) * 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) { + if (APR_STATUS_IS_ENOSPC(rv)) { r->status = HTTP_REQUEST_URI_TOO_LARGE; r->proto_num = HTTP_VERSION(1,0); r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); @@ -611,10 +617,19 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) else if (APR_STATUS_IS_TIMEUP(rv)) { r->status = HTTP_REQUEST_TIME_OUT; } + else if (APR_STATUS_IS_EINVAL(rv)) { + r->status = HTTP_BAD_REQUEST; + } return 0; } } while ((len <= 0) && (++num_blank_lines < max_blank_lines)); + if (APLOGrtrace5(r)) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, + "Request received from client: %s", + ap_escape_logitem(r->pool, r->the_request)); + } + /* we've probably got something to do, ignore graceful restart requests */ r->request_time = apr_time_now(); @@ -640,6 +655,26 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) ap_parse_uri(r, uri); + /* RFC 2616: + * Request-URI = "*" | absoluteURI | abs_path | authority + * + * authority is a special case for CONNECT. If the request is not + * using CONNECT, and the parsed URI does not have scheme, and + * it does not begin with '/', and it is not '*', then, fail + * and give a 400 response. */ + if (r->method_number != M_CONNECT + && !r->parsed_uri.scheme + && uri[0] != '/' + && !(uri[0] == '*' && uri[1] == '\0')) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00559) + "invalid request-URI %s", uri); + r->args = NULL; + r->hostname = NULL; + r->status = HTTP_BAD_REQUEST; + r->uri = apr_pstrdup(r->pool, uri); + return 0; + } + if (ll[0]) { r->assbackwards = 0; pro = ll; @@ -670,6 +705,35 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) return 1; } +static int table_do_fn_check_lengths(void *r_, const char *key, + const char *value) +{ + request_rec *r = r_; + if (value == NULL || r->server->limit_req_fieldsize >= strlen(value) ) + return 1; + + r->status = HTTP_BAD_REQUEST; + apr_table_setn(r->notes, "error-notes", + apr_pstrcat(r->pool, "Size of a request header field " + "after merging exceeds server limit.<br />" + "\n<pre>\n", + ap_escape_html(r->pool, key), + "</pre>\n", NULL)); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request header " + "exceeds LimitRequestFieldSize after merging: %s", key); + return 0; +} + +/* get the length of the field name for logging, but no more than 80 bytes */ +#define LOG_NAME_MAX_LEN 80 +static int field_name_len(const char *field) +{ + const char *end = ap_strchr_c(field, ':'); + if (end == NULL || end - field > LOG_NAME_MAX_LEN) + return LOG_NAME_MAX_LEN; + return end - field; +} + AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) { char *last_field = NULL; @@ -715,6 +779,9 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb "<pre>\n", ap_escape_html(r->pool, field), "</pre>\n", NULL)); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00561) + "Request header exceeds LimitRequestFieldSize: " + "%.*s", field_name_len(field), field); } return; } @@ -742,6 +809,10 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb "<pre>\n", ap_escape_html(r->pool, last_field), "</pre>\n", NULL)); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562) + "Request header exceeds LimitRequestFieldSize " + "after folding: %.*s", + field_name_len(last_field), last_field); return; } @@ -767,6 +838,9 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb apr_table_setn(r->notes, "error-notes", "The number of request header fields " "exceeds this server's limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) + "Number of request headers exceeds " + "LimitRequestFields"); return; } @@ -780,6 +854,10 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb ap_escape_html(r->pool, last_field), "</pre>\n", NULL)); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564) + "Request header field is missing ':' " + "separator: %.*s", (int)LOG_NAME_MAX_LEN, + last_field); return; } @@ -831,7 +909,13 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb } } + /* Combine multiple message-header fields with the same + * field-name, following RFC 2616, 4.2. + */ apr_table_compress(r->headers_in, APR_OVERLAP_TABLES_MERGE); + + /* enforce LimitRequestFieldSize for merged headers */ + apr_table_do(table_do_fn_check_lengths, r, r->headers_in, NULL); } AP_DECLARE(void) ap_get_mime_headers(request_rec *r) @@ -852,9 +936,11 @@ request_rec *ap_read_request(conn_rec *conn) 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)); + AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn); r->pool = p; r->connection = conn; r->server = conn->base_server; @@ -893,18 +979,31 @@ request_rec *ap_read_request(conn_rec *conn) */ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO; + r->useragent_addr = conn->client_addr; + r->useragent_ip = conn->client_ip; + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ap_run_pre_read_request(r, conn); + /* 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); + if (r->status == HTTP_REQUEST_URI_TOO_LARGE + || r->status == HTTP_BAD_REQUEST) { + if (r->status == HTTP_REQUEST_URI_TOO_LARGE) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565) + "request failed: URI too long (longer than %d)", + r->server->limit_req_line); + } + else if (r->method == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00566) + "request failed: invalid characters in URI"); + } 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; + goto traceout; } else if (r->status == HTTP_REQUEST_TIME_OUT) { ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); @@ -912,18 +1011,19 @@ request_rec *ap_read_request(conn_rec *conn) ap_run_log_transaction(r); } apr_brigade_destroy(tmp_bb); - return r; + goto traceout; } apr_brigade_destroy(tmp_bb); - return NULL; + r = NULL; + goto traceout; } /* 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); + csd = ap_get_conn_socket(conn); apr_socket_timeout_get(csd, &cur_timeout); if (cur_timeout != conn->base_server->timeout) { apr_socket_timeout_set(csd, conn->base_server->timeout); @@ -933,13 +1033,13 @@ request_rec *ap_read_request(conn_rec *conn) if (!r->assbackwards) { ap_get_mime_headers_core(r, tmp_bb); if (r->status != HTTP_OK) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00567) "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; + goto traceout; } if (apr_table_get(r->headers_in, "Transfer-Encoding") @@ -958,7 +1058,7 @@ request_rec *ap_read_request(conn_rec *conn) * headers! Have to dink things just to make sure the error message * comes through... */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00568) "client sent invalid HTTP/0.9 request: HEAD %s", r->uri); r->header_only = 0; @@ -967,7 +1067,7 @@ request_rec *ap_read_request(conn_rec *conn) ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); ap_run_log_transaction(r); apr_brigade_destroy(tmp_bb); - return r; + goto traceout; } } @@ -1000,7 +1100,7 @@ request_rec *ap_read_request(conn_rec *conn) * 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, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00569) "client sent HTTP/1.1 request without hostname " "(see RFC2616 section 14.23): %s", r->uri); } @@ -1019,14 +1119,15 @@ request_rec *ap_read_request(conn_rec *conn) ap_send_error_response(r, 0); ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); ap_run_log_transaction(r); - return r; + goto traceout; } 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; + r = NULL; + goto traceout; } if (((expect = apr_table_get(r->headers_in, "Expect")) != NULL) @@ -1042,16 +1143,20 @@ request_rec *ap_read_request(conn_rec *conn) } else { r->status = HTTP_EXPECTATION_FAILED; - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570) "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; + goto traceout; } } + AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, (char *)r->uri, (char *)r->server->defn_name, r->status); + return r; + traceout: + AP_READ_REQUEST_FAILURE((uintptr_t)r); return r; } @@ -1095,13 +1200,13 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew, rnew->status = HTTP_OK; - rnew->headers_in = apr_table_copy(rnew->pool, r->headers_in); + rnew->headers_in = apr_table_copy(rnew->pool, r->headers_in); /* 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")) { + if (!r->kept_body && (apr_table_get(r->headers_in, "Content-Length") + || apr_table_get(r->headers_in, "Transfer-Encoding"))) { strip_headers_request_body(rnew); } rnew->subprocess_env = apr_table_copy(rnew->pool, r->subprocess_env); @@ -1158,42 +1263,22 @@ 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); + ap_run_note_auth_failure(r, type); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, - 0, r, "need AuthType to note auth failure: %s", r->uri); + 0, r, APLOGNO(00571) "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_note_auth_failure(r); } 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_note_auth_failure(r); } AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) @@ -1209,20 +1294,20 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) if (!ap_auth_name(r)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, - 0, r, "need AuthName: %s", r->uri); + 0, r, APLOGNO(00572) "need AuthName: %s", r->uri); return HTTP_INTERNAL_SERVER_ERROR; } if (!auth_line) { - ap_note_basic_auth_failure(r); + ap_note_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, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00573) "client used wrong authentication scheme: %s", r->uri); - ap_note_basic_auth_failure(r); + ap_note_auth_failure(r); return HTTP_UNAUTHORIZED; } @@ -1244,6 +1329,7 @@ struct content_length_ctx { * least one bucket on to the next output filter * for this request */ + apr_bucket_brigade *tmpbb; }; /* This filter computes the content length, but it also computes the number @@ -1264,6 +1350,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter( if (!ctx) { f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx)); ctx->data_sent = 0; + ctx->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc); } /* Loop through this set of buckets to compute their length @@ -1293,16 +1380,17 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter( * 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_bucket *flush; + apr_brigade_split_ex(b, e, ctx->tmpbb); + 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; + apr_brigade_cleanup(b); + APR_BRIGADE_CONCAT(b, ctx->tmpbb); e = APR_BRIGADE_FIRST(b); ctx->data_sent = 1; @@ -1311,7 +1399,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter( continue; } else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00574) "ap_content_length_filter: " "apr_bucket_read() failed"); return rv; @@ -1334,13 +1422,13 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter( * 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 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 && + !(r->header_only && r->bytes_sent == 0 && apr_table_get(r->headers_out, "Content-Length"))) { ap_set_content_length(r, r->bytes_sent); } @@ -1358,12 +1446,11 @@ AP_DECLARE(apr_status_t) ap_send_fd(apr_file_t *fd, request_rec *r, { 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); + + apr_brigade_insert_file(bb, fd, offset, len, r->pool); rv = ap_pass_brigade(r->output_filters, bb); if (rv != APR_SUCCESS) { @@ -1396,6 +1483,7 @@ AP_DECLARE(size_t) ap_send_mmap(apr_mmap_t *mm, request_rec *r, size_t offset, typedef struct { apr_bucket_brigade *bb; + apr_bucket_brigade *tmpbb; } old_write_filter_ctx; AP_CORE_DECLARE_NONSTD(apr_status_t) ap_old_write_filter( @@ -1405,7 +1493,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_old_write_filter( AP_DEBUG_ASSERT(ctx); - if (ctx->bb != 0) { + if (ctx->bb != NULL) { /* 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. @@ -1416,16 +1504,11 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_old_write_filter( return ap_pass_brigade(f->next, bb); } -static apr_status_t buffer_output(request_rec *r, - const char *str, apr_size_t len) +static ap_filter_t *insert_old_write_filter(request_rec *r) { - 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. */ @@ -1439,24 +1522,41 @@ static apr_status_t buffer_output(request_rec *r, if (f == NULL) { /* our filter hasn't been added yet */ ctx = apr_pcalloc(r->pool, sizeof(*ctx)); + ctx->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ap_add_output_filter("OLD_WRITE", ctx, r, r->connection); f = r->output_filters; } + return f; +} + +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; + + f = insert_old_write_filter(r); + ctx = f->ctx; + /* 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_status_t rv; apr_bucket *b = apr_bucket_transient_create(str, len, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, b); + APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, b); - return ap_pass_brigade(r->output_filters, bb); + rv = ap_pass_brigade(r->output_filters, ctx->tmpbb); + apr_brigade_cleanup(ctx->tmpbb); + return rv; } - /* 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); } @@ -1478,19 +1578,6 @@ AP_DECLARE(int) ap_rputc(int c, request_rec *r) 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) @@ -1611,13 +1698,20 @@ AP_DECLARE_NONSTD(int) ap_rvputs(request_rec *r, ...) AP_DECLARE(int) ap_rflush(request_rec *r) { conn_rec *c = r->connection; - apr_bucket_brigade *bb; apr_bucket *b; + ap_filter_t *f; + old_write_filter_ctx *ctx; + apr_status_t rv; + + f = insert_old_write_filter(r); + ctx = f->ctx; - 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) + APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, b); + + rv = ap_pass_brigade(r->output_filters, ctx->tmpbb); + apr_brigade_cleanup(ctx->tmpbb); + if (rv != APR_SUCCESS) return -1; return 0; @@ -1643,14 +1737,12 @@ typedef struct hdr_ptr { ap_filter_t *f; apr_bucket_brigade *bb; } hdr_ptr; - static int send_header(void *data, const char *key, const char *val) { ap_fputstrs(((hdr_ptr*)data)->f, ((hdr_ptr*)data)->bb, key, ": ", val, CRLF, NULL); return 1; } - AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) { hdr_ptr x; @@ -1662,10 +1754,18 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) return; } if (!ap_is_HTTP_INFO(r->status)) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, NULL, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00575) "Status is %d - not sending interim response", r->status); return; } + if ((r->status == HTTP_CONTINUE) && !r->expecting_100) { + /* + * Don't send 100-Continue when there was no Expect: 100-continue + * in the request headers. For origin servers this is a SHOULD NOT + * for proxies it is a MUST NOT according to RFC 2616 8.2.3 + */ + return; + } /* if we send an interim response, we're no longer in a state of * expecting one. Also, this could feasibly be in a subrequest, @@ -1686,11 +1786,15 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) apr_table_do(send_header, &x, r->headers_out, NULL); apr_table_clear(r->headers_out); } - ap_fputs(x.f, x.bb, CRLF); + ap_fputs(x.f, x.bb, CRLF_ASCII); ap_fflush(x.f, x.bb); apr_brigade_destroy(x.bb); } + +AP_IMPLEMENT_HOOK_VOID(pre_read_request, + (request_rec *r, conn_rec *c), + (r, c)) AP_IMPLEMENT_HOOK_RUN_ALL(int,post_read_request, (request_rec *r), (r), OK, DECLINED) AP_IMPLEMENT_HOOK_RUN_ALL(int,log_transaction, @@ -1699,3 +1803,6 @@ 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) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure, + (request_rec *r, const char *auth_type), + (r, auth_type), DECLINED) diff --git a/server/request.c b/server/request.c index f076e637..bb2a054d 100644 --- a/server/request.c +++ b/server/request.c @@ -32,8 +32,8 @@ #define APR_WANT_STRFUNC #include "apr_want.h" -#define CORE_PRIVATE #include "ap_config.h" +#include "ap_provider.h" #include "httpd.h" #include "http_config.h" #include "http_request.h" @@ -44,13 +44,20 @@ #include "util_filter.h" #include "util_charset.h" #include "util_script.h" +#include "ap_expr.h" +#include "mod_request.h" #include "mod_core.h" +#include "mod_auth.h" #if APR_HAVE_STDARG_H #include <stdarg.h> #endif +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + APR_HOOK_STRUCT( APR_HOOK_LINK(translate_name) APR_HOOK_LINK(map_to_storage) @@ -58,6 +65,7 @@ APR_HOOK_STRUCT( APR_HOOK_LINK(fixups) APR_HOOK_LINK(type_checker) APR_HOOK_LINK(access_checker) + APR_HOOK_LINK(access_checker_ex) APR_HOOK_LINK(auth_checker) APR_HOOK_LINK(insert_filter) APR_HOOK_LINK(create_request) @@ -75,21 +83,30 @@ 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,access_checker_ex, + (request_rec *r), (r), 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 auth_internal_per_conf = 0; +static int auth_internal_per_conf_hooks = 0; +static int auth_internal_per_conf_providers = 0; + -static int decl_die(int status, char *phase, request_rec *r) +static int decl_die(int status, const char *phase, request_rec *r) { if (status == DECLINED) { - ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, APLOGNO(00025) "configuration error: couldn't %s: %s", phase, r->uri); return HTTP_INTERNAL_SERVER_ERROR; } else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "auth phase '%s' gave status %d: %s", phase, + status, r->uri); return status; } } @@ -103,13 +120,13 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) { int file_req = (r->main && r->filename); int access_status; + core_dir_config *d; /* 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); + d = ap_get_core_module_config(r->per_dir_config); if (d->allow_encoded_slashes) { - access_status = ap_unescape_url_keep2f_ex(r->parsed_uri.path, d->decode_encoded_slashes); + access_status = ap_unescape_url_keep2f(r->parsed_uri.path, d->decode_encoded_slashes); } else { access_status = ap_unescape_url(r->parsed_uri.path); @@ -117,7 +134,7 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) if (access_status) { if (access_status == HTTP_NOT_FOUND) { if (! d->allow_encoded_slashes) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026) "found %%2f (encoded '/') in URI " "(decoded='%s'), returning 404", r->parsed_uri.path); @@ -137,6 +154,14 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) if ((access_status = ap_location_walk(r))) { return access_status; } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) { + r->log = d->log; + } if ((access_status = ap_run_translate_name(r))) { return decl_die(access_status, "translate", r); @@ -157,6 +182,14 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) if ((access_status = ap_location_walk(r))) { return access_status; } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) { + r->log = d->log; + } /* Only on the main request! */ if (r->main == NULL) { @@ -170,61 +203,81 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) * 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)) { + 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 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 { 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 ((access_status = ap_run_access_checker(r)) != OK) { + return decl_die(access_status, + "check access (with Satisfy All)", 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. Check your authn provider!" - : "perform authentication. AuthType not set!", - r); + access_status = ap_run_access_checker_ex(r); + if (access_status == OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "request authorized without authentication by " + "access_checker_ex hook: %s", r->uri); + } + else if (access_status != DECLINED) { + return decl_die(access_status, "check access", r); + } + else { + if ((access_status = ap_run_check_user_id(r)) != OK) { + return decl_die(access_status, "check user", 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. Check your 'Require' directive" - : "perform authentication. AuthType not set!", - r); + if (r->user == NULL) { + /* don't let buggy authn module crash us in authz */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00027) + "Buggy authn provider failed to set user for %s", + r->uri); + access_status = HTTP_INTERNAL_SERVER_ERROR; + return decl_die(access_status, "check user", r); + } + if ((access_status = ap_run_auth_checker(r)) != OK) { + return decl_die(access_status, "check authorization", 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_access_checker(r)) == OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "request authorized without authentication by " + "access_checker hook and 'Satisfy any': %s", + r->uri); + break; + } - 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. Check your authn provider!" - : "perform authentication. AuthType not set!", - r); + access_status = ap_run_access_checker_ex(r); + if (access_status == OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "request authorized without authentication by " + "access_checker_ex hook: %s", r->uri); + } + else if (access_status != DECLINED) { + return decl_die(access_status, "check access", r); + } + else { + if ((access_status = ap_run_check_user_id(r)) != OK) { + return decl_die(access_status, "check user", 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. Check your 'Require' directive" - : "perform authentication. AuthType not set!", - r); + if (r->user == NULL) { + /* don't let buggy authn module crash us in authz */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00028) + "Buggy authn provider failed to set user for %s", + r->uri); + access_status = HTTP_INTERNAL_SERVER_ERROR; + return decl_die(access_status, "check user", r); + } + if ((access_status = ap_run_auth_checker(r)) != OK) { + return decl_die(access_status, "check authorization", r); } } break; @@ -234,11 +287,13 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) * 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) { + if ((access_status = ap_run_type_checker(r)) != OK) { return decl_die(access_status, "find types", r); } - if ((access_status = ap_run_fixups(r)) != 0) { + if ((access_status = ap_run_fixups(r)) != OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "fixups hook gave %d: %s", + access_status, r->uri); return access_status; } @@ -267,44 +322,59 @@ typedef struct walk_cache_t { 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 */ + struct walk_cache_t *prev; /* Prev cache of same call in this (sub)req */ + int count; /* Number of prev invocations of same call in this (sub)req */ } 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. + void **note, **inherit_note; + walk_cache_t *cache, *prev_cache, *copy_cache; + int count; + + /* Find the most relevant, recent walk cache to work from and provide + * a copy the caller is allowed to munge. In the case of a sub-request + * or internal redirect, this is the cache corresponding to the equivalent + * invocation of the same function call in the "parent" request, if such + * a cache exists. Otherwise it is the walk cache of the previous + * invocation of the same function call in the current request, if + * that exists; if not, 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); + AP_DEBUG_ASSERT(note != NULL); + + copy_cache = prev_cache = *note; + count = prev_cache ? (prev_cache->count + 1) : 0; + + if ((r->prev + && (inherit_note = ap_get_request_note(r->prev, t)) + && *inherit_note) + || (r->main + && (inherit_note = ap_get_request_note(r->main, t)) + && *inherit_note)) { + walk_cache_t *inherit_cache = *inherit_note; + + while (inherit_cache->count > count) { + inherit_cache = inherit_cache->prev; } - else { - cache = apr_pcalloc(r->pool, sizeof(*cache)); - cache->walked = apr_array_make(r->pool, 4, sizeof(walk_walked_t)); + if (inherit_cache->count == count) { + copy_cache = inherit_cache; } + } - *note = cache; + if (copy_cache) { + cache = apr_pmemdup(r->pool, copy_cache, sizeof(*cache)); + cache->walked = apr_array_copy(r->pool, cache->walked); + cache->prev = prev_cache; + cache->count = count; + } + else { + cache = apr_pcalloc(r->pool, sizeof(*cache)); + cache->walked = apr_array_make(r->pool, 4, sizeof(walk_walked_t)); } + + *note = cache; + return cache; } @@ -341,7 +411,6 @@ static walk_cache_t *prep_walk_cache(apr_size_t t, request_rec *r) 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))) { @@ -353,9 +422,9 @@ static int resolve_symlink(char *d, apr_finfo_t *lfi, int opts, apr_pool_t *p) /* if OPT_SYM_OWNER is unset, we only need to check target accessible */ if (!(opts & OPT_SYM_OWNER)) { - if ((res = apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME - | APR_FINFO_LINK), p)) - != APR_SUCCESS) { + if (apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME | APR_FINFO_LINK), p) + != APR_SUCCESS) + { return HTTP_FORBIDDEN; } @@ -374,15 +443,14 @@ static int resolve_symlink(char *d, apr_finfo_t *lfi, int opts, apr_pool_t *p) * owner of the symlink, then get the info of the target. */ if (!(lfi->valid & APR_FINFO_OWNER)) { - if ((res = apr_stat(lfi, d, - lfi->valid | APR_FINFO_LINK | APR_FINFO_OWNER, p)) - != APR_SUCCESS) { + if (apr_stat(lfi, 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) { + if (apr_stat(&fi, d, lfi->valid & ~(APR_FINFO_NAME), p) != APR_SUCCESS) { return HTTP_FORBIDDEN; } @@ -418,11 +486,12 @@ typedef struct core_opts_t { allow_options_t remove; overrides_t override; overrides_t override_opts; + apr_table_t *override_list; } 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); + core_dir_config *this_dir = ap_get_core_module_config(sec); if (!this_dir) { return; @@ -445,6 +514,11 @@ static void core_opts_merge(const ap_conf_vector_t *sec, core_opts_t *opts) opts->override = this_dir->override; opts->override_opts = this_dir->override_opts; } + + if (this_dir->override_list != NULL) { + opts->override_list = this_dir->override_list; + } + } @@ -466,13 +540,14 @@ static void core_opts_merge(const ap_conf_vector_t *sec, core_opts_t *opts) 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); + core_server_config *sconf = + ap_get_core_module_config(r->server->module_config); 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; + int cached; /* XXX: Better (faster) tests needed!!! * @@ -483,7 +558,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) * handler. Leave INFO notes here for module debugging. */ if (r->filename == NULL) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00029) "Module bug? Request filename is missing for URI %s", r->uri); return OK; @@ -496,7 +571,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) 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, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00030) "Module bug? Request filename path %s is invalid or " "or not absolute for uri %s", r->filename, r->uri); @@ -510,6 +585,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) r->filename = entry_dir; cache = prep_walk_cache(AP_NOTE_DIRECTORY_WALK, r); + cached = (cache->cached != NULL); /* If this is not a dirent subrequest with a preconstructed * r->finfo value, then we can simply stat the filename to @@ -521,7 +597,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) * 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) { + if (r->finfo.filetype == APR_NOFILE || 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 @@ -535,10 +611,10 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) * Also reset if the stat failed, just for safety. */ if ((rv != APR_SUCCESS) || - (r->finfo.filetype && + (r->finfo.filetype != APR_NOFILE && (r->finfo.filetype != APR_DIR) && (r->filename[strlen(r->filename) - 1] == '/'))) { - r->finfo.filetype = 0; /* forget what we learned */ + r->finfo.filetype = APR_NOFILE; /* forget what we learned */ } } @@ -553,13 +629,12 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) * and the vhost's list of directory sections hasn't changed, * we can skip rewalking the directory_walk entries. */ - if (cache->cached + if (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)) { - int familiar = 0; /* Well this looks really familiar! If our end-result (per_dir_result) @@ -571,17 +646,18 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) familiar = 1; } - else if (r->per_dir_config == cache->dir_conf_merged) { + if (r->per_dir_config == cache->dir_conf_merged) { r->per_dir_config = cache->per_dir_result; familiar = 1; } + if (familiar) { apr_finfo_t thisinfo; int res; allow_options_t opts; core_dir_config *this_dir; - this_dir = ap_get_module_config(r->per_dir_config, &core_module); + this_dir = ap_get_core_module_config(r->per_dir_config); opts = this_dir->opts; /* * If Symlinks are allowed in general we do not need the following @@ -607,7 +683,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) * with a HTTP_FORBIDDEN in case we hit a race condition * here. */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00031) "access to %s failed; stat of '%s' failed.", r->uri, r->filename); return r->status = HTTP_FORBIDDEN; @@ -616,7 +692,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) /* Is this a possibly acceptable symlink? */ if ((res = resolve_symlink(r->filename, &thisinfo, opts, r->pool)) != OK) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00032) "Symbolic link not allowed " "or link target not accessible: %s", r->filename); @@ -638,6 +714,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) */ int sec_idx; int matches = cache->walked->nelts; + int cached_matches = matches; walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; core_dir_config *this_dir; core_opts_t opts; @@ -655,18 +732,21 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) apr_size_t canonical_len; #endif + cached &= auth_internal_per_conf; + /* * 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); + this_dir = ap_get_core_module_config(r->per_dir_config); opts.opts = this_dir->opts; opts.add = this_dir->opts_add; opts.remove = this_dir->opts_remove; opts.override = this_dir->override; opts.override_opts = this_dir->override_opts; + opts.override_list = this_dir->override_list; /* Set aside path_info to merge back onto path_info later. * If r->filename is a directory, we must remerge the path_info, @@ -682,7 +762,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) r->path_info, APR_FILEPATH_NOTABOVEROOT, r->pool)) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00033) "dir_walk error, path_info %s is not relative " "to the filename path %s for uri %s", r->path_info, r->filename, r->uri); @@ -749,7 +829,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) #endif if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00034) "dir_walk error, could not determine the root " "path of filename %s%s for uri %s", r->filename, r->path_info, r->uri); @@ -802,7 +882,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) 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); + entry_core = ap_get_core_module_config(entry_config); /* No more possible matches for this many segments? * We are done when we find relative/regex/longer components. @@ -846,6 +926,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) */ cache->walked->nelts -= matches; matches = 0; + cached = 0; } if (now_merged) { @@ -872,12 +953,13 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) /* No htaccess in an incomplete root path, * nor if it's disabled */ - if (seg < startseg || !opts.override) { + if (seg < startseg || (!opts.override && opts.override_list == NULL)) { break; } + res = ap_parse_htaccess(&htaccess_conf, r, opts.override, - opts.override_opts, + opts.override_opts, opts.override_list, apr_pstrdup(r->pool, r->filename), sconf->access_name); if (res) { @@ -913,6 +995,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) */ cache->walked->nelts -= matches; matches = 0; + cached = 0; } if (now_merged) { @@ -979,7 +1062,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) * if...we have allowed symlinks * skip the lstat and dummy up an APR_DIR value for thisinfo. */ - if (r->finfo.filetype + if (r->finfo.filetype != APR_NOFILE #ifdef CASE_BLIND_FILESYSTEM && (filename_len <= canonical_len) #endif @@ -1010,8 +1093,10 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) break; } else if (APR_STATUS_IS_EACCES(rv)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - "access to %s denied", r->uri); + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00035) + "access to %s denied because search " + "permissions are missing on a component " + "of the path", r->uri); return r->status = HTTP_FORBIDDEN; } else if ((rv != APR_SUCCESS && rv != APR_INCOMPLETE) @@ -1019,7 +1104,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) /* If we hit ENOTDIR, we must have over-optimized, deny * rather than assume not found. */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00036) "access to %s failed", r->uri); return r->status = HTTP_FORBIDDEN; } @@ -1041,7 +1126,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) */ if ((res = resolve_symlink(r->filename, &thisinfo, opts.opts, r->pool)) != OK) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00037) "Symbolic link not allowed " "or link target not accessible: %s", r->filename); @@ -1058,7 +1143,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) break; } else if (thisinfo.filetype != APR_DIR) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00038) "Forbidden: %s doesn't point to " "a file or directory", r->filename); @@ -1071,7 +1156,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) /* If we have _not_ optimized, this is the time to recover * the final stat result. */ - if (!r->finfo.filetype || r->finfo.filetype == APR_LNK) { + if (r->finfo.filetype == APR_NOFILE || r->finfo.filetype == APR_LNK) { r->finfo = thisinfo; } @@ -1094,13 +1179,13 @@ AP_DECLARE(int) ap_directory_walk(request_rec *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); + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); if (!entry_core->r) { continue; } - if (ap_regexec(entry_core->r, r->filename, 0, NULL, AP_REG_NOTEOL)) { + if (ap_regexec(entry_core->r, r->filename, 0, NULL, 0)) { continue; } @@ -1125,6 +1210,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) */ cache->walked->nelts -= matches; matches = 0; + cached = 0; } if (now_merged) { @@ -1141,11 +1227,16 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) last_walk->merged = now_merged; } - /* Whoops - everything matched in sequence, but the original walk - * found some additional matches. Truncate them. + /* Whoops - everything matched in sequence, but either the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. */ if (matches) { cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; } } @@ -1185,6 +1276,12 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) cache->cached = ap_make_dirstr_parent(r->pool, r->filename); } + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + cache->dir_conf_tested = sec_ent; cache->dir_conf_merged = r->per_dir_config; @@ -1205,12 +1302,13 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) 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); + core_server_config *sconf = + ap_get_core_module_config(r->server->module_config); 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; + int cached; /* 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 @@ -1221,6 +1319,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) } cache = prep_walk_cache(AP_NOTE_LOCATION_WALK, r); + cached = (cache->cached != NULL); /* Location and LocationMatch differ on their behaviour w.r.t. multiple * slashes. Location matches multiple slashes with a single slash, @@ -1240,7 +1339,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) * and the vhost's list of locations hasn't changed, we can skip * rewalking the location_walk entries. */ - if (cache->cached + if (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) @@ -1252,11 +1351,6 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) 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; @@ -1268,7 +1362,10 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) */ int len, sec_idx; int matches = cache->walked->nelts; + int cached_matches = matches; walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + + cached &= auth_internal_per_conf; cache->cached = entry_uri; /* Go through the location entries, and check for matches. @@ -1278,7 +1375,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) 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); + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); /* ### const strlen can be optimized in location config parsing */ len = strlen(entry_core->d); @@ -1293,7 +1390,8 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) : (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] != '/' + || (len > 0 + && entry_core->d[len - 1] != '/' && cache->cached[len] != '/' && cache->cached[len] != '\0')))) { continue; @@ -1314,6 +1412,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) */ cache->walked->nelts -= matches; matches = 0; + cached = 0; } if (now_merged) { @@ -1330,14 +1429,25 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) last_walk->merged = now_merged; } - /* Whoops - everything matched in sequence, but the original walk - * found some additional matches. Truncate them. + /* Whoops - everything matched in sequence, but either the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. */ if (matches) { cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; } } + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + cache->dir_conf_tested = sec_ent; cache->dir_conf_merged = r->per_dir_config; @@ -1357,12 +1467,17 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) 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; + core_dir_config *dconf = ap_get_core_module_config(r->per_dir_config); + ap_conf_vector_t **sec_ent = NULL; + int num_sec = 0; walk_cache_t *cache; const char *test_file; + int cached; + + if (dconf->sec_file) { + sec_ent = (ap_conf_vector_t **)dconf->sec_file->elts; + num_sec = dconf->sec_file->nelts; + } /* 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. @@ -1373,6 +1488,7 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) } cache = prep_walk_cache(AP_NOTE_FILE_WALK, r); + cached = (cache->cached != NULL); /* 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 @@ -1397,7 +1513,7 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) * and the directory's list of file sections hasn't changed, we * can skip rewalking the file_walk entries. */ - if (cache->cached + if (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) @@ -1409,11 +1525,6 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) 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; @@ -1425,7 +1536,10 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) */ int sec_idx; int matches = cache->walked->nelts; + int cached_matches = matches; walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + + cached &= auth_internal_per_conf; cache->cached = test_file; /* Go through the location entries, and check for matches. @@ -1433,9 +1547,8 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) * 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); + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); if (entry_core->r ? ap_regexec(entry_core->r, cache->cached , 0, NULL, 0) @@ -1460,6 +1573,7 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) */ cache->walked->nelts -= matches; matches = 0; + cached = 0; } if (now_merged) { @@ -1476,12 +1590,155 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) last_walk->merged = now_merged; } - /* Whoops - everything matched in sequence, but the original walk - * found some additional matches. Truncate them. + /* Whoops - everything matched in sequence, but either the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. */ if (matches) { cache->walked->nelts -= matches; + cached = 0; } + else if (cache->walked->nelts > cached_matches) { + cached = 0; + } + } + + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; + } + + 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_if_walk(request_rec *r) +{ + ap_conf_vector_t *now_merged = NULL; + core_dir_config *dconf = ap_get_core_module_config(r->per_dir_config); + ap_conf_vector_t **sec_ent = NULL; + int num_sec = 0; + walk_cache_t *cache; + int cached; + int sec_idx; + int matches; + int cached_matches; + int prev_result = -1; + walk_walked_t *last_walk; + + if (dconf->sec_if) { + sec_ent = (ap_conf_vector_t **)dconf->sec_if->elts; + num_sec = dconf->sec_if->nelts; + } + + /* No tricks here, there are just no <If > 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 <If >. + */ + if (!num_sec) { + return OK; + } + + cache = prep_walk_cache(AP_NOTE_IF_WALK, r); + cached = (cache->cached != NULL); + cache->cached = (void *)1; + matches = cache->walked->nelts; + cached_matches = matches; + last_walk = (walk_walked_t*)cache->walked->elts; + + cached &= auth_internal_per_conf; + + /* Go through the if entries, and check for matches */ + for (sec_idx = 0; sec_idx < num_sec; ++sec_idx) { + const char *err = NULL; + core_dir_config *entry_core; + int rc; + entry_core = ap_get_core_module_config(sec_ent[sec_idx]); + + AP_DEBUG_ASSERT(entry_core->condition_ifelse != 0); + if (entry_core->condition_ifelse & AP_CONDITION_ELSE) { + AP_DEBUG_ASSERT(prev_result != -1); + if (prev_result == 1) + continue; + } + + if (entry_core->condition_ifelse & AP_CONDITION_IF) { + rc = ap_expr_exec(r, entry_core->condition, &err); + if (rc <= 0) { + if (rc < 0) + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00039) + "Failed to evaluate <If > condition: %s", + err); + prev_result = 0; + continue; + } + prev_result = 1; + } + else { + prev_result = -1; + } + + /* 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; + cached = 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; + } + + /* Everything matched in sequence, but it may be that the original + * walk found some additional matches (which we need to truncate), or + * this walk found some additional matches. + */ + if (matches) { + cache->walked->nelts -= matches; + cached = 0; + } + else if (cache->walked->nelts > cached_matches) { + cached = 0; + } + + if (cached + && r->per_dir_config == cache->dir_conf_merged) { + r->per_dir_config = cache->per_dir_result; + return OK; } cache->dir_conf_tested = sec_ent; @@ -1531,6 +1788,7 @@ static request_rec *make_sub_request(const request_rec *r, rnew->request_time = r->request_time; rnew->connection = r->connection; rnew->server = r->server; + rnew->log = r->log; rnew->request_config = ap_create_request_config(rnew->pool); @@ -1574,6 +1832,9 @@ static request_rec *make_sub_request(const request_rec *r, rnew->output_filters = r->proto_output_filters; } + rnew->useragent_addr = r->useragent_addr; + rnew->useragent_ip = r->useragent_ip; + /* no input filters for a subrequest */ ap_set_sub_req_protocol(rnew, r); @@ -1588,6 +1849,9 @@ static request_rec *make_sub_request(const request_rec *r, */ rnew->used_path_info = AP_REQ_DEFAULT_PATH_INFO; + /* Pass on the kept body (if any) into the new request. */ + rnew->kept_body = r->kept_body; + return rnew; } @@ -1607,30 +1871,124 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_sub_req_output_filter(ap_filter_t *f, return APR_SUCCESS; } +extern APR_OPTIONAL_FN_TYPE(authz_some_auth_required) *ap__authz_ap_some_auth_required; AP_DECLARE(int) ap_some_auth_required(request_rec *r) { /* Is there a require line configured for the type of *this* req? */ + if (ap__authz_ap_some_auth_required) { + return ap__authz_ap_some_auth_required(r); + } + else + return 0; +} + +AP_DECLARE(void) ap_clear_auth_internal(void) +{ + auth_internal_per_conf_hooks = 0; + auth_internal_per_conf_providers = 0; +} - const apr_array_header_t *reqs_arr = ap_requires(r); - require_line *reqs; - int i; +AP_DECLARE(void) ap_setup_auth_internal(apr_pool_t *ptemp) +{ + int total_auth_hooks = 0; + int total_auth_providers = 0; - if (!reqs_arr) { - return 0; + auth_internal_per_conf = 0; + + if (_hooks.link_access_checker) { + total_auth_hooks += _hooks.link_access_checker->nelts; + } + if (_hooks.link_access_checker_ex) { + total_auth_hooks += _hooks.link_access_checker_ex->nelts; + } + if (_hooks.link_check_user_id) { + total_auth_hooks += _hooks.link_check_user_id->nelts; + } + if (_hooks.link_auth_checker) { + total_auth_hooks += _hooks.link_auth_checker->nelts; } - reqs = (require_line *) reqs_arr->elts; + if (total_auth_hooks > auth_internal_per_conf_hooks) { + return; + } - for (i = 0; i < reqs_arr->nelts; ++i) { - if (reqs[i].method_mask & (AP_METHOD_BIT << r->method_number)) { - return 1; - } + total_auth_providers += + ap_list_provider_names(ptemp, AUTHN_PROVIDER_GROUP, + AUTHN_PROVIDER_VERSION)->nelts; + total_auth_providers += + ap_list_provider_names(ptemp, AUTHZ_PROVIDER_GROUP, + AUTHZ_PROVIDER_VERSION)->nelts; + + if (total_auth_providers > auth_internal_per_conf_providers) { + return; + } + + auth_internal_per_conf = 1; +} + +AP_DECLARE(apr_status_t) ap_register_auth_provider(apr_pool_t *pool, + const char *provider_group, + const char *provider_name, + const char *provider_version, + const void *provider, + int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_providers; + } + + return ap_register_provider(pool, provider_group, provider_name, + provider_version, provider); +} + +AP_DECLARE(void) ap_hook_check_access(ap_HOOK_access_checker_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; } - return 0; + ap_hook_access_checker(pf, aszPre, aszSucc, nOrder); } +AP_DECLARE(void) ap_hook_check_access_ex(ap_HOOK_access_checker_ex_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_access_checker_ex(pf, aszPre, aszSucc, nOrder); +} + +AP_DECLARE(void) ap_hook_check_authn(ap_HOOK_check_user_id_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_check_user_id(pf, aszPre, aszSucc, nOrder); +} + +AP_DECLARE(void) ap_hook_check_authz(ap_HOOK_auth_checker_t *pf, + const char * const *aszPre, + const char * const *aszSucc, + int nOrder, int type) +{ + if ((type & AP_AUTH_INTERNAL_MASK) == AP_AUTH_INTERNAL_PER_CONF) { + ++auth_internal_per_conf_hooks; + } + + ap_hook_auth_checker(pf, aszPre, aszSucc, nOrder); +} AP_DECLARE(request_rec *) ap_sub_req_method_uri(const char *method, const char *new_uri, @@ -1761,7 +2119,7 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent, if (((rv = apr_stat(&rnew->finfo, rnew->filename, APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS) && (rv != APR_INCOMPLETE)) { - rnew->finfo.filetype = 0; + rnew->finfo.filetype = APR_NOFILE; } } else { @@ -1769,7 +2127,7 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent, APR_FINFO_LINK | APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS) && (rv != APR_INCOMPLETE)) { - rnew->finfo.filetype = 0; + rnew->finfo.filetype = APR_NOFILE; } } } @@ -1867,7 +2225,7 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file, if (((rv = apr_stat(&rnew->finfo, rnew->filename, APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS) && (rv != APR_INCOMPLETE)) { - rnew->finfo.filetype = 0; + rnew->finfo.filetype = APR_NOFILE; } } else { @@ -1875,7 +2233,7 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file, APR_FINFO_LINK | APR_FINFO_MIN, rnew->pool)) != APR_SUCCESS) && (rv != APR_INCOMPLETE)) { - rnew->finfo.filetype = 0; + rnew->finfo.filetype = APR_NOFILE; } } @@ -1924,7 +2282,7 @@ AP_DECLARE(int) ap_run_sub_req(request_rec *r) /* Run the quick handler if the subrequest is not a dirent or file * subrequest */ - if (!(r->filename && r->finfo.filetype)) { + if (!(r->filename && r->finfo.filetype != APR_NOFILE)) { retval = ap_run_quick_handler(r, 0); } if (retval != OK) { @@ -1962,3 +2320,4 @@ 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 index 85f37557..72aa0704 100644 --- a/server/scoreboard.c +++ b/server/scoreboard.c @@ -34,14 +34,52 @@ #include "http_config.h" #include "ap_mpm.h" -#include "mpm.h" #include "scoreboard.h" +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + AP_DECLARE_DATA scoreboard *ap_scoreboard_image = NULL; AP_DECLARE_DATA const char *ap_scoreboard_fname = NULL; + +const char * ap_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; +} + +/* Default to false when mod_status is not loaded */ AP_DECLARE_DATA int ap_extended_status = 0; + +const char *ap_set_extended_status(cmd_parms *cmd, void *dummy, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + ap_extended_status = arg; + return NULL; +} + AP_DECLARE_DATA int ap_mod_status_reqtail = 0; +const char *ap_set_reqtail(cmd_parms *cmd, void *dummy, int arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + ap_mod_status_reqtail = arg; + return NULL; +} + #if APR_HAS_SHARED_MEMORY #include "apr_shm.h" @@ -61,15 +99,15 @@ 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; +static APR_OPTIONAL_FN_TYPE(ap_logio_get_last_bytes) + *pfn_ap_logio_get_last_bytes; struct ap_sb_handle_t { int child_num; int thread_num; }; -static int server_limit, thread_limit, lb_limit; +static int server_limit, thread_limit; static apr_size_t scoreboard_size; /* @@ -95,18 +133,11 @@ 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; + + pfn_ap_logio_get_last_bytes = APR_RETRIEVE_OPTIONAL_FN(ap_logio_get_last_bytes); return scoreboard_size; } @@ -118,7 +149,7 @@ void ap_init_scoreboard(void *shared_score) ap_calc_scoreboard_size(); ap_scoreboard_image = - calloc(1, sizeof(scoreboard) + server_limit * sizeof(worker_score *)); + ap_calloc(1, sizeof(scoreboard) + server_limit * sizeof(worker_score *)); more_storage = shared_score; ap_scoreboard_image->global = (global_score *)more_storage; more_storage += sizeof(global_score); @@ -130,14 +161,9 @@ void ap_init_scoreboard(void *shared_score) 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; } /** @@ -156,7 +182,7 @@ static apr_status_t create_namebased_scoreboard(apr_pool_t *pool, rv = apr_shm_create(&ap_scoreboard_shm, scoreboard_size, fname, pool); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00001) "unable to create or access scoreboard \"%s\" " "(name-based shared memory failure)", fname); return rv; @@ -181,9 +207,9 @@ static apr_status_t open_scoreboard(apr_pool_t *pconf) */ rv = apr_pool_create(&global_pool, NULL); if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00002) "Fatal error: unable to create global pool " - "for use with by the scoreboard"); + "for use by the scoreboard"); return rv; } @@ -192,7 +218,7 @@ static apr_status_t open_scoreboard(apr_pool_t *pconf) /* 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, APR_EBADPATH, ap_server_conf, APLOGNO(00003) "Fatal error: Invalid Scoreboard path %s", ap_scoreboard_fname); return APR_EBADPATH; @@ -203,7 +229,7 @@ static apr_status_t open_scoreboard(apr_pool_t *pconf) 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, + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00004) "Unable to create or access scoreboard " "(anonymous shared memory failure)"); return rv; @@ -221,7 +247,7 @@ static apr_status_t open_scoreboard(apr_pool_t *pconf) return APR_SUCCESS; } -/* If detach is non-zero, this is a seperate child process, +/* If detach is non-zero, this is a separate 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) @@ -231,7 +257,7 @@ apr_status_t ap_reopen_scoreboard(apr_pool_t *p, apr_shm_t **shm, int detached) return APR_SUCCESS; } if (apr_shm_size_get(ap_scoreboard_shm) < scoreboard_size) { - ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00005) "Fatal error: shared scoreboard too small for child!"); apr_shm_detach(ap_scoreboard_shm); ap_scoreboard_shm = NULL; @@ -271,6 +297,8 @@ int ap_create_scoreboard(apr_pool_t *p, ap_scoreboard_e sb_type) apr_status_t rv; #endif + pfn_ap_logio_get_last_bytes = APR_RETRIEVE_OPTIONAL_FN(ap_logio_get_last_bytes); + if (ap_scoreboard_image) { ap_scoreboard_image->global->restart_time = apr_time_now(); memset(ap_scoreboard_image->parent, 0, @@ -279,11 +307,6 @@ int ap_create_scoreboard(apr_pool_t *p, ap_scoreboard_e sb_type) 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; } @@ -302,13 +325,7 @@ int ap_create_scoreboard(apr_pool_t *p, ap_scoreboard_e sb_type) #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; - } + void *sb_mem = ap_calloc(1, scoreboard_size); ap_init_scoreboard(sb_mem); } @@ -340,11 +357,21 @@ AP_DECLARE(int) ap_exists_scoreboard_image(void) AP_DECLARE(void) ap_increment_counts(ap_sb_handle_t *sb, request_rec *r) { worker_score *ws; + apr_off_t bytes; if (!sb) return; ws = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num]; + if (pfn_ap_logio_get_last_bytes != NULL) { + bytes = pfn_ap_logio_get_last_bytes(r->connection); + } + else if (r->method_number == M_GET && r->method[0] == 'H') { + bytes = 0; + } + else { + bytes = r->bytes_sent; + } #ifdef HAVE_TIMES times(&ws->times); @@ -352,12 +379,12 @@ AP_DECLARE(void) ap_increment_counts(ap_sb_handle_t *sb, request_rec *r) 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; + ws->bytes_served += bytes; + ws->my_bytes_served += bytes; + ws->conn_bytes += bytes; } -int find_child_by_pid(apr_proc_t *pid) +AP_DECLARE(int) ap_find_child_by_pid(apr_proc_t *pid) { int i; int max_daemons_limit; @@ -417,18 +444,16 @@ static void copy_request(char *rbuf, apr_size_t rbuflen, request_rec *r) } } -AP_DECLARE(int) ap_update_child_status_from_indexes(int child_num, - int thread_num, - int status, - request_rec *r) +static int update_child_status_internal(int child_num, + int thread_num, + int status, + conn_rec *c, + request_rec *r) { int old_status; worker_score *ws; process_score *ps; - - if (child_num < 0) { - return -1; - } + int mpm_generation; ws = &ap_scoreboard_image->servers[child_num][thread_num]; old_status = ws->status; @@ -439,7 +464,8 @@ AP_DECLARE(int) ap_update_child_status_from_indexes(int child_num, if (status == SERVER_READY && old_status == SERVER_STARTING) { ws->thread_num = child_num * thread_limit + thread_num; - ps->generation = ap_my_generation; + ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation); + ps->generation = mpm_generation; } if (ap_extended_status) { @@ -456,29 +482,62 @@ AP_DECLARE(int) ap_update_child_status_from_indexes(int child_num, 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)); copy_request(ws->request, sizeof(ws->request), r); - apr_cpystrn(ws->vhost, r->server->server_hostname, - sizeof(ws->vhost)); + if (r->server) { + apr_cpystrn(ws->vhost, r->server->server_hostname, + sizeof(ws->vhost)); + } + } + else if (c) { + apr_cpystrn(ws->client, ap_get_remote_host(c, NULL, + REMOTE_NOLOOKUP, NULL), sizeof(ws->client)); + ws->request[0]='\0'; + ws->vhost[0]='\0'; } } return old_status; } +AP_DECLARE(int) ap_update_child_status_from_indexes(int child_num, + int thread_num, + int status, + request_rec *r) +{ + if (child_num < 0) { + return -1; + } + + return update_child_status_internal(child_num, thread_num, status, + r ? r->connection : NULL, + r); +} + AP_DECLARE(int) ap_update_child_status(ap_sb_handle_t *sbh, int status, request_rec *r) { if (!sbh) return -1; - return ap_update_child_status_from_indexes(sbh->child_num, sbh->thread_num, - status, r); + return update_child_status_internal(sbh->child_num, sbh->thread_num, + status, + r ? r->connection : NULL, + r); } -void ap_time_process_request(ap_sb_handle_t *sbh, int status) +AP_DECLARE(int) ap_update_child_status_from_conn(ap_sb_handle_t *sbh, int status, + conn_rec *c) +{ + if (!sbh) + return -1; + + return update_child_status_internal(sbh->child_num, sbh->thread_num, + status, c, NULL); +} + +AP_DECLARE(void) ap_time_process_request(ap_sb_handle_t *sbh, int status) { worker_score *ws; @@ -499,18 +558,27 @@ void ap_time_process_request(ap_sb_handle_t *sbh, int status) } } -AP_DECLARE(worker_score *) ap_get_scoreboard_worker(int x, int y) +AP_DECLARE(worker_score *) ap_get_scoreboard_worker_from_indexes(int x, int y) { - if (((x < 0) || (server_limit < x)) || - ((y < 0) || (thread_limit < y))) { + if (((x < 0) || (x >= server_limit)) || + ((y < 0) || (y >= thread_limit))) { return(NULL); /* Out of range */ } return &ap_scoreboard_image->servers[x][y]; } +AP_DECLARE(worker_score *) ap_get_scoreboard_worker(ap_sb_handle_t *sbh) +{ + if (!sbh) + return NULL; + + return ap_get_scoreboard_worker_from_indexes(sbh->child_num, + sbh->thread_num); +} + AP_DECLARE(process_score *) ap_get_scoreboard_process(int x) { - if ((x < 0) || (server_limit < x)) { + if ((x < 0) || (x >= server_limit)) { return(NULL); /* Out of range */ } return &ap_scoreboard_image->parent[x]; @@ -520,11 +588,3 @@ 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 index d0b90c6a..9ff8e394 100644 --- a/server/util.c +++ b/server/util.c @@ -38,12 +38,13 @@ #if APR_HAVE_UNISTD_H #include <unistd.h> #endif +#if APR_HAVE_PROCESS_H +#include <process.h> /* for getpid() on Win32 */ +#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" @@ -51,7 +52,9 @@ #include "http_log.h" #include "http_protocol.h" #include "http_config.h" +#include "http_core.h" #include "util_ebcdic.h" +#include "util_varbuf.h" #ifdef HAVE_PWD_H #include <pwd.h> @@ -78,10 +81,16 @@ */ #ifdef CASE_BLIND_FILESYSTEM #define IS_SLASH(s) ((s == '/') || (s == '\\')) +#define SLASHES "/\\" #else #define IS_SLASH(s) (s == '/') +#define SLASHES "/" #endif +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + /* * Examine a field value (such as a media-/content-type) string and return @@ -349,54 +358,79 @@ AP_DECLARE(const char *) ap_stripprefix(const char *bigstring, * 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. * + * nmatch must be <=AP_MAX_REG_MATCH (10). + * * 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. + * It returns the substituted string, or NULL if a vbuf is used. + * On errors, returns the orig string. * * 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[]) +static apr_status_t regsub_core(apr_pool_t *p, char **result, + struct ap_varbuf *vb, const char *input, + const char *source, size_t nmatch, + ap_regmatch_t pmatch[], apr_size_t maxlen) { const char *src = input; - char *dest, *dst; + char *dst; char c; size_t no; - int len; - - if (!source) - return NULL; - if (!nmatch) - return apr_pstrdup(p, src); + apr_size_t len = 0; + + AP_DEBUG_ASSERT((result && p && !vb) || (vb && !p && !result)); + if (!source || nmatch>AP_MAX_REG_MATCH) + return APR_EINVAL; + if (!nmatch) { + len = strlen(src); + if (maxlen > 0 && len >= maxlen) + return APR_ENOMEM; + if (!vb) { + *result = apr_pstrmemdup(p, src, len); + return APR_SUCCESS; + } + else { + ap_varbuf_strmemcat(vb, src, len); + return APR_SUCCESS; + } + } /* First pass, find the size */ - - len = 0; - while ((c = *src++) != '\0') { - if (c == '&') - no = 0; - else if (c == '$' && apr_isdigit(*src)) + if (c == '$' && apr_isdigit(*src)) no = *src++ - '0'; else - no = 10; + no = AP_MAX_REG_MATCH; - if (no > 9) { /* Ordinary character. */ - if (c == '\\' && (*src == '$' || *src == '&')) + if (no >= AP_MAX_REG_MATCH) { /* Ordinary character. */ + if (c == '\\' && *src) src++; len++; } else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { + if (APR_SIZE_MAX - len <= pmatch[no].rm_eo - pmatch[no].rm_so) + return APR_ENOMEM; len += pmatch[no].rm_eo - pmatch[no].rm_so; } } - dest = dst = apr_pcalloc(p, len + 1); + if (len >= maxlen && maxlen > 0) + return APR_ENOMEM; + + if (!vb) { + *result = dst = apr_palloc(p, len + 1); + } + else { + if (vb->buf && vb->strlen == AP_VARBUF_UNKNOWN) + vb->strlen = strlen(vb->buf); + ap_varbuf_grow(vb, vb->strlen + len); + dst = vb->buf + vb->strlen; + vb->strlen += len; + } /* Now actually fill in the string */ @@ -408,9 +442,9 @@ AP_DECLARE(char *) ap_pregsub(apr_pool_t *p, const char *input, else if (c == '$' && apr_isdigit(*src)) no = *src++ - '0'; else - no = 10; + no = AP_MAX_REG_MATCH; - if (no > 9) { /* Ordinary character. */ + if (no >= AP_MAX_REG_MATCH) { /* Ordinary character. */ if (c == '\\' && (*src == '$' || *src == '&')) c = *src++; *dst++ = c; @@ -424,7 +458,34 @@ AP_DECLARE(char *) ap_pregsub(apr_pool_t *p, const char *input, } *dst = '\0'; - return dest; + return APR_SUCCESS; +} + +#ifndef AP_PREGSUB_MAXLEN +#define AP_PREGSUB_MAXLEN (HUGE_STRING_LEN * 8) +#endif +AP_DECLARE(char *) ap_pregsub(apr_pool_t *p, const char *input, + const char *source, size_t nmatch, + ap_regmatch_t pmatch[]) +{ + char *result; + apr_status_t rc = regsub_core(p, &result, NULL, input, source, nmatch, + pmatch, AP_PREGSUB_MAXLEN); + if (rc != APR_SUCCESS) + result = NULL; + return result; +} + +AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, + const char *input, const char *source, + size_t nmatch, ap_regmatch_t pmatch[], + apr_size_t maxlen) +{ + apr_status_t rc = regsub_core(p, result, NULL, input, source, nmatch, + pmatch, maxlen); + if (rc != APR_SUCCESS) + *result = NULL; + return rc; } /* @@ -580,9 +641,8 @@ AP_DECLARE(char *) ap_make_dirstr_parent(apr_pool_t *p, const char *s) return apr_pstrdup(p, ""); } l = (last_slash - s) + 1; - d = apr_palloc(p, l + 1); - memcpy(d, s, l); - d[l] = 0; + d = apr_pstrmemdup(p, s, l); + return (d); } @@ -613,9 +673,7 @@ AP_DECLARE(char *) ap_getword(apr_pool_t *atrans, const char **line, char stop) } len = pos - *line; - res = (char *)apr_palloc(atrans, len + 1); - memcpy(res, *line, len); - res[len] = 0; + res = apr_pstrmemdup(atrans, *line, len); if (stop) { while (*pos == stop) { @@ -643,9 +701,7 @@ AP_DECLARE(char *) ap_getword_white(apr_pool_t *atrans, const char **line) } len = pos - *line; - res = (char *)apr_palloc(atrans, len + 1); - memcpy(res, *line, len); - res[len] = 0; + res = apr_pstrmemdup(atrans, *line, len); while (apr_isspace(*pos)) { ++pos; @@ -669,8 +725,9 @@ AP_DECLARE(char *) ap_getword_nulls(apr_pool_t *atrans, const char **line, char *res; if (!pos) { - res = apr_pstrdup(atrans, *line); - *line += strlen(*line); + size_t len = strlen(*line); + res = apr_pstrmemdup(atrans, *line, len); + *line += len; return res; } @@ -759,128 +816,29 @@ AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line) 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, + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00551) "Done with config file %s", cfp->name); #endif return (cfp->close == NULL) ? 0 : cfp->close(cfp->param); } +/* we can't use apr_file_* directly because of linking issues on Windows */ static apr_status_t cfg_close(void *param) { - apr_file_t *cfp = (apr_file_t *) param; - return (apr_file_close(cfp)); + return apr_file_close(param); } -static int cfg_getch(void *param) +static apr_status_t cfg_getch(char *ch, 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; + return apr_file_getc(ch, param); } -static void *cfg_getstr(void *buf, size_t bufsiz, void *param) +static apr_status_t 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; + return apr_file_gets(buf, bufsiz, param); } /* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */ @@ -896,7 +854,7 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, #endif if (name == NULL) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00552) "Internal error: pcfg_openfile() called with NULL filename"); return APR_EBADF; } @@ -904,7 +862,7 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, 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, + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00553) "Opening config file %s (%s)", name, (status != APR_SUCCESS) ? apr_strerror(status, buf, sizeof(buf)) : "successful"); @@ -922,7 +880,7 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, #else strcmp(name, "/dev/null") != 0) { #endif /* WIN32 || OS2 */ - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00554) "Access to file %s denied by server: not a regular file", name); apr_file_close(file); @@ -955,9 +913,9 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, 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->getch = cfg_getch; + new_cfg->getstr = cfg_getstr; + new_cfg->close = cfg_close; new_cfg->line_number = 0; *ret_cfg = new_cfg; return APR_SUCCESS; @@ -965,170 +923,200 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, /* 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_DECLARE(ap_configfile_t *) ap_pcfg_open_custom( + apr_pool_t *p, const char *descr, void *param, + apr_status_t (*getc_func) (char *ch, void *param), + apr_status_t (*gets_func) (void *buf, size_t bufsize, void *param), + apr_status_t (*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->getch = getc_func; + new_cfg->getstr = gets_func; 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) +AP_DECLARE(apr_status_t) ap_cfg_getc(char *ch, ap_configfile_t *cfp) { - register int ch = cfp->getch(cfp->param); - if (ch == LF) + apr_status_t rc = cfp->getch(ch, cfp->param); + if (rc == APR_SUCCESS && *ch == LF) ++cfp->line_number; - return ch; + return rc; +} + +AP_DECLARE(const char *) ap_pcfg_strerror(apr_pool_t *p, ap_configfile_t *cfp, + apr_status_t rc) +{ + char buf[MAX_STRING_LEN]; + if (rc == APR_SUCCESS) + return NULL; + return apr_psprintf(p, "Error reading %s at line %d: %s", + cfp->name, cfp->line_number, + rc == APR_ENOSPC ? "Line too long" + : apr_strerror(rc, buf, sizeof(buf))); } /* 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) +static apr_status_t ap_cfg_getline_core(char *buf, size_t bufsize, + ap_configfile_t *cfp) { + apr_status_t rc; /* 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; + rc = cfp->getstr(cbuf, cbufsize, cfp->param); + if (rc == APR_EOF) { + if (cbuf != buf) { + *cbuf = '\0'; + break; + } + else { + return APR_EOF; + } + } + if (rc != APR_SUCCESS) { + return rc; + } /* * check for line continuation, * i.e. match [^\\]\\[\r]\n only */ cp = cbuf; - while (cp < cbuf+cbufsize && *cp != '\0') - cp++; + cp += strlen(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]; - } + /* + * line continuation requested - + * then remove backslash and continue + */ + cbufsize -= (cp-cbuf); + cbuf = cp; + continue; } } + else if (cp - buf >= bufsize - 1) { + return APR_ENOSPC; + } 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; + size_t i = 0; - if(bufsize < 2) { + if (bufsize < 2) { /* too small, assume caller is crazy */ - return 1; + return APR_EINVAL; } + buf[0] = '\0'; 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); + char c; + rc = cfp->getch(&c, cfp->param); + if (rc == APR_EOF) { + if (i > 0) + break; + else + return APR_EOF; } + if (rc != APR_SUCCESS) + return rc; 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 - */ + /* 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 - */ + continue; } - /* 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; + else { + break; + } + } + else if (i >= bufsize - 2) { + return APR_ENOSPC; } buf[i] = c; ++i; - c = cfp->getch(cfp->param); } + buf[i] = '\0'; + } + return APR_SUCCESS; +} + +static int cfg_trim_line(char *buf) +{ + char *start, *end; + /* + * Leading and trailing white space is eliminated completely + */ + start = buf; + while (apr_isspace(*start)) + ++start; + /* blast trailing whitespace */ + end = &start[strlen(start)]; + while (--end >= start && apr_isspace(*end)) + *end = '\0'; + /* Zap leading whitespace by shifting */ + if (start != buf) + memmove(buf, start, end - start + 2); +#ifdef DEBUG_CFG_LINES + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, APLOGNO(00555) "Read config: '%s'", buf); +#endif + return end - start + 1; +} + +/* 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(apr_status_t) ap_cfg_getline(char *buf, size_t bufsize, ap_configfile_t *cfp) +{ + apr_status_t rc = ap_cfg_getline_core(buf, bufsize, cfp); + if (rc == APR_SUCCESS) + cfg_trim_line(buf); + return rc; +} + +AP_DECLARE(apr_status_t) ap_varbuf_cfg_getline(struct ap_varbuf *vb, + ap_configfile_t *cfp, + apr_size_t max_len) +{ + apr_status_t rc; + vb->strlen = 0; + *vb->buf = '\0'; + + for (;;) { + apr_size_t new_len; + rc = ap_cfg_getline_core(vb->buf + vb->strlen, vb->avail - vb->strlen, cfp); + if (rc == APR_ENOSPC || rc == APR_SUCCESS) + vb->strlen += strlen(vb->buf + vb->strlen); + if (rc != APR_ENOSPC) + break; + if (vb->avail >= max_len) + return APR_ENOSPC; + new_len = vb->avail * 2; + if (new_len > max_len) + new_len = max_len; + ap_varbuf_grow(vb, new_len); + --cfp->line_number; } + if (vb->strlen > max_len) + return APR_ENOSPC; + if (rc == APR_SUCCESS) + vb->strlen = cfg_trim_line(vb->buf); + return rc; } /* Size an HTTP header field list item, as separated by a comma. @@ -1542,54 +1530,15 @@ static char x2c(const char *what) } /* - * Unescapes a URL. + * Unescapes a URL, leaving reserved characters intact. * 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 + * decoding %00 or a forbidden 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_ex(char *url, int decode_2f) +static int unescape_url(char *url, const char *forbid, const char *reserved) { register int badesc, badpath; char *x, *y; @@ -1614,11 +1563,15 @@ AP_DECLARE(int) ap_unescape_url_keep2f_ex(char *url, int decode_2f) else { char decoded; decoded = x2c(y + 1); - if (decoded == '\0') { + if ((decoded == '\0') + || (forbid && ap_strchr_c(forbid, decoded))) { badpath = 1; + *x = decoded; + y += 2; } - else if (IS_SLASH(decoded) && !decode_2f) { - /* do not decode, just let it go by as-is */ + else if (reserved && ap_strchr_c(reserved, decoded)) { + *x++ = *y++; + *x++ = *y++; *x = *y; } else { @@ -1639,10 +1592,54 @@ AP_DECLARE(int) ap_unescape_url_keep2f_ex(char *url, int decode_2f) return OK; } } +AP_DECLARE(int) ap_unescape_url(char *url) +{ + /* Traditional */ + return unescape_url(url, SLASHES, NULL); +} +AP_DECLARE(int) ap_unescape_url_keep2f(char *url, int decode_slashes) +{ + /* AllowEncodedSlashes (corrected) */ + if (decode_slashes) { + /* no chars reserved */ + return unescape_url(url, NULL, NULL); + } else { + /* reserve (do not decode) encoded slashes */ + return unescape_url(url, NULL, SLASHES); + } +} +#ifdef NEW_APIS +/* IFDEF these out until they've been thought through. + * Just a germ of an API extension for now + */ +AP_DECLARE(int) ap_unescape_url_proxy(char *url) +{ + /* leave RFC1738 reserved characters intact, * so proxied URLs + * don't get mangled. Where does that leave encoded '&' ? + */ + return unescape_url(url, NULL, "/;?"); +} +AP_DECLARE(int) ap_unescape_url_reserved(char *url, const char *reserved) +{ + return unescape_url(url, NULL, reserved); +} +#endif -AP_DECLARE(int) ap_unescape_url_keep2f(char *url) +AP_DECLARE(int) ap_unescape_urlencoded(char *query) { - return ap_unescape_url_keep2f_ex(url, 1); + char *slider; + + /* replace plus with a space */ + if (query) { + for (slider = query; *slider; slider++) { + if (*slider == '+') { + *slider = ' '; + } + } + } + + /* unescape everything else */ + return unescape_url(query, NULL, NULL); } AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, @@ -1656,6 +1653,11 @@ AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, } } +AP_DECLARE(int) ap_unescape_all(char *url) +{ + return unescape_url(url, NULL, NULL); +} + /* 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 @@ -1695,9 +1697,8 @@ static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, * something with a '/' in it (and thus does not prefix "./"). */ -AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) +AP_DECLARE(char *) ap_escape_path_segment_buffer(char *copy, 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; @@ -1715,6 +1716,11 @@ AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) return copy; } +AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) +{ + return ap_escape_path_segment_buffer(apr_palloc(p, 3 * strlen(segment) + 1), segment); +} + 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); @@ -1744,6 +1750,33 @@ AP_DECLARE(char *) ap_os_escape_path(apr_pool_t *p, const char *path, int partia return copy; } +AP_DECLARE(char *) ap_escape_urlencoded_buffer(char *copy, const char *buffer) +{ + const unsigned char *s = (const unsigned char *)buffer; + unsigned char *d = (unsigned char *)copy; + unsigned c; + + while ((c = *s)) { + if (TEST_CHAR(c, T_ESCAPE_URLENCODED)) { + d = c2x(c, '%', d); + } + else if (c == ' ') { + *d++ = '+'; + } + else { + *d++ = c; + } + ++s; + } + *d = '\0'; + return copy; +} + +AP_DECLARE(char *) ap_escape_urlencoded(apr_pool_t *p, const char *buffer) +{ + return ap_escape_urlencoded_buffer(apr_palloc(p, 3 * strlen(buffer) + 1), buffer); +} + /* ap_escape_uri is now a macro for os_escape_path */ AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) @@ -1794,10 +1827,6 @@ AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) x[j] = '\0'; return x; } -AP_DECLARE(char *) ap_escape_html(apr_pool_t *p, const char *s) -{ - return ap_escape_html2(p, s, 0); -} AP_DECLARE(char *) ap_escape_logitem(apr_pool_t *p, const char *str) { char *ret; @@ -2004,6 +2033,14 @@ AP_DECLARE(void) ap_str_tolower(char *str) } } +AP_DECLARE(void) ap_str_toupper(char *str) +{ + while (*str) { + *str = apr_toupper(*str); + ++str; + } +} + /* * We must return a FQDN */ @@ -2018,7 +2055,7 @@ char *ap_get_local_host(apr_pool_t *a) char *hostname; if (apr_gethostname(str, sizeof(str) - 1, a) != APR_SUCCESS) { - ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, + ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, APLOGNO(00556) "%s: apr_gethostname() failed to determine ServerName", ap_server_argv0); } else { @@ -2035,7 +2072,7 @@ char *ap_get_local_host(apr_pool_t *a) server_hostname = apr_pstrdup(a, hostname); } } else { - ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, + ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, APLOGNO(00557) "%s: apr_sockaddr_info_get() failed for %s", ap_server_argv0, str); } @@ -2044,9 +2081,10 @@ char *ap_get_local_host(apr_pool_t *a) if (!server_hostname) server_hostname = apr_pstrdup(a, "127.0.0.1"); - ap_log_perror(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, a, + ap_log_perror(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, a, APLOGNO(00558) "%s: Could not reliably determine the server's fully qualified " - "domain name, using %s for ServerName", + "domain name, using %s. Set the 'ServerName' directive globally " + "to suppress this message", ap_server_argv0, server_hostname); return server_hostname; @@ -2215,7 +2253,7 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( break; case 'm': switch (*(++time_str)) { - /* Time is in miliseconds */ + /* Time is in milliseconds */ case 's': *timeout = (apr_interval_time_t) tout * 1000; break; @@ -2233,3 +2271,504 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( return APR_SUCCESS; } +/** + * Determine if a request has a request body or not. + * + * @param r the request_rec of the request + * @return truth value + */ +AP_DECLARE(int) ap_request_has_body(request_rec *r) +{ + apr_off_t cl; + char *estr; + const char *cls; + int has_body; + + has_body = (!r->header_only + && (r->kept_body + || apr_table_get(r->headers_in, "Transfer-Encoding") + || ( (cls = apr_table_get(r->headers_in, "Content-Length")) + && (apr_strtoff(&cl, cls, &estr, 10) == APR_SUCCESS) + && (!*estr) + && (cl > 0) ) + ) + ); + return has_body; +} + +AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_) +{ + void **ptr = (void **)data_; + *ptr = NULL; + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_str2_alnum(const char *src, char *dest) { + + for ( ; *src; src++, dest++) + { + if (!apr_isprint(*src)) + *dest = 'x'; + else if (!apr_isalnum(*src)) + *dest = '_'; + else + *dest = (char)*src; + } + *dest = '\0'; + return APR_SUCCESS; + +} + +AP_DECLARE(apr_status_t) ap_pstr2_alnum(apr_pool_t *p, const char *src, + const char **dest) +{ + char *new = apr_palloc(p, strlen(src)+1); + if (!new) + return APR_ENOMEM; + *dest = new; + return ap_str2_alnum(src, new); +} + +/** + * Read the body and parse any form found, which must be of the + * type application/x-www-form-urlencoded. + * + * Name/value pairs are returned in an array, with the names as + * strings with a maximum length of HUGE_STRING_LEN, and the + * values as bucket brigades. This allows values to be arbitrarily + * large. + * + * All url-encoding is removed from both the names and the values + * on the fly. The names are interpreted as strings, while the + * values are interpreted as blocks of binary data, that may + * contain the 0 character. + * + * In order to ensure that resource limits are not exceeded, a + * maximum size must be provided. If the sum of the lengths of + * the names and the values exceed this size, this function + * will return HTTP_REQUEST_ENTITY_TOO_LARGE. + * + * An optional number of parameters can be provided, if the number + * of parameters provided exceeds this amount, this function will + * return HTTP_REQUEST_ENTITY_TOO_LARGE. If this value is negative, + * no limit is imposed, and the number of parameters is in turn + * constrained by the size parameter above. + * + * This function honours any kept_body configuration, and the + * original raw request body will be saved to the kept_body brigade + * if so configured, just as ap_discard_request_body does. + * + * NOTE: File upload is not yet supported, but can be without change + * to the function call. + */ + +/* form parsing stuff */ +typedef enum { + FORM_NORMAL, + FORM_AMP, + FORM_NAME, + FORM_VALUE, + FORM_PERCENTA, + FORM_PERCENTB, + FORM_ABORT +} ap_form_type_t; + +AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, + apr_array_header_t **ptr, + apr_size_t num, apr_size_t usize) +{ + apr_bucket_brigade *bb = NULL; + int seen_eos = 0; + char buffer[HUGE_STRING_LEN + 1]; + const char *ct; + apr_size_t offset = 0; + apr_ssize_t size; + ap_form_type_t state = FORM_NAME, percent = FORM_NORMAL; + ap_form_pair_t *pair = NULL; + apr_array_header_t *pairs = apr_array_make(r->pool, 4, sizeof(ap_form_pair_t)); + + char hi = 0; + char low = 0; + + *ptr = pairs; + + /* sanity check - we only support forms for now */ + ct = apr_table_get(r->headers_in, "Content-Type"); + if (!ct || strcmp("application/x-www-form-urlencoded", ct)) { + return ap_discard_request_body(r); + } + + if (usize > APR_SIZE_MAX >> 1) + size = APR_SIZE_MAX >> 1; + else + size = usize; + + if (!f) { + f = r->input_filters; + } + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + do { + apr_bucket *bucket = NULL, *last = NULL; + + int rv = ap_get_brigade(f, bb, AP_MODE_READBYTES, + APR_BLOCK_READ, HUGE_STRING_LEN); + if (rv != APR_SUCCESS) { + apr_brigade_destroy(bb); + return (rv == AP_FILTER_ERROR) ? rv : HTTP_BAD_REQUEST; + } + + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + last = bucket, bucket = APR_BUCKET_NEXT(bucket)) { + const char *data; + apr_size_t len, slide; + + if (last) { + apr_bucket_delete(last); + } + if (APR_BUCKET_IS_EOS(bucket)) { + seen_eos = 1; + break; + } + if (bucket->length == 0) { + continue; + } + + rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + apr_brigade_destroy(bb); + return HTTP_BAD_REQUEST; + } + + slide = len; + while (state != FORM_ABORT && slide-- > 0 && size >= 0 && num != 0) { + char c = *data++; + if ('+' == c) { + c = ' '; + } + else if ('&' == c) { + state = FORM_AMP; + } + if ('%' == c) { + percent = FORM_PERCENTA; + continue; + } + if (FORM_PERCENTA == percent) { + if (c >= 'a') { + hi = c - 'a' + 10; + } + else if (c >= 'A') { + hi = c - 'A' + 10; + } + else if (c >= '0') { + hi = c - '0'; + } + hi = hi << 4; + percent = FORM_PERCENTB; + continue; + } + if (FORM_PERCENTB == percent) { + if (c >= 'a') { + low = c - 'a' + 10; + } + else if (c >= 'A') { + low = c - 'A' + 10; + } + else if (c >= '0') { + low = c - '0'; + } + c = low | hi; + percent = FORM_NORMAL; + } + switch (state) { + case FORM_AMP: + if (pair) { + const char *tmp = apr_pmemdup(r->pool, buffer, offset); + apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pair->value, b); + } + state = FORM_NAME; + pair = NULL; + offset = 0; + num--; + break; + case FORM_NAME: + if (offset < HUGE_STRING_LEN) { + if ('=' == c) { + buffer[offset] = 0; + offset = 0; + pair = (ap_form_pair_t *) apr_array_push(pairs); + pair->name = apr_pstrdup(r->pool, buffer); + pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc); + state = FORM_VALUE; + } + else { + buffer[offset++] = c; + size--; + } + } + else { + state = FORM_ABORT; + } + break; + case FORM_VALUE: + if (offset >= HUGE_STRING_LEN) { + const char *tmp = apr_pmemdup(r->pool, buffer, offset); + apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pair->value, b); + offset = 0; + } + buffer[offset++] = c; + size--; + break; + default: + break; + } + } + + } + + apr_brigade_cleanup(bb); + } while (!seen_eos); + + if (FORM_ABORT == state || size < 0 || num == 0) { + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + else if (FORM_VALUE == state && pair && offset > 0) { + const char *tmp = apr_pmemdup(r->pool, buffer, offset); + apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pair->value, b); + } + + return OK; + +} + +#define VARBUF_SMALL_SIZE 2048 +#define VARBUF_MAX_SIZE (APR_SIZE_MAX - 1 - \ + APR_ALIGN_DEFAULT(sizeof(struct ap_varbuf_info))) + +struct ap_varbuf_info { + struct apr_memnode_t *node; + apr_allocator_t *allocator; +}; + +static apr_status_t varbuf_cleanup(void *info_) +{ + struct ap_varbuf_info *info = info_; + info->node->next = NULL; + apr_allocator_free(info->allocator, info->node); + return APR_SUCCESS; +} + +const char nul = '\0'; +static char * const varbuf_empty = (char *)&nul; + +AP_DECLARE(void) ap_varbuf_init(apr_pool_t *p, struct ap_varbuf *vb, + apr_size_t init_size) +{ + vb->buf = varbuf_empty; + vb->avail = 0; + vb->strlen = AP_VARBUF_UNKNOWN; + vb->pool = p; + vb->info = NULL; + + ap_varbuf_grow(vb, init_size); +} + +AP_DECLARE(void) ap_varbuf_grow(struct ap_varbuf *vb, apr_size_t new_len) +{ + apr_memnode_t *new_node = NULL; + apr_allocator_t *allocator; + struct ap_varbuf_info *new_info; + char *new; + + if (new_len <= vb->avail) + return; + + if (new_len < 2 * vb->avail && vb->avail < VARBUF_MAX_SIZE/2) { + /* at least double the size, to avoid repeated reallocations */ + new_len = 2 * vb->avail; + } + else if (new_len > VARBUF_MAX_SIZE) { + apr_abortfunc_t abort_fn = apr_pool_abort_get(vb->pool); + ap_assert(abort_fn != NULL); + abort_fn(APR_ENOMEM); + return; + } + + new_len++; /* add space for trailing \0 */ + if (new_len <= VARBUF_SMALL_SIZE) { + new_len = APR_ALIGN_DEFAULT(new_len); + new = apr_palloc(vb->pool, new_len); + if (vb->avail && vb->strlen != 0) { + AP_DEBUG_ASSERT(vb->buf != NULL); + AP_DEBUG_ASSERT(vb->buf != varbuf_empty); + if (new == vb->buf + vb->avail + 1) { + /* We are lucky: the new memory lies directly after our old + * buffer, we can now use both. + */ + vb->avail += new_len; + return; + } + else { + /* copy up to vb->strlen + 1 bytes */ + memcpy(new, vb->buf, vb->strlen == AP_VARBUF_UNKNOWN ? + vb->avail + 1 : vb->strlen + 1); + } + } + else { + *new = '\0'; + } + vb->avail = new_len - 1; + vb->buf = new; + return; + } + + /* The required block is rather larger. Use allocator directly so that + * the memory can be freed independently from the pool. */ + allocator = apr_pool_allocator_get(vb->pool); + if (new_len <= VARBUF_MAX_SIZE) + new_node = apr_allocator_alloc(allocator, + new_len + APR_ALIGN_DEFAULT(sizeof(*new_info))); + if (!new_node) { + apr_abortfunc_t abort_fn = apr_pool_abort_get(vb->pool); + ap_assert(abort_fn != NULL); + abort_fn(APR_ENOMEM); + return; + } + new_info = (struct ap_varbuf_info *)new_node->first_avail; + new_node->first_avail += APR_ALIGN_DEFAULT(sizeof(*new_info)); + new_info->node = new_node; + new_info->allocator = allocator; + new = new_node->first_avail; + AP_DEBUG_ASSERT(new_node->endp - new_node->first_avail >= new_len); + new_len = new_node->endp - new_node->first_avail; + + if (vb->avail && vb->strlen != 0) + memcpy(new, vb->buf, vb->strlen == AP_VARBUF_UNKNOWN ? + vb->avail + 1 : vb->strlen + 1); + else + *new = '\0'; + if (vb->info) + apr_pool_cleanup_run(vb->pool, vb->info, varbuf_cleanup); + apr_pool_cleanup_register(vb->pool, new_info, varbuf_cleanup, + apr_pool_cleanup_null); + vb->info = new_info; + vb->buf = new; + vb->avail = new_len - 1; +} + +AP_DECLARE(void) ap_varbuf_strmemcat(struct ap_varbuf *vb, const char *str, + int len) +{ + if (len == 0) + return; + if (!vb->avail) { + ap_varbuf_grow(vb, len); + memcpy(vb->buf, str, len); + vb->buf[len] = '\0'; + vb->strlen = len; + return; + } + if (vb->strlen == AP_VARBUF_UNKNOWN) + vb->strlen = strlen(vb->buf); + ap_varbuf_grow(vb, vb->strlen + len); + memcpy(vb->buf + vb->strlen, str, len); + vb->strlen += len; + vb->buf[vb->strlen] = '\0'; +} + +AP_DECLARE(void) ap_varbuf_free(struct ap_varbuf *vb) +{ + if (vb->info) { + apr_pool_cleanup_run(vb->pool, vb->info, varbuf_cleanup); + vb->info = NULL; + } + vb->buf = NULL; +} + +AP_DECLARE(char *) ap_varbuf_pdup(apr_pool_t *p, struct ap_varbuf *buf, + const char *prepend, apr_size_t prepend_len, + const char *append, apr_size_t append_len, + apr_size_t *new_len) +{ + apr_size_t i = 0; + struct iovec vec[3]; + + if (prepend) { + vec[i].iov_base = (void *)prepend; + vec[i].iov_len = prepend_len; + i++; + } + if (buf->avail && buf->strlen) { + vec[i].iov_base = (void *)buf->buf; + vec[i].iov_len = (buf->strlen == AP_VARBUF_UNKNOWN) ? buf->avail + : buf->strlen; + i++; + } + if (append) { + vec[i].iov_base = (void *)append; + vec[i].iov_len = append_len; + i++; + } + if (i) + return apr_pstrcatv(p, vec, i, new_len); + + if (new_len) + *new_len = 0; + return ""; +} + +AP_DECLARE(apr_status_t) ap_varbuf_regsub(struct ap_varbuf *vb, + const char *input, + const char *source, size_t nmatch, + ap_regmatch_t pmatch[], + apr_size_t maxlen) +{ + return regsub_core(NULL, NULL, vb, input, source, nmatch, pmatch, maxlen); +} + +static const char * const oom_message = "[crit] Memory allocation failed, " + "aborting process." APR_EOL_STR; + +AP_DECLARE(void) ap_abort_on_oom() +{ + int written, count = strlen(oom_message); + const char *buf = oom_message; + do { + written = write(STDERR_FILENO, buf, count); + if (written == count) + break; + if (written > 0) { + buf += written; + count -= written; + } + } while (written >= 0 || errno == EINTR); + abort(); +} + +AP_DECLARE(void *) ap_malloc(size_t size) +{ + void *p = malloc(size); + if (p == NULL && size != 0) + ap_abort_on_oom(); + return p; +} + +AP_DECLARE(void *) ap_calloc(size_t nelem, size_t size) +{ + void *p = calloc(nelem, size); + if (p == NULL && nelem != 0 && size != 0) + ap_abort_on_oom(); + return p; +} + +AP_DECLARE(void *) ap_realloc(void *ptr, size_t size) +{ + void *p = realloc(ptr, size); + if (p == NULL && size != 0) + ap_abort_on_oom(); + return p; +} diff --git a/server/util_cfgtree.c b/server/util_cfgtree.c index ac284a7b..a7142e3d 100644 --- a/server/util_cfgtree.c +++ b/server/util_cfgtree.c @@ -14,7 +14,6 @@ * limitations under the License. */ -#define CORE_PRIVATE #include "util_cfgtree.h" #include <stdlib.h> diff --git a/server/util_cookies.c b/server/util_cookies.c new file mode 100644 index 00000000..dc8c457b --- /dev/null +++ b/server/util_cookies.c @@ -0,0 +1,290 @@ +/* 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_cookies.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" + +#define LOG_PREFIX "ap_cookie: " + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/** + * Write an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val, + const char *attrs, long maxage, ...) +{ + + const char *buffer; + const char *rfc2109; + apr_table_t *t; + va_list vp; + + /* handle expiry */ + buffer = ""; + if (maxage) { + buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); + } + + /* create RFC2109 compliant cookie */ + rfc2109 = apr_pstrcat(r->pool, name, "=", val, ";", buffer, + attrs && *attrs ? attrs : DEFAULT_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00007) LOG_PREFIX + "user '%s' set cookie: '%s'", r->user, rfc2109); + + /* write the cookie to the header table(s) provided */ + va_start(vp, maxage); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE, rfc2109); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/** + * Write an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs2 The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val, + const char *attrs2, long maxage, ...) +{ + + const char *buffer; + const char *rfc2965; + apr_table_t *t; + va_list vp; + + /* handle expiry */ + buffer = ""; + if (maxage) { + buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); + } + + /* create RFC2965 compliant cookie */ + rfc2965 = apr_pstrcat(r->pool, name2, "=", val, ";", buffer, + attrs2 && *attrs2 ? attrs2 : DEFAULT_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00008) LOG_PREFIX + "user '%s' set cookie2: '%s'", r->user, rfc2965); + + /* write the cookie to the header table(s) provided */ + va_start(vp, maxage); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE2, rfc2965); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/** + * Remove an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name, const char *attrs, ...) +{ + apr_table_t *t; + va_list vp; + + /* create RFC2109 compliant cookie */ + const char *rfc2109 = apr_pstrcat(r->pool, name, "=;Max-Age=0;", + attrs ? attrs : CLEAR_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00009) LOG_PREFIX + "user '%s' removed cookie: '%s'", r->user, rfc2109); + + /* write the cookie to the header table(s) provided */ + va_start(vp, attrs); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE, rfc2109); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/** + * Remove an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2, const char *attrs2, ...) +{ + apr_table_t *t; + va_list vp; + + /* create RFC2965 compliant cookie */ + const char *rfc2965 = apr_pstrcat(r->pool, name2, "=;Max-Age=0;", + attrs2 ? attrs2 : CLEAR_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00010) LOG_PREFIX + "user '%s' removed cookie2: '%s'", r->user, rfc2965); + + /* write the cookie to the header table(s) provided */ + va_start(vp, attrs2); + while ((t = va_arg(vp, apr_table_t *))) { + apr_table_addn(t, SET_COOKIE2, rfc2965); + } + va_end(vp); + + return APR_SUCCESS; + +} + +/* Iterate through the cookies, isolate our cookie and then remove it. + * + * If our cookie appears two or more times, but with different values, + * remove it twice and set the duplicated flag to true. Remove any + * $path or other attributes following our cookie if present. If we end + * up with an empty cookie, remove the whole header. + */ +static int extract_cookie_line(ap_cookie_do * v, const char *key, const char *val) +{ + char *last1, *last2; + char *cookie = apr_pstrdup(v->r->pool, val); + const char *name = apr_pstrcat(v->r->pool, v->name ? v->name : "", "=", NULL); + size_t len = strlen(name); + const char *new_cookie = ""; + const char *comma = ","; + char *next1; + const char *semi = ";"; + char *next2; + const char *sep = ""; + int cookies = 0; + + /* find the cookie called name */ + int eat = 0; + next1 = apr_strtok(cookie, comma, &last1); + while (next1) { + next2 = apr_strtok(next1, semi, &last2); + while (next2) { + char *trim = next2; + while (apr_isspace(*trim)) { + trim++; + } + if (!strncmp(trim, name, len)) { + if (v->encoded) { + if (strcmp(v->encoded, trim + len)) { + v->duplicated = 1; + } + } + v->encoded = apr_pstrdup(v->r->pool, trim + len); + eat = 1; + } + else { + if (*trim != '$') { + cookies++; + eat = 0; + } + if (!eat) { + new_cookie = apr_pstrcat(v->r->pool, new_cookie, sep, next2, NULL); + } + } + next2 = apr_strtok(NULL, semi, &last2); + sep = semi; + } + + next1 = apr_strtok(NULL, comma, &last1); + sep = comma; + } + + /* any cookies left over? */ + if (cookies) { + apr_table_addn(v->new_cookies, key, new_cookie); + } + + return 1; +} + +/** + * Read a cookie called name, placing its value in val. + * + * Both the Cookie and Cookie2 headers are scanned for the cookie. + * + * If the cookie is duplicated, this function returns APR_EGENERAL. If found, + * and if remove is non zero, the cookie will be removed from the headers, and + * thus kept private from the backend. + */ +AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val, + int remove) +{ + + ap_cookie_do v; + v.r = r; + v.encoded = NULL; + v.new_cookies = apr_table_make(r->pool, 10); + v.duplicated = 0; + v.name = name; + + apr_table_do((int (*) (void *, const char *, const char *)) + extract_cookie_line, (void *) &v, r->headers_in, + "Cookie", "Cookie2", NULL); + if (v.duplicated) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00011) LOG_PREFIX + "client submitted cookie '%s' more than once: %s", v.name, r->uri); + return APR_EGENERAL; + } + + /* remove our cookie(s), and replace them */ + if (remove) { + apr_table_unset(r->headers_in, "Cookie"); + apr_table_unset(r->headers_in, "Cookie2"); + r->headers_in = apr_table_overlay(r->pool, r->headers_in, v.new_cookies); + } + + *val = v.encoded; + + return APR_SUCCESS; + +} + +/** + * Sanity check a given string that it exists, is not empty, + * and does not contain the special characters '=', ';' and '&'. + * + * It is used to sanity check the cookie names. + */ +AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string) +{ + if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&') || + ap_strchr_c(string, ';')) { + return APR_EGENERAL; + } + return APR_SUCCESS; +} diff --git a/server/util_debug.c b/server/util_debug.c index 0fefc5c8..41250da6 100644 --- a/server/util_debug.c +++ b/server/util_debug.c @@ -19,6 +19,7 @@ #include "httpd.h" #include "http_config.h" +#include "http_core.h" /* Possibly get rid of the macros we defined in httpd.h */ #if defined(strchr) @@ -106,6 +107,91 @@ AP_DECLARE(void *) ap_get_module_config(const ap_conf_vector_t *cv, return ((void **)cv)[m->module_index]; } +#if defined(ap_get_core_module_config) +#undef ap_get_core_module_config +AP_DECLARE(void *) ap_get_core_module_config(const ap_conf_vector_t *cv); +#endif + +AP_DECLARE(void *) ap_get_core_module_config(const ap_conf_vector_t *cv) +{ + return ((void **)cv)[AP_CORE_MODULE_INDEX]; +} + + +#if defined(ap_get_server_module_loglevel) +#undef ap_get_server_module_loglevel +AP_DECLARE(int) ap_get_server_module_loglevel(const server_rec *s, int module_index); +#endif + +AP_DECLARE(int) ap_get_server_module_loglevel(const server_rec *s, int module_index) +{ + if (module_index < 0 || s->log.module_levels == NULL || + s->log.module_levels[module_index] < 0) + { + return s->log.level; + } + + return s->log.module_levels[module_index]; +} + +#if defined(ap_get_conn_module_loglevel) +#undef ap_get_conn_module_loglevel +AP_DECLARE(int) ap_get_conn_module_loglevel(const conn_rec *c, int module_index); +#endif + +AP_DECLARE(int) ap_get_conn_module_loglevel(const conn_rec *c, int module_index) +{ + const struct ap_logconf *l = (c)->log ? (c)->log : &(c)->base_server->log; + if (module_index < 0 || l->module_levels == NULL || + l->module_levels[module_index] < 0) + { + return l->level; + } + + return l->module_levels[module_index]; +} + +#if defined(ap_get_conn_server_module_loglevel) +#undef ap_get_conn_server_module_loglevel +AP_DECLARE(int) ap_get_conn_server_module_loglevel(const conn_rec *c, + const server_rec *s, + int module_index); +#endif + +AP_DECLARE(int) ap_get_conn_server_module_loglevel(const conn_rec *c, + const server_rec *s, + int module_index) +{ + const struct ap_logconf *l = (c->log && c->log != &c->base_server->log) ? + c->log : &s->log; + if (module_index < 0 || l->module_levels == NULL || + l->module_levels[module_index] < 0) + { + return l->level; + } + + return l->module_levels[module_index]; +} + +#if defined(ap_get_request_module_loglevel) +#undef ap_get_request_module_loglevel +AP_DECLARE(int) ap_get_request_module_loglevel(const request_rec *c, int module_index); +#endif + +AP_DECLARE(int) ap_get_request_module_loglevel(const request_rec *r, int module_index) +{ + const struct ap_logconf *l = r->log ? r->log : + r->connection->log ? r->connection->log : + &r->server->log; + if (module_index < 0 || l->module_levels == NULL || + l->module_levels[module_index] < 0) + { + return l->level; + } + + return l->module_levels[module_index]; +} + /** * Generic accessors for other modules to set at their own module-specific * data @@ -113,7 +199,7 @@ AP_DECLARE(void *) ap_get_module_config(const ap_conf_vector_t *cv, * 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) + * @fn 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 @@ -126,3 +212,14 @@ AP_DECLARE(void) ap_set_module_config(ap_conf_vector_t *cv, const module *m, { ((void **)cv)[m->module_index] = val; } + + +#if defined(ap_set_core_module_config) +#undef ap_set_core_module_config +AP_DECLARE(void) ap_set_core_module_config(ap_conf_vector_t *cv, void *val); +#endif + +AP_DECLARE(void) ap_set_core_module_config(ap_conf_vector_t *cv, void *val) +{ + ((void **)cv)[AP_CORE_MODULE_INDEX] = val; +} diff --git a/server/util_ebcdic.c b/server/util_ebcdic.c index 78cf2739..1a8c0ba3 100644 --- a/server/util_ebcdic.c +++ b/server/util_ebcdic.c @@ -24,42 +24,45 @@ #include "http_core.h" #include "util_ebcdic.h" +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + 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, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00040) "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, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00041) "apr_xlate_open() failed"); return rv; } rv = apr_MD5InitEBCDIC(ap_hdrs_to_ascii); if (rv) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00042) "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, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00043) "apr_base64init_ebcdic() failed"); return rv; } rv = apr_SHA1InitEBCDIC(ap_hdrs_to_ascii); if (rv) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00044) "apr_SHA1InitEBCDIC() failed"); return rv; } diff --git a/server/util_expr_eval.c b/server/util_expr_eval.c new file mode 100644 index 00000000..6c543f54 --- /dev/null +++ b/server/util_expr_eval.c @@ -0,0 +1,1723 @@ +/* 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. + */ + +/* _ _ + * ap_expr_eval.c, based on ssl_expr_eval.c from mod_ssl + */ + +#include "httpd.h" +#include "http_log.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "ap_provider.h" +#include "util_expr_private.h" + +#include "apr_lib.h" +#include "apr_fnmatch.h" + +#include <limits.h> /* for INT_MAX */ + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +APR_HOOK_STRUCT( + APR_HOOK_LINK(expr_lookup) +) + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, expr_lookup, (ap_expr_lookup_parms *parms), + (parms), DECLINED) + +#define LOG_MARK(info) __FILE__, __LINE__, (info)->module_index + +static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *info, + const ap_expr_t *args); +static const char *ap_expr_eval_re_backref(ap_expr_eval_ctx_t *ctx, + unsigned int n); +static const char *ap_expr_eval_var(ap_expr_eval_ctx_t *ctx, + ap_expr_var_func_t *func, + const void *data); + +/* define AP_EXPR_DEBUG to log the parse tree when parsing an expression */ +#ifdef AP_EXPR_DEBUG +static void expr_dump_tree(const ap_expr_t *e, const server_rec *s, + int loglevel, int indent); +#endif + +/* + * To reduce counting overhead, we only count calls to + * ap_expr_eval_word() and ap_expr_eval(). The max number of + * stack frames is larger by some factor. + */ +#define AP_EXPR_MAX_RECURSION 20 +static int inc_rec(ap_expr_eval_ctx_t *ctx) +{ + if (ctx->reclvl < AP_EXPR_MAX_RECURSION) { + ctx->reclvl++; + return 0; + } + *ctx->err = "Recursion limit reached"; + /* short circuit further evaluation */ + ctx->reclvl = INT_MAX; + return 1; +} + +static const char *ap_expr_eval_word(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *node) +{ + const char *result = ""; + if (inc_rec(ctx)) + return result; + switch (node->node_op) { + case op_Digit: + case op_String: + result = node->node_arg1; + break; + case op_Var: + result = ap_expr_eval_var(ctx, (ap_expr_var_func_t *)node->node_arg1, + node->node_arg2); + break; + case op_Concat: + if (((ap_expr_t *)node->node_arg2)->node_op != op_Concat) { + const char *s1 = ap_expr_eval_word(ctx, node->node_arg1); + const char *s2 = ap_expr_eval_word(ctx, node->node_arg2); + if (!*s1) + result = s2; + else if (!*s2) + result = s1; + else + result = apr_pstrcat(ctx->p, s1, s2, NULL); + } + else { + const ap_expr_t *nodep = node; + int i = 1; + struct iovec *vec; + do { + nodep = nodep->node_arg2; + i++; + } while (nodep->node_op == op_Concat); + vec = apr_palloc(ctx->p, i * sizeof(struct iovec)); + nodep = node; + i = 0; + do { + vec[i].iov_base = (void *)ap_expr_eval_word(ctx, + nodep->node_arg1); + vec[i].iov_len = strlen(vec[i].iov_base); + i++; + nodep = nodep->node_arg2; + } while (nodep->node_op == op_Concat); + vec[i].iov_base = (void *)ap_expr_eval_word(ctx, nodep); + vec[i].iov_len = strlen(vec[i].iov_base); + i++; + result = apr_pstrcatv(ctx->p, vec, i, NULL); + } + break; + case op_StringFuncCall: { + const ap_expr_t *info = node->node_arg1; + const ap_expr_t *args = node->node_arg2; + result = ap_expr_eval_string_func(ctx, info, args); + break; + } + case op_RegexBackref: { + const unsigned int *np = node->node_arg1; + result = ap_expr_eval_re_backref(ctx, *np); + break; + } + default: + *ctx->err = "Internal evaluation error: Unknown word expression node"; + break; + } + if (!result) + result = ""; + ctx->reclvl--; + return result; +} + +static const char *ap_expr_eval_var(ap_expr_eval_ctx_t *ctx, + ap_expr_var_func_t *func, + const void *data) +{ + AP_DEBUG_ASSERT(func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*func)(ctx, data); +} + +static const char *ap_expr_eval_re_backref(ap_expr_eval_ctx_t *ctx, unsigned int n) +{ + int len; + + if (!ctx->re_pmatch || !ctx->re_source || *ctx->re_source == '\0' || + ctx->re_nmatch < n + 1) + return ""; + + len = ctx->re_pmatch[n].rm_eo - ctx->re_pmatch[n].rm_so; + if (len == 0) + return ""; + + return apr_pstrndup(ctx->p, *ctx->re_source + ctx->re_pmatch[n].rm_so, len); +} + +static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *info, + const ap_expr_t *arg) +{ + ap_expr_string_func_t *func = (ap_expr_string_func_t *)info->node_arg1; + const void *data = info->node_arg2; + + AP_DEBUG_ASSERT(info->node_op == op_StringFuncInfo); + AP_DEBUG_ASSERT(func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*func)(ctx, data, ap_expr_eval_word(ctx, arg)); +} + +static int intstrcmp(const char *s1, const char *s2) +{ + apr_int64_t i1 = apr_atoi64(s1); + apr_int64_t i2 = apr_atoi64(s2); + + if (i1 < i2) + return -1; + else if (i1 == i2) + return 0; + else + return 1; +} + +static int ap_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) +{ + const ap_expr_t *e1 = node->node_arg1; + const ap_expr_t *e2 = node->node_arg2; + switch (node->node_op) { + case op_EQ: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0); + case op_NE: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0); + case op_LT: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0); + case op_LE: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0); + case op_GT: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0); + case op_GE: + return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0); + case op_STR_EQ: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0); + case op_STR_NE: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0); + case op_STR_LT: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0); + case op_STR_LE: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0); + case op_STR_GT: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0); + case op_STR_GE: + return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0); + case op_IN: { + const char *needle = ap_expr_eval_word(ctx, e1); + if (e2->node_op == op_ListElement) { + do { + const ap_expr_t *val = e2->node_arg1; + AP_DEBUG_ASSERT(e2->node_op == op_ListElement); + if (strcmp(needle, ap_expr_eval_word(ctx, val)) == 0) { + return 1; + break; + } + e2 = e2->node_arg2; + } while (e2 != NULL); + } + else if (e2->node_op == op_ListFuncCall) { + const ap_expr_t *info = e2->node_arg1; + const ap_expr_t *arg = e2->node_arg2; + ap_expr_list_func_t *func = (ap_expr_list_func_t *)info->node_arg1; + apr_array_header_t *haystack; + int i = 0; + AP_DEBUG_ASSERT(func != NULL); + AP_DEBUG_ASSERT(info->node_op == op_ListFuncInfo); + haystack = (*func)(ctx, info->node_arg2, ap_expr_eval_word(ctx, arg)); + if (haystack == NULL) + return 0; + for (; i < haystack->nelts; i++) { + if (strcmp(needle, APR_ARRAY_IDX(haystack,i,char *)) == 0) + return 1; + } + } + return 0; + } + case op_REG: + case op_NRE: { + const char *word = ap_expr_eval_word(ctx, e1); + const ap_regex_t *regex = e2->node_arg1; + int result; + + /* + * $0 ... $9 may contain stuff the user wants to keep. Therefore + * we only set them if there are capturing parens in the regex. + */ + if (regex->re_nsub > 0) { + result = (0 == ap_regexec(regex, word, ctx->re_nmatch, + ctx->re_pmatch, 0)); + *ctx->re_source = result ? word : NULL; + } + else { + result = (0 == ap_regexec(regex, word, 0, NULL, 0)); + } + + if (node->node_op == op_REG) + return result; + else + return !result; + } + default: + *ctx->err = "Internal evaluation error: Unknown comp expression node"; + return -1; + } +} + +/* combined string/int comparison for compatibility with ssl_expr */ +static int strcmplex(const char *str1, const char *str2) +{ + int i, n1, n2; + + if (str1 == NULL) + return -1; + if (str2 == NULL) + return +1; + n1 = strlen(str1); + n2 = strlen(str2); + if (n1 > n2) + return 1; + if (n1 < n2) + return -1; + for (i = 0; i < n1; i++) { + if (str1[i] > str2[i]) + return 1; + if (str1[i] < str2[i]) + return -1; + } + return 0; +} + +static int ssl_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) +{ + const ap_expr_t *e1 = node->node_arg1; + const ap_expr_t *e2 = node->node_arg2; + switch (node->node_op) { + case op_EQ: + case op_STR_EQ: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0); + case op_NE: + case op_STR_NE: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0); + case op_LT: + case op_STR_LT: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0); + case op_LE: + case op_STR_LE: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0); + case op_GT: + case op_STR_GT: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0); + case op_GE: + case op_STR_GE: + return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0); + default: + return ap_expr_eval_comp(ctx, node); + } +} + +AP_DECLARE_NONSTD(int) ap_expr_lookup_default(ap_expr_lookup_parms *parms) +{ + return ap_run_expr_lookup(parms); +} + +AP_DECLARE(const char *) ap_expr_parse(apr_pool_t *pool, apr_pool_t *ptemp, + ap_expr_info_t *info, const char *expr, + ap_expr_lookup_fn_t *lookup_fn) +{ + ap_expr_parse_ctx_t ctx; + int rc; + + ctx.pool = pool; + ctx.ptemp = ptemp; + ctx.inputbuf = expr; + ctx.inputlen = strlen(expr); + ctx.inputptr = ctx.inputbuf; + ctx.expr = NULL; + ctx.error = NULL; /* generic bison error message (XXX: usually not very useful, should be axed) */ + ctx.error2 = NULL; /* additional error message */ + ctx.flags = info->flags; + ctx.scan_del = '\0'; + ctx.scan_buf[0] = '\0'; + ctx.scan_ptr = ctx.scan_buf; + ctx.lookup_fn = lookup_fn ? lookup_fn : ap_expr_lookup_default; + ctx.at_start = 1; + + ap_expr_yylex_init(&ctx.scanner); + ap_expr_yyset_extra(&ctx, ctx.scanner); + rc = ap_expr_yyparse(&ctx); + ap_expr_yylex_destroy(ctx.scanner); + if (ctx.error) { + if (ctx.error2) + return apr_psprintf(pool, "%s: %s", ctx.error, ctx.error2); + else + return ctx.error; + } + else if (ctx.error2) { + return ctx.error2; + } + + if (rc) /* XXX can this happen? */ + return "syntax error"; + +#ifdef AP_EXPR_DEBUG + if (ctx.expr) + expr_dump_tree(ctx.expr, NULL, APLOG_NOTICE, 2); +#endif + + info->root_node = ctx.expr; + + return NULL; +} + +AP_DECLARE(ap_expr_info_t*) ap_expr_parse_cmd_mi(const cmd_parms *cmd, + const char *expr, + unsigned int flags, + const char **err, + ap_expr_lookup_fn_t *lookup_fn, + int module_index) +{ + ap_expr_info_t *info = apr_pcalloc(cmd->pool, sizeof(ap_expr_info_t)); + info->filename = cmd->directive->filename; + info->line_number = cmd->directive->line_num; + info->flags = flags; + info->module_index = module_index; + *err = ap_expr_parse(cmd->pool, cmd->temp_pool, info, expr, lookup_fn); + + if (*err) + return NULL; + + return info; +} + +ap_expr_t *ap_expr_make(ap_expr_node_op_e op, const void *a1, const void *a2, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *node = apr_palloc(ctx->pool, sizeof(ap_expr_t)); + node->node_op = op; + node->node_arg1 = a1; + node->node_arg2 = a2; + return node; +} + +static ap_expr_t *ap_expr_info_make(int type, const char *name, + ap_expr_parse_ctx_t *ctx, + const ap_expr_t *arg) +{ + ap_expr_t *info = apr_palloc(ctx->pool, sizeof(ap_expr_t)); + ap_expr_lookup_parms parms; + parms.type = type; + parms.flags = ctx->flags; + parms.pool = ctx->pool; + parms.ptemp = ctx->ptemp; + parms.name = name; + parms.func = &info->node_arg1; + parms.data = &info->node_arg2; + parms.err = &ctx->error2; + parms.arg = (arg && arg->node_op == op_String) ? arg->node_arg1 : NULL; + if (ctx->lookup_fn(&parms) != OK) + return NULL; + return info; +} + +ap_expr_t *ap_expr_str_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_STRING, name, ctx, arg); + if (!info) + return NULL; + + info->node_op = op_StringFuncInfo; + return ap_expr_make(op_StringFuncCall, info, arg, ctx); +} + +ap_expr_t *ap_expr_list_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_LIST, name, ctx, arg); + if (!info) + return NULL; + + info->node_op = op_ListFuncInfo; + return ap_expr_make(op_ListFuncCall, info, arg, ctx); +} + +ap_expr_t *ap_expr_unary_op_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_OP_UNARY, name, ctx, arg); + if (!info) + return NULL; + + info->node_op = op_UnaryOpInfo; + return ap_expr_make(op_UnaryOpCall, info, arg, ctx); +} + +ap_expr_t *ap_expr_binary_op_make(const char *name, const ap_expr_t *arg1, + const ap_expr_t *arg2, ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *args; + ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_OP_BINARY, name, ctx, + arg2); + if (!info) + return NULL; + + info->node_op = op_BinaryOpInfo; + args = ap_expr_make(op_BinaryOpArgs, arg1, arg2, ctx); + return ap_expr_make(op_BinaryOpCall, info, args, ctx); +} + + +ap_expr_t *ap_expr_var_make(const char *name, ap_expr_parse_ctx_t *ctx) +{ + ap_expr_t *node = ap_expr_info_make(AP_EXPR_FUNC_VAR, name, ctx, NULL); + if (!node) + return NULL; + + node->node_op = op_Var; + return node; +} + +#ifdef AP_EXPR_DEBUG + +#define MARK APLOG_MARK,loglevel,0,s +#define DUMP_E_E(op, e1, e2) \ + do { ap_log_error(MARK,"%*s%s: %pp %pp", indent, " ", op, e1, e2); \ + if (e1) expr_dump_tree(e1, s, loglevel, indent + 2); \ + if (e2) expr_dump_tree(e2, s, loglevel, indent + 2); \ + } while (0) +#define DUMP_S_E(op, s1, e1) \ + do { ap_log_error(MARK,"%*s%s: '%s' %pp", indent, " ", op, (char *)s1, e1); \ + if (e1) expr_dump_tree(e1, s, loglevel, indent + 2); \ + } while (0) +#define DUMP_S_P(op, s1, p1) \ + ap_log_error(MARK,"%*s%s: '%s' %pp", indent, " ", op, (char *)s1, p1); +#define DUMP_P_P(op, p1, p2) \ + ap_log_error(MARK,"%*s%s: %pp %pp", indent, " ", op, p1, p2); +#define DUMP_S_S(op, s1, s2) \ + ap_log_error(MARK,"%*s%s: '%s' '%s'", indent, " ", op, (char *)s1, (char *)s2) +#define DUMP_P(op, p1) \ + ap_log_error(MARK,"%*s%s: %pp", indent, " ", op, p1); +#define DUMP_IP(op, p1) \ + ap_log_error(MARK,"%*s%s: %d", indent, " ", op, *(int *)p1); +#define DUMP_S(op, s1) \ + ap_log_error(MARK,"%*s%s: '%s'", indent, " ", op, (char *)s1) + +#define CASE_OP(op) case op: name = #op ; break; + +static void expr_dump_tree(const ap_expr_t *e, const server_rec *s, + int loglevel, int indent) +{ + switch (e->node_op) { + /* no arg */ + case op_NOP: + case op_True: + case op_False: + { + char *name; + switch (e->node_op) { + CASE_OP(op_NOP); + CASE_OP(op_True); + CASE_OP(op_False); + default: + ap_assert(0); + } + ap_log_error(MARK, "%*s%s", indent, " ", name); + } + break; + + /* arg1: string, arg2: expr */ + case op_UnaryOpCall: + case op_BinaryOpCall: + case op_BinaryOpArgs: + { + char *name; + switch (e->node_op) { + CASE_OP(op_BinaryOpCall); + CASE_OP(op_UnaryOpCall); + CASE_OP(op_BinaryOpArgs); + default: + ap_assert(0); + } + DUMP_S_E(name, e->node_arg1, e->node_arg2); + } + break; + + /* arg1: expr, arg2: expr */ + case op_Comp: + case op_Not: + case op_Or: + case op_And: + case op_EQ: + case op_NE: + case op_LT: + case op_LE: + case op_GT: + case op_GE: + case op_STR_EQ: + case op_STR_NE: + case op_STR_LT: + case op_STR_LE: + case op_STR_GT: + case op_STR_GE: + case op_IN: + case op_REG: + case op_NRE: + case op_Concat: + case op_StringFuncCall: + case op_ListFuncCall: + case op_ListElement: + { + char *name; + switch (e->node_op) { + CASE_OP(op_Comp); + CASE_OP(op_Not); + CASE_OP(op_Or); + CASE_OP(op_And); + CASE_OP(op_EQ); + CASE_OP(op_NE); + CASE_OP(op_LT); + CASE_OP(op_LE); + CASE_OP(op_GT); + CASE_OP(op_GE); + CASE_OP(op_STR_EQ); + CASE_OP(op_STR_NE); + CASE_OP(op_STR_LT); + CASE_OP(op_STR_LE); + CASE_OP(op_STR_GT); + CASE_OP(op_STR_GE); + CASE_OP(op_IN); + CASE_OP(op_REG); + CASE_OP(op_NRE); + CASE_OP(op_Concat); + CASE_OP(op_StringFuncCall); + CASE_OP(op_ListFuncCall); + CASE_OP(op_ListElement); + default: + ap_assert(0); + } + DUMP_E_E(name, e->node_arg1, e->node_arg2); + } + break; + /* arg1: string */ + case op_Digit: + case op_String: + { + char *name; + switch (e->node_op) { + CASE_OP(op_Digit); + CASE_OP(op_String); + default: + ap_assert(0); + } + DUMP_S(name, e->node_arg1); + } + break; + /* arg1: pointer, arg2: pointer */ + case op_Var: + case op_StringFuncInfo: + case op_UnaryOpInfo: + case op_BinaryOpInfo: + case op_ListFuncInfo: + { + char *name; + switch (e->node_op) { + CASE_OP(op_Var); + CASE_OP(op_StringFuncInfo); + CASE_OP(op_UnaryOpInfo); + CASE_OP(op_BinaryOpInfo); + CASE_OP(op_ListFuncInfo); + default: + ap_assert(0); + } + DUMP_P_P(name, e->node_arg1, e->node_arg2); + } + break; + /* arg1: pointer */ + case op_Regex: + DUMP_P("op_Regex", e->node_arg1); + break; + /* arg1: pointer to int */ + case op_RegexBackref: + DUMP_IP("op_RegexBackref", e->node_arg1); + break; + default: + ap_log_error(MARK, "%*sERROR: INVALID OP %d", indent, " ", e->node_op); + break; + } +} +#endif /* AP_EXPR_DEBUG */ + +static int ap_expr_eval_unary_op(ap_expr_eval_ctx_t *ctx, const ap_expr_t *info, + const ap_expr_t *arg) +{ + ap_expr_op_unary_t *op_func = (ap_expr_op_unary_t *)info->node_arg1; + const void *data = info->node_arg2; + + AP_DEBUG_ASSERT(info->node_op == op_UnaryOpInfo); + AP_DEBUG_ASSERT(op_func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*op_func)(ctx, data, ap_expr_eval_word(ctx, arg)); +} + +static int ap_expr_eval_binary_op(ap_expr_eval_ctx_t *ctx, + const ap_expr_t *info, + const ap_expr_t *args) +{ + ap_expr_op_binary_t *op_func = (ap_expr_op_binary_t *)info->node_arg1; + const void *data = info->node_arg2; + const ap_expr_t *a1 = args->node_arg1; + const ap_expr_t *a2 = args->node_arg2; + + AP_DEBUG_ASSERT(info->node_op == op_BinaryOpInfo); + AP_DEBUG_ASSERT(args->node_op == op_BinaryOpArgs); + AP_DEBUG_ASSERT(op_func != NULL); + AP_DEBUG_ASSERT(data != NULL); + return (*op_func)(ctx, data, ap_expr_eval_word(ctx, a1), + ap_expr_eval_word(ctx, a2)); +} + + +static int ap_expr_eval(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) +{ + const ap_expr_t *e1 = node->node_arg1; + const ap_expr_t *e2 = node->node_arg2; + int result = FALSE; + if (inc_rec(ctx)) + return result; + while (1) { + switch (node->node_op) { + case op_True: + result ^= TRUE; + goto out; + case op_False: + result ^= FALSE; + goto out; + case op_Not: + result = !result; + node = e1; + break; + case op_Or: + do { + if (e1->node_op == op_Not) { + if (!ap_expr_eval(ctx, e1->node_arg1)) { + result ^= TRUE; + goto out; + } + } + else { + if (ap_expr_eval(ctx, e1)) { + result ^= TRUE; + goto out; + } + } + node = node->node_arg2; + e1 = node->node_arg1; + } while (node->node_op == op_Or); + break; + case op_And: + do { + if (e1->node_op == op_Not) { + if (ap_expr_eval(ctx, e1->node_arg1)) { + result ^= FALSE; + goto out; + } + } + else { + if (!ap_expr_eval(ctx, e1)) { + result ^= FALSE; + goto out; + } + } + node = node->node_arg2; + e1 = node->node_arg1; + } while (node->node_op == op_And); + break; + case op_UnaryOpCall: + result ^= ap_expr_eval_unary_op(ctx, e1, e2); + goto out; + case op_BinaryOpCall: + result ^= ap_expr_eval_binary_op(ctx, e1, e2); + goto out; + case op_Comp: + if (ctx->info->flags & AP_EXPR_FLAG_SSL_EXPR_COMPAT) + result ^= ssl_expr_eval_comp(ctx, e1); + else + result ^= ap_expr_eval_comp(ctx, e1); + goto out; + default: + *ctx->err = "Internal evaluation error: Unknown expression node"; + goto out; + } + e1 = node->node_arg1; + e2 = node->node_arg2; + } +out: + ctx->reclvl--; + return result; +} + +AP_DECLARE(int) ap_expr_exec(request_rec *r, const ap_expr_info_t *info, + const char **err) +{ + return ap_expr_exec_re(r, info, 0, NULL, NULL, err); +} + +AP_DECLARE(int) ap_expr_exec_ctx(ap_expr_eval_ctx_t *ctx) +{ + int rc; + + AP_DEBUG_ASSERT(ctx->p != NULL); + /* XXX: allow r, c == NULL */ + AP_DEBUG_ASSERT(ctx->r != NULL); + AP_DEBUG_ASSERT(ctx->c != NULL); + AP_DEBUG_ASSERT(ctx->s != NULL); + AP_DEBUG_ASSERT(ctx->err != NULL); + AP_DEBUG_ASSERT(ctx->info != NULL); + if (ctx->re_pmatch) { + AP_DEBUG_ASSERT(ctx->re_source != NULL); + AP_DEBUG_ASSERT(ctx->re_nmatch > 0); + } + ctx->reclvl = 0; + + *ctx->err = NULL; + if (ctx->info->flags & AP_EXPR_FLAG_STRING_RESULT) { + *ctx->result_string = ap_expr_eval_word(ctx, ctx->info->root_node); + if (*ctx->err != NULL) { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r, + "Evaluation of expression from %s:%d failed: %s", + ctx->info->filename, ctx->info->line_number, *ctx->err); + return -1; + } else { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r, + "Evaluation of string expression from %s:%d gave: %s", + ctx->info->filename, ctx->info->line_number, + *ctx->result_string); + return 1; + } + } + else { + rc = ap_expr_eval(ctx, ctx->info->root_node); + if (*ctx->err != NULL) { + ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r, + "Evaluation of expression from %s:%d failed: %s", + ctx->info->filename, ctx->info->line_number, *ctx->err); + return -1; + } else { + rc = rc ? 1 : 0; + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r, + "Evaluation of expression from %s:%d gave: %d", + ctx->info->filename, ctx->info->line_number, rc); + + if (ctx->vary_this && *ctx->vary_this) + apr_table_merge(ctx->r->headers_out, "Vary", *ctx->vary_this); + + return rc; + } + } +} + +AP_DECLARE(int) ap_expr_exec_re(request_rec *r, const ap_expr_info_t *info, + apr_size_t nmatch, ap_regmatch_t *pmatch, + const char **source, const char **err) +{ + ap_expr_eval_ctx_t ctx; + int dont_vary = (info->flags & AP_EXPR_FLAG_DONT_VARY); + const char *tmp_source = NULL, *vary_this = NULL; + ap_regmatch_t tmp_pmatch[AP_MAX_REG_MATCH]; + + AP_DEBUG_ASSERT((info->flags & AP_EXPR_FLAG_STRING_RESULT) == 0); + + ctx.r = r; + ctx.c = r->connection; + ctx.s = r->server; + ctx.p = r->pool; + ctx.err = err; + ctx.info = info; + ctx.re_nmatch = nmatch; + ctx.re_pmatch = pmatch; + ctx.re_source = source; + ctx.vary_this = dont_vary ? NULL : &vary_this; + ctx.data = NULL; + + if (!pmatch) { + ctx.re_nmatch = AP_MAX_REG_MATCH; + ctx.re_pmatch = tmp_pmatch; + ctx.re_source = &tmp_source; + } + + return ap_expr_exec_ctx(&ctx); +} + +AP_DECLARE(const char *) ap_expr_str_exec_re(request_rec *r, + const ap_expr_info_t *info, + apr_size_t nmatch, + ap_regmatch_t *pmatch, + const char **source, + const char **err) +{ + ap_expr_eval_ctx_t ctx; + int dont_vary, rc; + const char *tmp_source = NULL, *vary_this = NULL; + ap_regmatch_t tmp_pmatch[AP_MAX_REG_MATCH]; + const char *result; + + AP_DEBUG_ASSERT(info->flags & AP_EXPR_FLAG_STRING_RESULT); + + if (info->root_node->node_op == op_String) { + /* short-cut for constant strings */ + *err = NULL; + return (const char *)info->root_node->node_arg1; + } + + dont_vary = (info->flags & AP_EXPR_FLAG_DONT_VARY); + + ctx.r = r; + ctx.c = r->connection; + ctx.s = r->server; + ctx.p = r->pool; + ctx.err = err; + ctx.info = info; + ctx.re_nmatch = nmatch; + ctx.re_pmatch = pmatch; + ctx.re_source = source; + ctx.vary_this = dont_vary ? NULL : &vary_this; + ctx.data = NULL; + ctx.result_string = &result; + + if (!pmatch) { + ctx.re_nmatch = AP_MAX_REG_MATCH; + ctx.re_pmatch = tmp_pmatch; + ctx.re_source = &tmp_source; + } + + rc = ap_expr_exec_ctx(&ctx); + if (rc > 0) + return result; + else if (rc < 0) + return NULL; + else + ap_assert(0); +} + +AP_DECLARE(const char *) ap_expr_str_exec(request_rec *r, + const ap_expr_info_t *info, + const char **err) +{ + return ap_expr_str_exec_re(r, info, 0, NULL, NULL, err); +} + + +static void add_vary(ap_expr_eval_ctx_t *ctx, const char *name) +{ + if (!ctx->vary_this) + return; + + if (*ctx->vary_this) { + *ctx->vary_this = apr_pstrcat(ctx->p, *ctx->vary_this, ", ", name, + NULL); + } + else { + *ctx->vary_this = name; + } +} + +static const char *req_table_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + const char *name = (const char *)data; + apr_table_t *t; + if (!ctx->r) + return ""; + + if (name[2] == 's') { /* resp */ + /* Try r->headers_out first, fall back on err_headers_out. */ + const char *v = apr_table_get(ctx->r->headers_out, arg); + if (v) { + return v; + } + t = ctx->r->err_headers_out; + } + else if (name[0] == 'n') /* notes */ + t = ctx->r->notes; + else if (name[3] == 'e') /* reqenv */ + t = ctx->r->subprocess_env; + else { /* req, http */ + t = ctx->r->headers_in; + add_vary(ctx, arg); + } + return apr_table_get(t, arg); +} + +static const char *env_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + const char *res; + /* this order is for ssl_expr compatibility */ + if (ctx->r) { + if ((res = apr_table_get(ctx->r->notes, arg)) != NULL) + return res; + else if ((res = apr_table_get(ctx->r->subprocess_env, arg)) != NULL) + return res; + } + return getenv(arg); +} + +static const char *osenv_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return getenv(arg); +} + +static const char *tolower_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + char *result = apr_pstrdup(ctx->p, arg); + ap_str_tolower(result); + return result; +} + +static const char *toupper_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + char *result = apr_pstrdup(ctx->p, arg); + ap_str_toupper(result); + return result; +} + +static const char *escape_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return ap_escape_uri(ctx->p, arg); +} + +#define MAX_FILE_SIZE 10*1024*1024 +static const char *file_func(ap_expr_eval_ctx_t *ctx, const void *data, + char *arg) +{ + apr_file_t *fp; + char *buf; + apr_off_t offset; + apr_size_t len; + apr_finfo_t finfo; + + if (apr_file_open(&fp, arg, APR_READ|APR_BUFFERED, + APR_OS_DEFAULT, ctx->p) != APR_SUCCESS) { + *ctx->err = apr_psprintf(ctx->p, "Cannot open file %s", arg); + return ""; + } + apr_file_info_get(&finfo, APR_FINFO_SIZE, fp); + if (finfo.size > MAX_FILE_SIZE) { + *ctx->err = apr_psprintf(ctx->p, "File %s too large", arg); + apr_file_close(fp); + return ""; + } + len = (apr_size_t)finfo.size; + if (len == 0) { + apr_file_close(fp); + return ""; + } + else { + if ((buf = (char *)apr_palloc(ctx->p, sizeof(char)*(len+1))) == NULL) { + *ctx->err = "Cannot allocate memory"; + apr_file_close(fp); + return ""; + } + offset = 0; + apr_file_seek(fp, APR_SET, &offset); + if (apr_file_read(fp, buf, &len) != APR_SUCCESS) { + *ctx->err = apr_psprintf(ctx->p, "Cannot read from file %s", arg); + apr_file_close(fp); + return ""; + } + buf[len] = '\0'; + } + apr_file_close(fp); + return buf; +} + +static const char *filesize_func(ap_expr_eval_ctx_t *ctx, const void *data, + char *arg) +{ + apr_finfo_t sb; + if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) == APR_SUCCESS + && sb.filetype == APR_REG && sb.size > 0) + return apr_psprintf(ctx->p, "%" APR_OFF_T_FMT, sb.size); + else + return "0"; +} + +static const char *unescape_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + char *result = apr_pstrdup(ctx->p, arg); + int ret = ap_unescape_url_keep2f(result, 0); + if (ret == OK) + return result; + ap_log_rerror(LOG_MARK(ctx->info), APLOG_DEBUG, 0, ctx->r, APLOGNO(00538) + "%s %% escape in unescape('%s') at %s:%d", + ret == HTTP_BAD_REQUEST ? "Bad" : "Forbidden", arg, + ctx->info->filename, ctx->info->line_number); + return ""; +} + +static int op_nz(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + const char *name = (const char *)data; + if (name[0] == 'z') + return (arg[0] == '\0'); + else + return (arg[0] != '\0'); +} + +static int op_file_min(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + apr_finfo_t sb; + const char *name = (const char *)data; + if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) != APR_SUCCESS) + return FALSE; + switch (name[0]) { + case 'd': + return (sb.filetype == APR_DIR); + case 'e': + return TRUE; + case 'f': + return (sb.filetype == APR_REG); + case 's': + return (sb.filetype == APR_REG && sb.size > 0); + default: + ap_assert(0); + } + return FALSE; +} + +static int op_file_link(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + apr_finfo_t sb; +#if !defined(OS2) + if (apr_stat(&sb, arg, APR_FINFO_MIN | APR_FINFO_LINK, ctx->p) == APR_SUCCESS + && sb.filetype == APR_LNK) { + return TRUE; + } +#endif + return FALSE; +} + +static int op_file_xbit(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + apr_finfo_t sb; + if (apr_stat(&sb, arg, APR_FINFO_PROT| APR_FINFO_LINK, ctx->p) == APR_SUCCESS + && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) { + return TRUE; + } + return FALSE; +} + +static int op_url_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + int rc = FALSE; + request_rec *rsub, *r = ctx->r; + if (!r) + return FALSE; + /* avoid some infinite recursions */ + if (r->main && r->main->uri && r->uri && strcmp(r->main->uri, r->uri) == 0) + return FALSE; + + rsub = ap_sub_req_lookup_uri(arg, r, NULL); + if (rsub->status < 400) { + rc = TRUE; + } + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r, + "Subrequest for -U %s at %s:%d gave status: %d", + arg, ctx->info->filename, ctx->info->line_number, + rsub->status); + ap_destroy_sub_req(rsub); + return rc; +} + +static int op_file_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + int rc = FALSE; + apr_finfo_t sb; + request_rec *rsub, *r = ctx->r; + if (!r) + return FALSE; + rsub = ap_sub_req_lookup_file(arg, r, NULL); + if (rsub->status < 300 && + /* double-check that file exists since default result is 200 */ + apr_stat(&sb, rsub->filename, APR_FINFO_MIN, ctx->p) == APR_SUCCESS) { + rc = TRUE; + } + ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r, + "Subrequest for -F %s at %s:%d gave status: %d", + arg, ctx->info->filename, ctx->info->line_number, + rsub->status); + ap_destroy_sub_req(rsub); + return rc; +} + + +APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_is_https) *is_https = NULL; + +static const char *conn_var_names[] = { + "HTTPS", /* 0 */ + "IPV6", /* 1 */ + "CONN_LOG_ID", /* 2 */ + "CONN_REMOTE_ADDR", /* 3 */ + NULL +}; + +static const char *conn_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + int index = ((const char **)data - conn_var_names); + conn_rec *c = ctx->c; + if (!c) + return ""; + + switch (index) { + case 0: + if (is_https && is_https(c)) + return "on"; + else + return "off"; + case 1: +#if APR_HAVE_IPV6 + { + apr_sockaddr_t *addr = c->client_addr; + if (addr->family == AF_INET6 + && !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr)) + return "on"; + else + return "off"; + } +#else + return "off"; +#endif + case 2: + return c->log_id; + case 3: + return c->client_ip; + default: + ap_assert(0); + return NULL; + } +} + +static const char *request_var_names[] = { + "REQUEST_METHOD", /* 0 */ + "REQUEST_SCHEME", /* 1 */ + "REQUEST_URI", /* 2 */ + "REQUEST_FILENAME", /* 3 */ + "REMOTE_HOST", /* 4 */ + "REMOTE_IDENT", /* 5 */ + "REMOTE_USER", /* 6 */ + "SERVER_ADMIN", /* 7 */ + "SERVER_NAME", /* 8 */ + "SERVER_PORT", /* 9 */ + "SERVER_PROTOCOL", /* 10 */ + "SCRIPT_FILENAME", /* 11 */ + "PATH_INFO", /* 12 */ + "QUERY_STRING", /* 13 */ + "IS_SUBREQ", /* 14 */ + "DOCUMENT_ROOT", /* 15 */ + "AUTH_TYPE", /* 16 */ + "THE_REQUEST", /* 17 */ + "CONTENT_TYPE", /* 18 */ + "HANDLER", /* 19 */ + "REQUEST_LOG_ID", /* 20 */ + "SCRIPT_USER", /* 21 */ + "SCRIPT_GROUP", /* 22 */ + "DOCUMENT_URI", /* 23 */ + "LAST_MODIFIED", /* 24 */ + "CONTEXT_PREFIX", /* 25 */ + "CONTEXT_DOCUMENT_ROOT", /* 26 */ + "REQUEST_STATUS", /* 27 */ + "REMOTE_ADDR", /* 28 */ + NULL +}; + +static const char *request_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + int index = ((const char **)data - request_var_names); + request_rec *r = ctx->r; + if (!r) + return ""; + + switch (index) { + case 0: + return r->method; + case 1: + return ap_http_scheme(r); + case 2: + return r->uri; + case 3: + return r->filename; + case 4: + return ap_get_remote_host(r->connection, r->per_dir_config, + REMOTE_NAME, NULL); + case 5: + return ap_get_remote_logname(r); + case 6: + return r->user; + case 7: + return r->server->server_admin; + case 8: + return ap_get_server_name_for_url(r); + case 9: + return apr_psprintf(ctx->p, "%u", ap_get_server_port(r)); + case 10: + return r->protocol; + case 11: + return r->filename; + case 12: + return r->path_info; + case 13: + return r->args; + case 14: + return (r->main != NULL ? "true" : "false"); + case 15: + return ap_document_root(r); + case 16: + return r->ap_auth_type; + case 17: + return r->the_request; + case 18: + return r->content_type; + case 19: + return r->handler; + case 20: + return r->log_id; + case 21: + { + char *result = ""; + if (r->finfo.valid & APR_FINFO_USER) + apr_uid_name_get(&result, r->finfo.user, ctx->p); + return result; + } + case 22: + { + char *result = ""; + if (r->finfo.valid & APR_FINFO_USER) + apr_gid_name_get(&result, r->finfo.group, ctx->p); + return result; + } + case 23: + return r->uri; + case 24: + { + apr_time_exp_t tm; + apr_time_exp_lt(&tm, r->mtime); + return apr_psprintf(ctx->p, "%02d%02d%02d%02d%02d%02d%02d", + (tm.tm_year / 100) + 19, (tm.tm_year % 100), + tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec); + } + case 25: + return ap_context_prefix(r); + case 26: + return ap_context_document_root(r); + case 27: + return r->status ? apr_psprintf(ctx->p, "%d", r->status) : ""; + case 28: + return r->useragent_ip; + default: + ap_assert(0); + return NULL; + } +} + +static const char *req_header_var_names[] = { + "HTTP_USER_AGENT", + "HTTP_PROXY_CONNECTION", + "HTTP_REFERER", + "HTTP_COOKIE", + "HTTP_FORWARDED", + "HTTP_HOST", + "HTTP_ACCEPT", + NULL +}; + +static const char *req_header_header_names[] = { + "User-Agent", + "Proxy-Connection", + "Referer", + "Cookie", + "Forwarded", + "Host", + "Accept" +}; + +static const char *req_header_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + const char **varname = (const char **)data; + int index = (varname - req_header_var_names); + const char *name; + + AP_DEBUG_ASSERT(index < 6); + if (!ctx->r) + return ""; + + name = req_header_header_names[index]; + add_vary(ctx, name); + return apr_table_get(ctx->r->headers_in, name); +} + +static const char *misc_var_names[] = { + "TIME_YEAR", /* 0 */ + "TIME_MON", /* 1 */ + "TIME_DAY", /* 2 */ + "TIME_HOUR", /* 3 */ + "TIME_MIN", /* 4 */ + "TIME_SEC", /* 5 */ + "TIME_WDAY", /* 6 */ + "TIME", /* 7 */ + "SERVER_SOFTWARE", /* 8 */ + "API_VERSION", /* 9 */ + NULL +}; + +static const char *misc_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) +{ + apr_time_exp_t tm; + int index = ((const char **)data - misc_var_names); + apr_time_exp_lt(&tm, apr_time_now()); + + switch (index) { + case 0: + return apr_psprintf(ctx->p, "%02d%02d", (tm.tm_year / 100) + 19, + tm.tm_year % 100); + case 1: + return apr_psprintf(ctx->p, "%02d", tm.tm_mon+1); + case 2: + return apr_psprintf(ctx->p, "%02d", tm.tm_mday); + case 3: + return apr_psprintf(ctx->p, "%02d", tm.tm_hour); + case 4: + return apr_psprintf(ctx->p, "%02d", tm.tm_min); + case 5: + return apr_psprintf(ctx->p, "%02d", tm.tm_sec); + case 6: + return apr_psprintf(ctx->p, "%d", tm.tm_wday); + case 7: + return apr_psprintf(ctx->p, "%02d%02d%02d%02d%02d%02d%02d", + (tm.tm_year / 100) + 19, (tm.tm_year % 100), + tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec); + case 8: + return ap_get_server_banner(); + case 9: + return apr_itoa(ctx->p, MODULE_MAGIC_NUMBER); + default: + ap_assert(0); + } + + return NULL; +} + +static int subnet_parse_arg(ap_expr_lookup_parms *parms) +{ + apr_ipsubnet_t *subnet; + const char *addr = parms->arg; + const char *mask; + apr_status_t ret; + + if (!parms->arg) { + *parms->err = apr_psprintf(parms->ptemp, + "-%s requires subnet/netmask as constant argument", + parms->name); + return !OK; + } + + mask = ap_strchr_c(addr, '/'); + if (mask) { + addr = apr_pstrmemdup(parms->ptemp, addr, mask - addr); + mask++; + } + + ret = apr_ipsubnet_create(&subnet, addr, mask, parms->pool); + if (ret != APR_SUCCESS) { + *parms->err = "parsing of subnet/netmask failed"; + return !OK; + } + + *parms->data = subnet; + return OK; +} + +static int op_ipmatch(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg1, + const char *arg2) +{ + apr_ipsubnet_t *subnet = (apr_ipsubnet_t *)data; + apr_sockaddr_t *saddr; + + AP_DEBUG_ASSERT(subnet != NULL); + + /* maybe log an error if this goes wrong? */ + if (apr_sockaddr_info_get(&saddr, arg1, APR_UNSPEC, 0, 0, ctx->p) != APR_SUCCESS) + return FALSE; + + return apr_ipsubnet_test(subnet, saddr); +} + +static int op_R(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg1) +{ + apr_ipsubnet_t *subnet = (apr_ipsubnet_t *)data; + + AP_DEBUG_ASSERT(subnet != NULL); + + if (!ctx->r) + return FALSE; + + return apr_ipsubnet_test(subnet, ctx->r->useragent_addr); +} + +static int op_T(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) +{ + switch (arg[0]) { + case '\0': + return FALSE; + case 'o': + case 'O': + return strcasecmp(arg, "off") == 0 ? FALSE : TRUE; + case 'n': + case 'N': + return strcasecmp(arg, "no") == 0 ? FALSE : TRUE; + case 'f': + case 'F': + return strcasecmp(arg, "false") == 0 ? FALSE : TRUE; + case '0': + return arg[1] == '\0' ? FALSE : TRUE; + default: + return TRUE; + } +} + +static int op_fnmatch(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg1, const char *arg2) +{ + return (APR_SUCCESS == apr_fnmatch(arg2, arg1, APR_FNM_PATHNAME)); +} + +static int op_strmatch(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg1, const char *arg2) +{ + return (APR_SUCCESS == apr_fnmatch(arg2, arg1, 0)); +} + +static int op_strcmatch(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg1, const char *arg2) +{ + return (APR_SUCCESS == apr_fnmatch(arg2, arg1, APR_FNM_CASE_BLIND)); +} + +struct expr_provider_single { + const void *func; + const char *name; + ap_expr_lookup_fn_t *arg_parsing_func; + int restricted; +}; + +struct expr_provider_multi { + const void *func; + const char **names; +}; + +static const struct expr_provider_multi var_providers[] = { + { misc_var_fn, misc_var_names }, + { req_header_var_fn, req_header_var_names }, + { request_var_fn, request_var_names }, + { conn_var_fn, conn_var_names }, + { NULL, NULL } +}; + +static const struct expr_provider_single string_func_providers[] = { + { osenv_func, "osenv", NULL, 0 }, + { env_func, "env", NULL, 0 }, + { req_table_func, "resp", NULL, 0 }, + { req_table_func, "req", NULL, 0 }, + /* 'http' as alias for 'req' for compatibility with ssl_expr */ + { req_table_func, "http", NULL, 0 }, + { req_table_func, "note", NULL, 0 }, + { req_table_func, "reqenv", NULL, 0 }, + { tolower_func, "tolower", NULL, 0 }, + { toupper_func, "toupper", NULL, 0 }, + { escape_func, "escape", NULL, 0 }, + { unescape_func, "unescape", NULL, 0 }, + { file_func, "file", NULL, 1 }, + { filesize_func, "filesize", NULL, 1 }, + { NULL, NULL, NULL} +}; +/* XXX: base64 encode/decode ? */ + +static const struct expr_provider_single unary_op_providers[] = { + { op_nz, "n", NULL, 0 }, + { op_nz, "z", NULL, 0 }, + { op_R, "R", subnet_parse_arg, 0 }, + { op_T, "T", NULL, 0 }, + { op_file_min, "d", NULL, 1 }, + { op_file_min, "e", NULL, 1 }, + { op_file_min, "f", NULL, 1 }, + { op_file_min, "s", NULL, 1 }, + { op_file_link, "L", NULL, 1 }, + { op_file_link, "h", NULL, 1 }, + { op_file_xbit, "x", NULL, 1 }, + { op_file_subr, "F", NULL, 0 }, + { op_url_subr, "U", NULL, 0 }, + { op_url_subr, "A", NULL, 0 }, + { NULL, NULL, NULL } +}; + +static const struct expr_provider_single binary_op_providers[] = { + { op_ipmatch, "ipmatch", subnet_parse_arg, 0 }, + { op_fnmatch, "fnmatch", NULL, 0 }, + { op_strmatch, "strmatch", NULL, 0 }, + { op_strcmatch, "strcmatch", NULL, 0 }, + { NULL, NULL, NULL } +}; + +static int core_expr_lookup(ap_expr_lookup_parms *parms) +{ + switch (parms->type) { + case AP_EXPR_FUNC_VAR: { + const struct expr_provider_multi *prov = var_providers; + while (prov->func) { + const char **name = prov->names; + while (*name) { + if (strcasecmp(*name, parms->name) == 0) { + *parms->func = prov->func; + *parms->data = name; + return OK; + } + name++; + } + prov++; + } + } + break; + case AP_EXPR_FUNC_STRING: + case AP_EXPR_FUNC_OP_UNARY: + case AP_EXPR_FUNC_OP_BINARY: { + const struct expr_provider_single *prov; + switch (parms->type) { + case AP_EXPR_FUNC_STRING: + prov = string_func_providers; + break; + case AP_EXPR_FUNC_OP_UNARY: + prov = unary_op_providers; + break; + case AP_EXPR_FUNC_OP_BINARY: + prov = binary_op_providers; + break; + default: + ap_assert(0); + } + while (prov->func) { + int match; + if (parms->type == AP_EXPR_FUNC_OP_UNARY) + match = !strcmp(prov->name, parms->name); + else + match = !strcasecmp(prov->name, parms->name); + if (match) { + if ((parms->flags & AP_EXPR_FLAG_RESTRICTED) + && prov->restricted) { + *parms->err = + apr_psprintf(parms->ptemp, + "%s%s not available in restricted context", + (parms->type == AP_EXPR_FUNC_STRING) ? "" : "-", + prov->name); + return !OK; + } + *parms->func = prov->func; + if (prov->arg_parsing_func) { + return prov->arg_parsing_func(parms); + } + else { + *parms->data = prov->name; + return OK; + } + } + prov++; + } + } + break; + default: + break; + } + return DECLINED; +} + +static int expr_lookup_not_found(ap_expr_lookup_parms *parms) +{ + const char *type; + const char *prefix = ""; + + switch (parms->type) { + case AP_EXPR_FUNC_VAR: + type = "Variable"; + break; + case AP_EXPR_FUNC_STRING: + type = "Function"; + break; + case AP_EXPR_FUNC_LIST: + type = "List-returning function"; + break; + case AP_EXPR_FUNC_OP_UNARY: + type = "Unary operator"; + break; + case AP_EXPR_FUNC_OP_BINARY: + type = "Binary operator"; + break; + default: + *parms->err = "Inavalid expression type in expr_lookup"; + return !OK; + } + if ( parms->type == AP_EXPR_FUNC_OP_UNARY + || parms->type == AP_EXPR_FUNC_OP_BINARY) { + prefix = "-"; + } + *parms->err = apr_psprintf(parms->ptemp, "%s '%s%s' does not exist", type, + prefix, parms->name); + return !OK; +} + +static int ap_expr_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + apr_pool_cleanup_register(pconf, &is_https, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + return OK; +} + +void ap_expr_init(apr_pool_t *p) +{ + ap_hook_expr_lookup(core_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_expr_lookup(expr_lookup_not_found, NULL, NULL, APR_HOOK_REALLY_LAST); + ap_hook_post_config(ap_expr_post_config, NULL, NULL, APR_HOOK_MIDDLE); +} + diff --git a/server/util_expr_parse.c b/server/util_expr_parse.c new file mode 100644 index 00000000..bcf0173b --- /dev/null +++ b/server/util_expr_parse.c @@ -0,0 +1,2130 @@ +/* A Bison parser, made by GNU Bison 2.5. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.5" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + +/* Substitute the variable and function names. */ +#define yyparse ap_expr_yyparse +#define yylex ap_expr_yylex +#define yyerror ap_expr_yyerror +#define yylval ap_expr_yylval +#define yychar ap_expr_yychar +#define yydebug ap_expr_yydebug +#define yynerrs ap_expr_yynerrs + + +/* Copy the first part of user declarations. */ + +/* Line 268 of yacc.c */ +#line 31 "util_expr_parse.y" + +#include "util_expr_private.h" + + +/* Line 268 of yacc.c */ +#line 84 "util_expr_parse.c" + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + T_TRUE = 258, + T_FALSE = 259, + T_EXPR_BOOL = 260, + T_EXPR_STRING = 261, + T_ERROR = 262, + T_DIGIT = 263, + T_ID = 264, + T_STRING = 265, + T_REGEX = 266, + T_REGEX_I = 267, + T_REGEX_BACKREF = 268, + T_OP_UNARY = 269, + T_OP_BINARY = 270, + T_STR_BEGIN = 271, + T_STR_END = 272, + T_VAR_BEGIN = 273, + T_VAR_END = 274, + T_OP_EQ = 275, + T_OP_NE = 276, + T_OP_LT = 277, + T_OP_LE = 278, + T_OP_GT = 279, + T_OP_GE = 280, + T_OP_REG = 281, + T_OP_NRE = 282, + T_OP_IN = 283, + T_OP_STR_EQ = 284, + T_OP_STR_NE = 285, + T_OP_STR_LT = 286, + T_OP_STR_LE = 287, + T_OP_STR_GT = 288, + T_OP_STR_GE = 289, + T_OP_CONCAT = 290, + T_OP_OR = 291, + T_OP_AND = 292, + T_OP_NOT = 293 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 293 of yacc.c */ +#line 35 "util_expr_parse.y" + + char *cpVal; + ap_expr_t *exVal; + int num; + + + +/* Line 293 of yacc.c */ +#line 166 "util_expr_parse.c" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + +/* Copy the second part of user declarations. */ + +/* Line 343 of yacc.c */ +#line 102 "util_expr_parse.y" + +#include "util_expr_private.h" +#define yyscanner ctx->scanner + +int ap_expr_yylex(YYSTYPE *lvalp, void *scanner); + + +/* Line 343 of yacc.c */ +#line 186 "util_expr_parse.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int yyi) +#else +static int +YYID (yyi) + int yyi; +#endif +{ + return yyi; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 28 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 128 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 45 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 14 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 53 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 96 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 293 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 39, 40, 2, 2, 43, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 44, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 41, 2, 42, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 6, 9, 11, 13, 15, 18, 22, + 26, 28, 31, 35, 39, 41, 45, 49, 53, 57, + 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, + 101, 103, 107, 109, 113, 116, 118, 120, 122, 124, + 126, 130, 136, 138, 142, 144, 146, 148, 152, 155, + 157, 159, 161, 166 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 46, 0, -1, 5, 47, -1, 6, 51, -1, 7, + -1, 3, -1, 4, -1, 38, 47, -1, 47, 36, + 47, -1, 47, 37, 47, -1, 48, -1, 14, 54, + -1, 54, 15, 54, -1, 39, 47, 40, -1, 7, + -1, 54, 20, 54, -1, 54, 21, 54, -1, 54, + 22, 54, -1, 54, 23, 54, -1, 54, 24, 54, + -1, 54, 25, 54, -1, 54, 29, 54, -1, 54, + 30, 54, -1, 54, 31, 54, -1, 54, 32, 54, + -1, 54, 33, 54, -1, 54, 34, 54, -1, 54, + 28, 49, -1, 54, 26, 55, -1, 54, 27, 55, + -1, 57, -1, 41, 50, 42, -1, 54, -1, 50, + 43, 54, -1, 51, 52, -1, 52, -1, 7, -1, + 10, -1, 53, -1, 56, -1, 18, 9, 19, -1, + 18, 9, 44, 51, 19, -1, 8, -1, 54, 35, + 54, -1, 53, -1, 56, -1, 58, -1, 16, 51, + 17, -1, 16, 17, -1, 11, -1, 12, -1, 13, + -1, 9, 39, 54, 40, -1, 9, 39, 54, 40, + -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 112, 112, 113, 114, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 129, 130, 131, 132, 133, + 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 146, 147, 150, 151, 154, 155, 156, 159, 160, 161, + 164, 165, 168, 169, 170, 171, 172, 173, 174, 177, + 186, 197, 204, 207 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "T_TRUE", "T_FALSE", "T_EXPR_BOOL", + "T_EXPR_STRING", "T_ERROR", "T_DIGIT", "T_ID", "T_STRING", "T_REGEX", + "T_REGEX_I", "T_REGEX_BACKREF", "T_OP_UNARY", "T_OP_BINARY", + "T_STR_BEGIN", "T_STR_END", "T_VAR_BEGIN", "T_VAR_END", "T_OP_EQ", + "T_OP_NE", "T_OP_LT", "T_OP_LE", "T_OP_GT", "T_OP_GE", "T_OP_REG", + "T_OP_NRE", "T_OP_IN", "T_OP_STR_EQ", "T_OP_STR_NE", "T_OP_STR_LT", + "T_OP_STR_LE", "T_OP_STR_GT", "T_OP_STR_GE", "T_OP_CONCAT", "T_OP_OR", + "T_OP_AND", "T_OP_NOT", "'('", "')'", "'{'", "'}'", "','", "':'", + "$accept", "root", "expr", "comparison", "wordlist", "words", "string", + "strpart", "var", "word", "regex", "backref", "lstfunccall", + "strfunccall", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 40, + 41, 123, 125, 44, 58 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 45, 46, 46, 46, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 49, 49, 50, 50, 51, 51, 51, 52, 52, 52, + 53, 53, 54, 54, 54, 54, 54, 54, 54, 55, + 55, 56, 57, 58 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 2, 2, 1, 1, 1, 2, 3, 3, + 1, 2, 3, 3, 1, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 1, 3, 1, 3, 2, 1, 1, 1, 1, 1, + 3, 5, 1, 3, 1, 1, 1, 3, 2, 1, + 1, 1, 4, 4 +}; + +/* YYDEFACT[STATE-NAME] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 0, 4, 0, 5, 6, 14, 42, 0, + 51, 0, 0, 0, 0, 0, 2, 10, 44, 0, + 45, 46, 36, 37, 3, 35, 38, 39, 1, 0, + 11, 48, 0, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 34, 0, 47, 40, 0, + 13, 8, 9, 12, 15, 16, 17, 18, 19, 20, + 49, 50, 28, 29, 0, 0, 27, 30, 21, 22, + 23, 24, 25, 26, 43, 53, 0, 0, 0, 32, + 41, 0, 31, 0, 52, 33 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 4, 16, 17, 76, 88, 24, 25, 18, 19, + 72, 20, 77, 21 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -35 +static const yytype_int8 yypact[] = +{ + 48, 60, 73, -35, 7, -35, -35, -35, -35, -34, + -35, 43, 8, 11, 60, 60, 86, -35, -35, 80, + -35, -35, -35, -35, 108, -35, -35, -35, -35, 43, + 25, -35, 79, -17, -35, -8, 60, 60, 43, 43, + 43, 43, 43, 43, 43, 5, 5, 0, 43, 43, + 43, 43, 43, 43, 43, -35, -27, -35, -35, 73, + -35, 86, 3, 25, 25, 25, 25, 25, 25, 25, + -35, -35, -35, -35, 23, 43, -35, -35, 25, 25, + 25, 25, 25, 25, 25, -35, 106, 43, 85, 25, + -35, -21, -35, 43, -35, 25 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -35, -35, 57, -35, -35, -35, -9, -20, -2, -5, + -4, -1, -35, -35 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 26, 27, 58, 32, 55, 29, 30, 28, 54, 74, + 26, 27, 55, 85, 54, 22, 70, 71, 23, 94, + 33, 10, 26, 27, 56, 31, 13, 59, 36, 37, + 26, 27, 60, 63, 64, 65, 66, 67, 68, 69, + 37, 75, 73, 78, 79, 80, 81, 82, 83, 84, + 86, 8, 9, 1, 2, 3, 10, 26, 27, 12, + 54, 13, 87, 5, 6, 0, 55, 7, 8, 9, + 89, 34, 35, 10, 11, 0, 12, 0, 13, 0, + 22, 0, 91, 23, 26, 27, 10, 0, 95, 23, + 0, 13, 10, 61, 62, 38, 57, 13, 14, 15, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 23, 0, 23, 10, + 0, 10, 36, 37, 13, 90, 13, 92, 93 +}; + +#define yypact_value_is_default(yystate) \ + ((yystate) == (-35)) + +#define yytable_value_is_error(yytable_value) \ + YYID (0) + +static const yytype_int8 yycheck[] = +{ + 2, 2, 19, 12, 24, 39, 11, 0, 35, 9, + 12, 12, 32, 40, 35, 7, 11, 12, 10, 40, + 9, 13, 24, 24, 29, 17, 18, 44, 36, 37, + 32, 32, 40, 38, 39, 40, 41, 42, 43, 44, + 37, 41, 46, 48, 49, 50, 51, 52, 53, 54, + 59, 8, 9, 5, 6, 7, 13, 59, 59, 16, + 35, 18, 39, 3, 4, -1, 86, 7, 8, 9, + 75, 14, 15, 13, 14, -1, 16, -1, 18, -1, + 7, -1, 87, 10, 86, 86, 13, -1, 93, 10, + -1, 18, 13, 36, 37, 15, 17, 18, 38, 39, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 10, -1, 10, 13, + -1, 13, 36, 37, 18, 19, 18, 42, 43 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 5, 6, 7, 46, 3, 4, 7, 8, 9, + 13, 14, 16, 18, 38, 39, 47, 48, 53, 54, + 56, 58, 7, 10, 51, 52, 53, 56, 0, 39, + 54, 17, 51, 9, 47, 47, 36, 37, 15, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 52, 54, 17, 19, 44, + 40, 47, 47, 54, 54, 54, 54, 54, 54, 54, + 11, 12, 55, 55, 9, 41, 49, 57, 54, 54, + 54, 54, 54, 54, 54, 40, 51, 39, 50, 54, + 19, 54, 42, 43, 40, 54 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. However, + YYFAIL appears to be in use. Nevertheless, it is formally deprecated + in Bison 2.4.2's NEWS entry, where a plan to phase it out is + discussed. */ + +#define YYFAIL goto yyerrlab +#if defined YYFAIL + /* This is here to suppress warnings from the GCC cpp's + -Wunused-macros. Normally we don't worry about that warning, but + some users do, and we want to make it easy for users to remove + YYFAIL uses, which will produce warnings from Bison 2.5. */ +#endif + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (ctx, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* This macro is provided for backward compatibility. */ + +#ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, yyscanner) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, ctx); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, ap_expr_parse_ctx_t *ctx) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, ctx) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + ap_expr_parse_ctx_t *ctx; +#endif +{ + if (!yyvaluep) + return; + YYUSE (ctx); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, ap_expr_parse_ctx_t *ctx) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, ctx) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + ap_expr_parse_ctx_t *ctx; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep, ctx); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +#else +static void +yy_stack_print (yybottom, yytop) + yytype_int16 *yybottom; + yytype_int16 *yytop; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, int yyrule, ap_expr_parse_ctx_t *ctx) +#else +static void +yy_reduce_print (yyvsp, yyrule, ctx) + YYSTYPE *yyvsp; + int yyrule; + ap_expr_parse_ctx_t *ctx; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , ctx); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, Rule, ctx); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) +{ + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = 0; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; + + /* There are many possibilities here to consider: + - Assume YYFAIL is not used. It's too flawed to consider. See + <http://lists.gnu.org/archive/html/bison-patches/2009-12/msg00024.html> + for details. YYERROR is fine as it does not invoke this + function. + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) + { + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; +} +#endif /* YYERROR_VERBOSE */ + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, ap_expr_parse_ctx_t *ctx) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, ctx) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + ap_expr_parse_ctx_t *ctx; +#endif +{ + YYUSE (yyvaluep); + YYUSE (ctx); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (ap_expr_parse_ctx_t *ctx); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (ap_expr_parse_ctx_t *ctx) +#else +int +yyparse (ctx) + ap_expr_parse_ctx_t *ctx; +#endif +#endif +{ +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; + + /* Number of syntax errors so far. */ + int yynerrs; + + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + `yyss': related to states. + `yyvs': related to semantic values. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yytoken = 0; + yyss = yyssa; + yyvs = yyvsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + yyssp = yyss; + yyvsp = yyvs; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + +/* Line 1806 of yacc.c */ +#line 112 "util_expr_parse.y" + { ctx->expr = (yyvsp[(2) - (2)].exVal); } + break; + + case 3: + +/* Line 1806 of yacc.c */ +#line 113 "util_expr_parse.y" + { ctx->expr = (yyvsp[(2) - (2)].exVal); } + break; + + case 4: + +/* Line 1806 of yacc.c */ +#line 114 "util_expr_parse.y" + { YYABORT; } + break; + + case 5: + +/* Line 1806 of yacc.c */ +#line 117 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_True, NULL, NULL, ctx); } + break; + + case 6: + +/* Line 1806 of yacc.c */ +#line 118 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_False, NULL, NULL, ctx); } + break; + + case 7: + +/* Line 1806 of yacc.c */ +#line 119 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Not, (yyvsp[(2) - (2)].exVal), NULL, ctx); } + break; + + case 8: + +/* Line 1806 of yacc.c */ +#line 120 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Or, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 9: + +/* Line 1806 of yacc.c */ +#line 121 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_And, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 10: + +/* Line 1806 of yacc.c */ +#line 122 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Comp, (yyvsp[(1) - (1)].exVal), NULL, ctx); } + break; + + case 11: + +/* Line 1806 of yacc.c */ +#line 123 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_unary_op_make( (yyvsp[(1) - (2)].cpVal), (yyvsp[(2) - (2)].exVal), ctx); } + break; + + case 12: + +/* Line 1806 of yacc.c */ +#line 124 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_binary_op_make((yyvsp[(2) - (3)].cpVal), (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 13: + +/* Line 1806 of yacc.c */ +#line 125 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(2) - (3)].exVal); } + break; + + case 14: + +/* Line 1806 of yacc.c */ +#line 126 "util_expr_parse.y" + { YYABORT; } + break; + + case 15: + +/* Line 1806 of yacc.c */ +#line 129 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_EQ, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 16: + +/* Line 1806 of yacc.c */ +#line 130 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_NE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 17: + +/* Line 1806 of yacc.c */ +#line 131 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_LT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 18: + +/* Line 1806 of yacc.c */ +#line 132 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_LE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 19: + +/* Line 1806 of yacc.c */ +#line 133 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_GT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 20: + +/* Line 1806 of yacc.c */ +#line 134 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_GE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 21: + +/* Line 1806 of yacc.c */ +#line 135 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_EQ, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 22: + +/* Line 1806 of yacc.c */ +#line 136 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_NE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 23: + +/* Line 1806 of yacc.c */ +#line 137 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_LT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 24: + +/* Line 1806 of yacc.c */ +#line 138 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_LE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 25: + +/* Line 1806 of yacc.c */ +#line 139 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_GT, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 26: + +/* Line 1806 of yacc.c */ +#line 140 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_STR_GE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 27: + +/* Line 1806 of yacc.c */ +#line 141 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_IN, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 28: + +/* Line 1806 of yacc.c */ +#line 142 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_REG, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 29: + +/* Line 1806 of yacc.c */ +#line 143 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_NRE, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 30: + +/* Line 1806 of yacc.c */ +#line 146 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 31: + +/* Line 1806 of yacc.c */ +#line 147 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(2) - (3)].exVal); } + break; + + case 32: + +/* Line 1806 of yacc.c */ +#line 150 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_ListElement, (yyvsp[(1) - (1)].exVal), NULL, ctx); } + break; + + case 33: + +/* Line 1806 of yacc.c */ +#line 151 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_ListElement, (yyvsp[(3) - (3)].exVal), (yyvsp[(1) - (3)].exVal), ctx); } + break; + + case 34: + +/* Line 1806 of yacc.c */ +#line 154 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Concat, (yyvsp[(1) - (2)].exVal), (yyvsp[(2) - (2)].exVal), ctx); } + break; + + case 35: + +/* Line 1806 of yacc.c */ +#line 155 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 36: + +/* Line 1806 of yacc.c */ +#line 156 "util_expr_parse.y" + { YYABORT; } + break; + + case 37: + +/* Line 1806 of yacc.c */ +#line 159 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_String, (yyvsp[(1) - (1)].cpVal), NULL, ctx); } + break; + + case 38: + +/* Line 1806 of yacc.c */ +#line 160 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 39: + +/* Line 1806 of yacc.c */ +#line 161 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 40: + +/* Line 1806 of yacc.c */ +#line 164 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_var_make((yyvsp[(2) - (3)].cpVal), ctx); } + break; + + case 41: + +/* Line 1806 of yacc.c */ +#line 165 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_str_func_make((yyvsp[(2) - (5)].cpVal), (yyvsp[(4) - (5)].exVal), ctx); } + break; + + case 42: + +/* Line 1806 of yacc.c */ +#line 168 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Digit, (yyvsp[(1) - (1)].cpVal), NULL, ctx); } + break; + + case 43: + +/* Line 1806 of yacc.c */ +#line 169 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_Concat, (yyvsp[(1) - (3)].exVal), (yyvsp[(3) - (3)].exVal), ctx); } + break; + + case 44: + +/* Line 1806 of yacc.c */ +#line 170 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 45: + +/* Line 1806 of yacc.c */ +#line 171 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 46: + +/* Line 1806 of yacc.c */ +#line 172 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(1) - (1)].exVal); } + break; + + case 47: + +/* Line 1806 of yacc.c */ +#line 173 "util_expr_parse.y" + { (yyval.exVal) = (yyvsp[(2) - (3)].exVal); } + break; + + case 48: + +/* Line 1806 of yacc.c */ +#line 174 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_make(op_String, "", NULL, ctx); } + break; + + case 49: + +/* Line 1806 of yacc.c */ +#line 177 "util_expr_parse.y" + { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, (yyvsp[(1) - (1)].cpVal), + AP_REG_EXTENDED|AP_REG_NOSUB)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + (yyval.exVal) = ap_expr_make(op_Regex, regex, NULL, ctx); + } + break; + + case 50: + +/* Line 1806 of yacc.c */ +#line 186 "util_expr_parse.y" + { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, (yyvsp[(1) - (1)].cpVal), + AP_REG_EXTENDED|AP_REG_NOSUB|AP_REG_ICASE)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + (yyval.exVal) = ap_expr_make(op_Regex, regex, NULL, ctx); + } + break; + + case 51: + +/* Line 1806 of yacc.c */ +#line 197 "util_expr_parse.y" + { + int *n = apr_palloc(ctx->pool, sizeof(int)); + *n = (yyvsp[(1) - (1)].num); + (yyval.exVal) = ap_expr_make(op_RegexBackref, n, NULL, ctx); + } + break; + + case 52: + +/* Line 1806 of yacc.c */ +#line 204 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_list_func_make((yyvsp[(1) - (4)].cpVal), (yyvsp[(3) - (4)].exVal), ctx); } + break; + + case 53: + +/* Line 1806 of yacc.c */ +#line 207 "util_expr_parse.y" + { (yyval.exVal) = ap_expr_str_func_make((yyvsp[(1) - (4)].cpVal), (yyvsp[(3) - (4)].exVal), ctx); } + break; + + + +/* Line 1806 of yacc.c */ +#line 1891 "util_expr_parse.c" + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (ctx, YY_("syntax error")); +#else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) + { + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (ctx, yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; + } +# undef YYSYNTAX_ERROR +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, ctx); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp, ctx); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined(yyoverflow) || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (ctx, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, ctx); + } + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, ctx); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + +/* Line 2067 of yacc.c */ +#line 210 "util_expr_parse.y" + + +void yyerror(ap_expr_parse_ctx_t *ctx, const char *s) +{ + /* s is allocated on the stack */ + ctx->error = apr_pstrdup(ctx->ptemp, s); +} + + diff --git a/server/util_expr_parse.h b/server/util_expr_parse.h new file mode 100644 index 00000000..8540ec6e --- /dev/null +++ b/server/util_expr_parse.h @@ -0,0 +1,104 @@ +/* A Bison parser, made by GNU Bison 2.5. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + T_TRUE = 258, + T_FALSE = 259, + T_EXPR_BOOL = 260, + T_EXPR_STRING = 261, + T_ERROR = 262, + T_DIGIT = 263, + T_ID = 264, + T_STRING = 265, + T_REGEX = 266, + T_REGEX_I = 267, + T_REGEX_BACKREF = 268, + T_OP_UNARY = 269, + T_OP_BINARY = 270, + T_STR_BEGIN = 271, + T_STR_END = 272, + T_VAR_BEGIN = 273, + T_VAR_END = 274, + T_OP_EQ = 275, + T_OP_NE = 276, + T_OP_LT = 277, + T_OP_LE = 278, + T_OP_GT = 279, + T_OP_GE = 280, + T_OP_REG = 281, + T_OP_NRE = 282, + T_OP_IN = 283, + T_OP_STR_EQ = 284, + T_OP_STR_NE = 285, + T_OP_STR_LT = 286, + T_OP_STR_LE = 287, + T_OP_STR_GT = 288, + T_OP_STR_GE = 289, + T_OP_CONCAT = 290, + T_OP_OR = 291, + T_OP_AND = 292, + T_OP_NOT = 293 + }; +#endif + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 2068 of yacc.c */ +#line 35 "util_expr_parse.y" + + char *cpVal; + ap_expr_t *exVal; + int num; + + + +/* Line 2068 of yacc.c */ +#line 96 "util_expr_parse.h" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + + + diff --git a/server/util_expr_parse.y b/server/util_expr_parse.y new file mode 100644 index 00000000..85ed1231 --- /dev/null +++ b/server/util_expr_parse.y @@ -0,0 +1,217 @@ +/* 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. + */ + +/* based on ap_expr_parse.y from mod_ssl */ + +/* _________________________________________________________________ +** +** Expression Parser +** _________________________________________________________________ +*/ + +%pure-parser +%error-verbose +%defines +%lex-param { void *yyscanner } +%parse-param { ap_expr_parse_ctx_t *ctx } + +%{ +#include "util_expr_private.h" +%} + +%union { + char *cpVal; + ap_expr_t *exVal; + int num; +} + +%token T_TRUE +%token T_FALSE + +%token T_EXPR_BOOL +%token T_EXPR_STRING + +%token <cpVal> T_ERROR + +%token <cpVal> T_DIGIT +%token <cpVal> T_ID +%token <cpVal> T_STRING +%token <cpVal> T_REGEX +%token <cpVal> T_REGEX_I +%token <num> T_REGEX_BACKREF +%token <cpVal> T_OP_UNARY +%token <cpVal> T_OP_BINARY + +%token T_STR_BEGIN +%token T_STR_END +%token T_VAR_BEGIN +%token T_VAR_END + +%token T_OP_EQ +%token T_OP_NE +%token T_OP_LT +%token T_OP_LE +%token T_OP_GT +%token T_OP_GE +%token T_OP_REG +%token T_OP_NRE +%token T_OP_IN +%token T_OP_STR_EQ +%token T_OP_STR_NE +%token T_OP_STR_LT +%token T_OP_STR_LE +%token T_OP_STR_GT +%token T_OP_STR_GE +%token T_OP_CONCAT + +%token T_OP_OR +%token T_OP_AND +%token T_OP_NOT + +%right T_OP_OR +%right T_OP_AND +%right T_OP_NOT +%right T_OP_CONCAT + +%type <exVal> expr +%type <exVal> comparison +%type <exVal> strfunccall +%type <exVal> lstfunccall +%type <exVal> regex +%type <exVal> words +%type <exVal> wordlist +%type <exVal> word +%type <exVal> string +%type <exVal> strpart +%type <exVal> var +%type <exVal> backref + +%{ +#include "util_expr_private.h" +#define yyscanner ctx->scanner + +int ap_expr_yylex(YYSTYPE *lvalp, void *scanner); +%} + + +%% + +root : T_EXPR_BOOL expr { ctx->expr = $2; } + | T_EXPR_STRING string { ctx->expr = $2; } + | T_ERROR { YYABORT; } + ; + +expr : T_TRUE { $$ = ap_expr_make(op_True, NULL, NULL, ctx); } + | T_FALSE { $$ = ap_expr_make(op_False, NULL, NULL, ctx); } + | T_OP_NOT expr { $$ = ap_expr_make(op_Not, $2, NULL, ctx); } + | expr T_OP_OR expr { $$ = ap_expr_make(op_Or, $1, $3, ctx); } + | expr T_OP_AND expr { $$ = ap_expr_make(op_And, $1, $3, ctx); } + | comparison { $$ = ap_expr_make(op_Comp, $1, NULL, ctx); } + | T_OP_UNARY word { $$ = ap_expr_unary_op_make( $1, $2, ctx); } + | word T_OP_BINARY word { $$ = ap_expr_binary_op_make($2, $1, $3, ctx); } + | '(' expr ')' { $$ = $2; } + | T_ERROR { YYABORT; } + ; + +comparison: word T_OP_EQ word { $$ = ap_expr_make(op_EQ, $1, $3, ctx); } + | word T_OP_NE word { $$ = ap_expr_make(op_NE, $1, $3, ctx); } + | word T_OP_LT word { $$ = ap_expr_make(op_LT, $1, $3, ctx); } + | word T_OP_LE word { $$ = ap_expr_make(op_LE, $1, $3, ctx); } + | word T_OP_GT word { $$ = ap_expr_make(op_GT, $1, $3, ctx); } + | word T_OP_GE word { $$ = ap_expr_make(op_GE, $1, $3, ctx); } + | word T_OP_STR_EQ word { $$ = ap_expr_make(op_STR_EQ, $1, $3, ctx); } + | word T_OP_STR_NE word { $$ = ap_expr_make(op_STR_NE, $1, $3, ctx); } + | word T_OP_STR_LT word { $$ = ap_expr_make(op_STR_LT, $1, $3, ctx); } + | word T_OP_STR_LE word { $$ = ap_expr_make(op_STR_LE, $1, $3, ctx); } + | word T_OP_STR_GT word { $$ = ap_expr_make(op_STR_GT, $1, $3, ctx); } + | word T_OP_STR_GE word { $$ = ap_expr_make(op_STR_GE, $1, $3, ctx); } + | word T_OP_IN wordlist { $$ = ap_expr_make(op_IN, $1, $3, ctx); } + | word T_OP_REG regex { $$ = ap_expr_make(op_REG, $1, $3, ctx); } + | word T_OP_NRE regex { $$ = ap_expr_make(op_NRE, $1, $3, ctx); } + ; + +wordlist : lstfunccall { $$ = $1; } + | '{' words '}' { $$ = $2; } + ; + +words : word { $$ = ap_expr_make(op_ListElement, $1, NULL, ctx); } + | words ',' word { $$ = ap_expr_make(op_ListElement, $3, $1, ctx); } + ; + +string : string strpart { $$ = ap_expr_make(op_Concat, $1, $2, ctx); } + | strpart { $$ = $1; } + | T_ERROR { YYABORT; } + ; + +strpart : T_STRING { $$ = ap_expr_make(op_String, $1, NULL, ctx); } + | var { $$ = $1; } + | backref { $$ = $1; } + ; + +var : T_VAR_BEGIN T_ID T_VAR_END { $$ = ap_expr_var_make($2, ctx); } + | T_VAR_BEGIN T_ID ':' string T_VAR_END { $$ = ap_expr_str_func_make($2, $4, ctx); } + ; + +word : T_DIGIT { $$ = ap_expr_make(op_Digit, $1, NULL, ctx); } + | word T_OP_CONCAT word { $$ = ap_expr_make(op_Concat, $1, $3, ctx); } + | var { $$ = $1; } + | backref { $$ = $1; } + | strfunccall { $$ = $1; } + | T_STR_BEGIN string T_STR_END { $$ = $2; } + | T_STR_BEGIN T_STR_END { $$ = ap_expr_make(op_String, "", NULL, ctx); } + ; + +regex : T_REGEX { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, $1, + AP_REG_EXTENDED|AP_REG_NOSUB)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + $$ = ap_expr_make(op_Regex, regex, NULL, ctx); + } + | T_REGEX_I { + ap_regex_t *regex; + if ((regex = ap_pregcomp(ctx->pool, $1, + AP_REG_EXTENDED|AP_REG_NOSUB|AP_REG_ICASE)) == NULL) { + ctx->error = "Failed to compile regular expression"; + YYERROR; + } + $$ = ap_expr_make(op_Regex, regex, NULL, ctx); + } + ; + +backref : T_REGEX_BACKREF { + int *n = apr_palloc(ctx->pool, sizeof(int)); + *n = $1; + $$ = ap_expr_make(op_RegexBackref, n, NULL, ctx); + } + ; + +lstfunccall : T_ID '(' word ')' { $$ = ap_expr_list_func_make($1, $3, ctx); } + ; + +strfunccall : T_ID '(' word ')' { $$ = ap_expr_str_func_make($1, $3, ctx); } + ; + +%% + +void yyerror(ap_expr_parse_ctx_t *ctx, const char *s) +{ + /* s is allocated on the stack */ + ctx->error = apr_pstrdup(ctx->ptemp, s); +} + diff --git a/server/util_expr_private.h b/server/util_expr_private.h new file mode 100644 index 00000000..14cc4e3c --- /dev/null +++ b/server/util_expr_private.h @@ -0,0 +1,141 @@ +/* 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. + */ + +#ifndef __AP_EXPR_PRIVATE_H__ +#define __AP_EXPR_PRIVATE_H__ + +#include "httpd.h" +#include "apr_strings.h" +#include "apr_tables.h" +#include "ap_expr.h" + +#ifndef YY_NULL +#define YY_NULL 0 +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#if !APR_HAVE_UNISTD_H +#define YY_NO_UNISTD_H +#endif + +#ifdef _MSC_VER +/* Avoid some warnings with Visual Studio (likely due to a bug in bison) */ +#define YYMALLOC malloc +#define YYFREE free +#endif + +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif + +/** The operations in a parse tree node */ +typedef enum { + op_NOP, + op_True, op_False, + op_Not, op_Or, op_And, + op_Comp, + op_EQ, op_NE, op_LT, op_LE, op_GT, op_GE, op_IN, + op_REG, op_NRE, + op_STR_EQ, op_STR_NE, op_STR_LT, op_STR_LE, op_STR_GT, op_STR_GE, + op_Concat, + op_Digit, op_String, op_Regex, op_RegexBackref, + op_Var, + op_ListElement, + /* + * call external functions/operators. + * The info node contains the function pointer and some function specific + * info. + * For Binary operators, the Call node links to the Info node and the + * Args node, which in turn links to the left and right operand. + * For all other variants, the Call node links to the Info node and the + * argument. + */ + op_UnaryOpCall, op_UnaryOpInfo, + op_BinaryOpCall, op_BinaryOpInfo, op_BinaryOpArgs, + op_StringFuncCall, op_StringFuncInfo, + op_ListFuncCall, op_ListFuncInfo +} ap_expr_node_op_e; + +/** The basic parse tree node */ +struct ap_expr_node { + ap_expr_node_op_e node_op; + const void *node_arg1; + const void *node_arg2; +}; + +/** The context used by scanner and parser */ +typedef struct { + /* internal state of the scanner */ + const char *inputbuf; + int inputlen; + const char *inputptr; + void *scanner; + char *scan_ptr; + char scan_buf[MAX_STRING_LEN]; + char scan_del; + int at_start; + + /* pools for result and temporary usage */ + apr_pool_t *pool; + apr_pool_t *ptemp; + + /* The created parse tree */ + ap_expr_t *expr; + + const char *error; + const char *error2; + unsigned flags; + + /* + * The function to use to lookup provider functions for variables + * and funtctions + */ + ap_expr_lookup_fn_t *lookup_fn; +} ap_expr_parse_ctx_t; + +/* flex/bison functions */ +int ap_expr_yyparse(ap_expr_parse_ctx_t *context); +void ap_expr_yyerror(ap_expr_parse_ctx_t *context, const char *err); +int ap_expr_yylex_init(void **scanner); +int ap_expr_yylex_destroy(void *scanner); +void ap_expr_yyset_extra(ap_expr_parse_ctx_t *context, void *scanner); + +/* create a parse tree node */ +ap_expr_t *ap_expr_make(ap_expr_node_op_e op, const void *arg1, + const void *arg2, ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the string-returning function 'name' */ +ap_expr_t *ap_expr_str_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the list-returning function 'name' */ +ap_expr_t *ap_expr_list_func_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the variable 'name' */ +ap_expr_t *ap_expr_var_make(const char *name, ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the unary operator 'name' */ +ap_expr_t *ap_expr_unary_op_make(const char *name, const ap_expr_t *arg, + ap_expr_parse_ctx_t *ctx); +/* create parse tree node for the binary operator 'name' */ +ap_expr_t *ap_expr_binary_op_make(const char *name, const ap_expr_t *arg1, + const ap_expr_t *arg2, + ap_expr_parse_ctx_t *ctx); + + +#endif /* __AP_EXPR_PRIVATE_H__ */ +/** @} */ + diff --git a/server/util_expr_scan.c b/server/util_expr_scan.c new file mode 100644 index 00000000..ba759f22 --- /dev/null +++ b/server/util_expr_scan.c @@ -0,0 +1,2669 @@ +#line 2 "util_expr_scan.c" + +#line 4 "util_expr_scan.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE ap_expr_yyrestart(yyin ,yyscanner ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via ap_expr_yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void ap_expr_yyrestart (FILE *input_file ,yyscan_t yyscanner ); +void ap_expr_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +YY_BUFFER_STATE ap_expr_yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); +void ap_expr_yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ap_expr_yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ap_expr_yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +void ap_expr_yypop_buffer_state (yyscan_t yyscanner ); + +static void ap_expr_yyensure_buffer_stack (yyscan_t yyscanner ); +static void ap_expr_yy_load_buffer_state (yyscan_t yyscanner ); +static void ap_expr_yy_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); + +#define YY_FLUSH_BUFFER ap_expr_yy_flush_buffer(YY_CURRENT_BUFFER ,yyscanner) + +YY_BUFFER_STATE ap_expr_yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); +YY_BUFFER_STATE ap_expr_yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); +YY_BUFFER_STATE ap_expr_yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); + +void *ap_expr_yyalloc (yy_size_t ,yyscan_t yyscanner ); +void *ap_expr_yyrealloc (void *,yy_size_t ,yyscan_t yyscanner ); +void ap_expr_yyfree (void * ,yyscan_t yyscanner ); + +#define yy_new_buffer ap_expr_yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + ap_expr_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + ap_expr_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define ap_expr_yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); +static int yy_get_next_buffer (yyscan_t yyscanner ); +static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (size_t) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 67 +#define YY_END_OF_BUFFER 68 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[124] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 68, 66, 1, 43, 2, 66, 66, 66, + 65, 66, 44, 26, 63, 32, 30, 34, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 66, + 14, 4, 3, 17, 17, 67, 17, 23, 4, 22, + 20, 21, 67, 16, 16, 24, 27, 29, 28, 1, + 31, 37, 19, 18, 39, 63, 59, 59, 59, 59, + 59, 59, 33, 30, 36, 35, 64, 64, 57, 64, + 55, 54, 58, 53, 52, 25, 25, 56, 64, 40, + 64, 41, 14, 13, 15, 12, 5, 6, 10, 11, + + 7, 8, 9, 20, 60, 46, 48, 50, 45, 49, + 51, 47, 38, 64, 42, 64, 5, 6, 64, 61, + 5, 62, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 5, 6, 7, 8, 9, 5, 10, + 10, 1, 1, 11, 12, 13, 14, 15, 15, 15, + 15, 15, 15, 15, 15, 16, 16, 17, 6, 18, + 19, 20, 6, 1, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 1, 22, 1, 6, 23, 1, 24, 25, 21, 26, + + 27, 28, 29, 21, 30, 21, 21, 31, 32, 33, + 34, 21, 35, 36, 37, 38, 39, 21, 21, 21, + 21, 21, 40, 41, 42, 43, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[44] = + { 0, + 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, + 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, + 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, + 1, 2, 1 + } ; + +static yyconst flex_int16_t yy_base[133] = + { 0, + 0, 0, 41, 47, 89, 0, 130, 136, 0, 0, + 147, 146, 175, 275, 54, 28, 275, 43, 134, 164, + 275, 164, 275, 275, 45, 152, 32, 151, 0, 136, + 133, 143, 26, 133, 35, 194, 38, 129, 128, 122, + 0, 275, 275, 51, 122, 221, 275, 275, 275, 275, + 0, 275, 275, 61, 121, 275, 275, 275, 275, 76, + 275, 275, 275, 275, 275, 65, 0, 125, 47, 126, + 107, 130, 275, 275, 275, 275, 0, 130, 0, 124, + 0, 0, 0, 0, 0, 275, 0, 0, 104, 0, + 101, 275, 0, 275, 275, 275, 71, 131, 275, 275, + + 275, 275, 275, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 99, 0, 61, 133, 135, 57, 0, + 138, 0, 275, 259, 262, 265, 79, 67, 268, 271, + 65, 42 + } ; + +static yyconst flex_int16_t yy_def[133] = + { 0, + 123, 1, 124, 124, 123, 5, 124, 124, 125, 125, + 126, 126, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 127, 123, 123, 123, 123, 123, 123, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 123, + 129, 123, 123, 123, 123, 130, 123, 123, 123, 123, + 131, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 132, 132, 132, 132, + 132, 132, 123, 123, 123, 123, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 123, 128, 128, 128, 128, + 128, 123, 129, 123, 123, 123, 123, 123, 123, 123, + + 123, 123, 123, 131, 132, 132, 132, 132, 132, 132, + 132, 132, 128, 128, 128, 128, 123, 123, 128, 128, + 123, 128, 0, 123, 123, 123, 123, 123, 123, 123, + 123, 123 + } ; + +static yyconst flex_int16_t yy_nxt[319] = + { 0, + 14, 15, 15, 16, 17, 14, 18, 19, 20, 21, + 21, 22, 23, 24, 25, 25, 21, 26, 27, 28, + 29, 14, 14, 30, 29, 29, 31, 32, 33, 34, + 35, 36, 37, 38, 29, 29, 29, 39, 29, 21, + 40, 21, 14, 42, 105, 43, 61, 44, 45, 42, + 74, 43, 81, 44, 45, 60, 60, 63, 63, 66, + 66, 84, 46, 82, 88, 94, 94, 104, 46, 77, + 62, 89, 85, 107, 75, 94, 94, 60, 60, 66, + 66, 67, 47, 122, 108, 117, 118, 120, 47, 48, + 48, 49, 48, 48, 48, 48, 48, 48, 48, 48, + + 48, 48, 48, 48, 48, 50, 48, 48, 48, 51, + 48, 48, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 48, 48, + 52, 48, 42, 110, 53, 119, 54, 55, 42, 116, + 53, 115, 54, 55, 111, 118, 118, 121, 118, 118, + 118, 46, 118, 118, 114, 113, 112, 46, 109, 106, + 95, 95, 92, 91, 90, 83, 80, 79, 78, 76, + 73, 56, 65, 64, 123, 59, 59, 56, 66, 66, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 68, 123, 69, 70, 71, 123, 72, 86, 86, 86, + + 86, 86, 123, 123, 86, 86, 86, 86, 123, 123, + 86, 123, 123, 123, 123, 123, 87, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 86, 97, 98, 123, 123, 123, + 123, 123, 123, 123, 123, 99, 123, 123, 100, 123, + 123, 123, 123, 101, 123, 123, 102, 123, 103, 41, + 41, 41, 57, 57, 57, 58, 58, 58, 93, 123, + 93, 96, 96, 96, 13, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123 + } ; + +static yyconst flex_int16_t yy_chk[319] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 132, 3, 16, 3, 3, 4, + 27, 4, 33, 4, 4, 15, 15, 18, 18, 25, + 25, 35, 3, 33, 37, 44, 44, 131, 4, 128, + 16, 37, 35, 69, 27, 54, 54, 60, 60, 66, + 66, 127, 3, 119, 69, 97, 97, 116, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 7, 71, 7, 114, 7, 7, 8, 91, + 8, 89, 8, 8, 71, 98, 98, 117, 117, 118, + 118, 7, 121, 121, 80, 78, 72, 8, 70, 68, + 55, 45, 40, 39, 38, 34, 32, 31, 30, 28, + 26, 7, 20, 19, 13, 12, 11, 8, 22, 22, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 22, 0, 22, 22, 22, 0, 22, 36, 36, 36, + + 36, 36, 0, 0, 36, 36, 36, 36, 0, 0, + 36, 0, 0, 0, 0, 0, 36, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 36, 46, 46, 0, 0, 0, + 0, 0, 0, 0, 0, 46, 0, 0, 46, 0, + 0, 0, 0, 46, 0, 0, 46, 0, 46, 124, + 124, 124, 125, 125, 125, 126, 126, 126, 129, 0, + 129, 130, 130, 130, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + + 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "util_expr_scan.l" +/* 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. + */ +/* + * ap_expr_scan.l, based on ssl_expr_scan.l from mod_ssl + */ +/* _________________________________________________________________ +** +** Expression Scanner +** _________________________________________________________________ +*/ +#define YY_NO_INPUT 1 + + + + +#line 43 "util_expr_scan.l" +#include "util_expr_private.h" +#include "util_expr_parse.h" + +#undef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ +{ \ + if ((result = MIN(max_size, yyextra->inputbuf \ + + yyextra->inputlen \ + - yyextra->inputptr)) <= 0) \ + { \ + result = YY_NULL; \ + } \ + else { \ + memcpy(buf, yyextra->inputptr, result); \ + yyextra->inputptr += result; \ + } \ +} + +#define YY_EXTRA_TYPE ap_expr_parse_ctx_t* + +#define PERROR(msg) do { yyextra->error2 = msg ; return T_ERROR; } while (0) + +#define str_ptr (yyextra->scan_ptr) +#define str_buf (yyextra->scan_buf) +#define str_del (yyextra->scan_del) + +#define STR_APPEND(c) do { \ + *str_ptr++ = (c); \ + if (str_ptr >= str_buf + sizeof(str_buf)) \ + PERROR("String too long"); \ + } while (0) + +#line 615 "util_expr_scan.c" + +#define INITIAL 0 +#define str 1 +#define var 2 +#define vararg 3 +#define regex 4 +#define regex_flags 5 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals (yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + +int ap_expr_yylex_init (yyscan_t* scanner); + +int ap_expr_yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int ap_expr_yylex_destroy (yyscan_t yyscanner ); + +int ap_expr_yyget_debug (yyscan_t yyscanner ); + +void ap_expr_yyset_debug (int debug_flag ,yyscan_t yyscanner ); + +YY_EXTRA_TYPE ap_expr_yyget_extra (yyscan_t yyscanner ); + +void ap_expr_yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); + +FILE *ap_expr_yyget_in (yyscan_t yyscanner ); + +void ap_expr_yyset_in (FILE * in_str ,yyscan_t yyscanner ); + +FILE *ap_expr_yyget_out (yyscan_t yyscanner ); + +void ap_expr_yyset_out (FILE * out_str ,yyscan_t yyscanner ); + +int ap_expr_yyget_leng (yyscan_t yyscanner ); + +char *ap_expr_yyget_text (yyscan_t yyscanner ); + +int ap_expr_yyget_lineno (yyscan_t yyscanner ); + +void ap_expr_yyset_lineno (int line_number ,yyscan_t yyscanner ); + +YYSTYPE * ap_expr_yyget_lval (yyscan_t yyscanner ); + +void ap_expr_yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int ap_expr_yywrap (yyscan_t yyscanner ); +#else +extern int ap_expr_yywrap (yyscan_t yyscanner ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (yyscan_t yyscanner ); +#else +static int input (yyscan_t yyscanner ); +#endif + +#endif + + static void yy_push_state (int new_state ,yyscan_t yyscanner); + + static void yy_pop_state (yyscan_t yyscanner ); + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int ap_expr_yylex \ + (YYSTYPE * yylval_param ,yyscan_t yyscanner); + +#define YY_DECL int ap_expr_yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + +#line 78 "util_expr_scan.l" + + + char regex_buf[MAX_STRING_LEN]; + char *regex_ptr = NULL; + char regex_del = '\0'; + + + /* + * Set initial state for string expressions + */ + if (yyextra->at_start) { + yyextra->at_start = 0; + if (yyextra->flags & AP_EXPR_FLAG_STRING_RESULT) { + BEGIN(str); + return T_EXPR_STRING; + } + else { + return T_EXPR_BOOL; + } + } + + + /* + * Whitespaces + */ +#line 886 "util_expr_scan.c" + + yylval = yylval_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + ap_expr_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ap_expr_yy_load_buffer_state(yyscanner ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 124 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_current_state != 123 ); + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 103 "util_expr_scan.l" +{ + /* NOP */ +} + YY_BREAK +/* + * strings ("..." and '...') + */ +case 2: +YY_RULE_SETUP +#line 110 "util_expr_scan.l" +{ + str_ptr = str_buf; + str_del = yytext[0]; + BEGIN(str); + return T_STR_BEGIN; +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 116 "util_expr_scan.l" +{ + if (yytext[0] == str_del) { + if (YY_START == var) { + PERROR("Unterminated variable in string"); + } + else if (str_ptr == str_buf) { + BEGIN(INITIAL); + return T_STR_END; + } + else { + /* return what we have so far and scan delimiter again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + } + else { + STR_APPEND(yytext[0]); + } +} + YY_BREAK +case 4: +/* rule 4 can match eol */ +YY_RULE_SETUP +#line 138 "util_expr_scan.l" +{ + PERROR("Unterminated string or variable"); +} + YY_BREAK +case YY_STATE_EOF(var): +case YY_STATE_EOF(vararg): +#line 141 "util_expr_scan.l" +{ + PERROR("Unterminated string or variable"); +} + YY_BREAK +case YY_STATE_EOF(str): +#line 144 "util_expr_scan.l" +{ + if (!(yyextra->flags & AP_EXPR_FLAG_STRING_RESULT)) { + PERROR("Unterminated string or variable"); + } + else { + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + BEGIN(INITIAL); + return T_STRING; + } +} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 157 "util_expr_scan.l" +{ + int result; + + (void)sscanf(yytext+1, "%o", &result); + if (result > 0xff) { + PERROR("Escape sequence out of bound"); + } + else { + STR_APPEND(result); + } +} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 168 "util_expr_scan.l" +{ + PERROR("Bad escape sequence"); +} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 171 "util_expr_scan.l" +{ STR_APPEND('\n'); } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 172 "util_expr_scan.l" +{ STR_APPEND('\r'); } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 173 "util_expr_scan.l" +{ STR_APPEND('\t'); } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 174 "util_expr_scan.l" +{ STR_APPEND('\b'); } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 175 "util_expr_scan.l" +{ STR_APPEND('\f'); } + YY_BREAK +case 12: +/* rule 12 can match eol */ +YY_RULE_SETUP +#line 176 "util_expr_scan.l" +{ STR_APPEND(yytext[1]); } + YY_BREAK +/* regexp backref inside string/arg */ +case 13: +YY_RULE_SETUP +#line 179 "util_expr_scan.l" +{ + if (str_ptr != str_buf) { + /* return what we have so far and scan '$x' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; + } +} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 194 "util_expr_scan.l" +{ + char *cp = yytext; + while (*cp != '\0') { + STR_APPEND(*cp); + cp++; + } +} + YY_BREAK +/* variable inside string/arg */ +case 15: +YY_RULE_SETUP +#line 203 "util_expr_scan.l" +{ + if (str_ptr != str_buf) { + /* return what we have so far and scan '%{' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + else { + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; + } +} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 218 "util_expr_scan.l" +{ + STR_APPEND(yytext[0]); +} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 222 "util_expr_scan.l" +{ + STR_APPEND(yytext[0]); +} + YY_BREAK +case 18: +YY_RULE_SETUP +#line 226 "util_expr_scan.l" +{ + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; +} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 231 "util_expr_scan.l" +{ + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; +} + YY_BREAK +/* + * fixed name variable expansion %{XXX} and function call in %{func:arg} syntax + */ +case 20: +YY_RULE_SETUP +#line 239 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 244 "util_expr_scan.l" +{ + yy_pop_state(yyscanner); + return T_VAR_END; +} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 249 "util_expr_scan.l" +{ + BEGIN(vararg); + return yytext[0]; +} + YY_BREAK +case 23: +/* rule 23 can match eol */ +YY_RULE_SETUP +#line 254 "util_expr_scan.l" +{ + char *msg = apr_psprintf(yyextra->pool, + "Invalid character in variable name '%c'", yytext[0]); + PERROR(msg); +} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 260 "util_expr_scan.l" +{ + if (str_ptr != str_buf) { + /* return what we have so far and scan '}' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yy_pop_state(yyscanner); + return T_VAR_END; + } +} + YY_BREAK +/* + * Regular Expression + */ +case 25: +YY_RULE_SETUP +#line 278 "util_expr_scan.l" +{ + regex_del = yytext[1]; + regex_ptr = regex_buf; + BEGIN(regex); +} + YY_BREAK +case 26: +YY_RULE_SETUP +#line 283 "util_expr_scan.l" +{ + regex_del = yytext[0]; + regex_ptr = regex_buf; + BEGIN(regex); +} + YY_BREAK +case 27: +/* rule 27 can match eol */ +YY_RULE_SETUP +#line 288 "util_expr_scan.l" +{ + if (yytext[0] == regex_del) { + *regex_ptr = '\0'; + BEGIN(regex_flags); + } + else { + *regex_ptr++ = yytext[0]; + if (regex_ptr >= regex_buf + sizeof(regex_buf)) + PERROR("Regexp too long"); + } +} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 299 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX_I; +} + YY_BREAK +case 29: +/* rule 29 can match eol */ +YY_RULE_SETUP +#line 304 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + yyless(0); + BEGIN(INITIAL); + return T_REGEX; +} + YY_BREAK +case YY_STATE_EOF(regex_flags): +#line 310 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX; +} + YY_BREAK +/* + * Operators + */ +case 30: +YY_RULE_SETUP +#line 319 "util_expr_scan.l" +{ return T_OP_STR_EQ; } + YY_BREAK +case 31: +YY_RULE_SETUP +#line 320 "util_expr_scan.l" +{ return T_OP_STR_NE; } + YY_BREAK +case 32: +YY_RULE_SETUP +#line 321 "util_expr_scan.l" +{ return T_OP_STR_LT; } + YY_BREAK +case 33: +YY_RULE_SETUP +#line 322 "util_expr_scan.l" +{ return T_OP_STR_LE; } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 323 "util_expr_scan.l" +{ return T_OP_STR_GT; } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 324 "util_expr_scan.l" +{ return T_OP_STR_GE; } + YY_BREAK +case 36: +YY_RULE_SETUP +#line 325 "util_expr_scan.l" +{ return T_OP_REG; } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 326 "util_expr_scan.l" +{ return T_OP_NRE; } + YY_BREAK +case 38: +YY_RULE_SETUP +#line 327 "util_expr_scan.l" +{ return T_OP_AND; } + YY_BREAK +case 39: +YY_RULE_SETUP +#line 328 "util_expr_scan.l" +{ return T_OP_AND; } + YY_BREAK +case 40: +YY_RULE_SETUP +#line 329 "util_expr_scan.l" +{ return T_OP_OR; } + YY_BREAK +case 41: +YY_RULE_SETUP +#line 330 "util_expr_scan.l" +{ return T_OP_OR; } + YY_BREAK +case 42: +YY_RULE_SETUP +#line 331 "util_expr_scan.l" +{ return T_OP_NOT; } + YY_BREAK +case 43: +YY_RULE_SETUP +#line 332 "util_expr_scan.l" +{ return T_OP_NOT; } + YY_BREAK +case 44: +YY_RULE_SETUP +#line 333 "util_expr_scan.l" +{ return T_OP_CONCAT; } + YY_BREAK +case 45: +YY_RULE_SETUP +#line 334 "util_expr_scan.l" +{ return T_OP_IN; } + YY_BREAK +case 46: +YY_RULE_SETUP +#line 335 "util_expr_scan.l" +{ return T_OP_EQ; } + YY_BREAK +case 47: +YY_RULE_SETUP +#line 336 "util_expr_scan.l" +{ return T_OP_NE; } + YY_BREAK +case 48: +YY_RULE_SETUP +#line 337 "util_expr_scan.l" +{ return T_OP_GE; } + YY_BREAK +case 49: +YY_RULE_SETUP +#line 338 "util_expr_scan.l" +{ return T_OP_LE; } + YY_BREAK +case 50: +YY_RULE_SETUP +#line 339 "util_expr_scan.l" +{ return T_OP_GT; } + YY_BREAK +case 51: +YY_RULE_SETUP +#line 340 "util_expr_scan.l" +{ return T_OP_LT; } + YY_BREAK +/* for compatibility with ssl_expr */ +case 52: +YY_RULE_SETUP +#line 343 "util_expr_scan.l" +{ return T_OP_LT; } + YY_BREAK +case 53: +YY_RULE_SETUP +#line 344 "util_expr_scan.l" +{ return T_OP_LE; } + YY_BREAK +case 54: +YY_RULE_SETUP +#line 345 "util_expr_scan.l" +{ return T_OP_GT; } + YY_BREAK +case 55: +YY_RULE_SETUP +#line 346 "util_expr_scan.l" +{ return T_OP_GE; } + YY_BREAK +case 56: +YY_RULE_SETUP +#line 347 "util_expr_scan.l" +{ return T_OP_NE; } + YY_BREAK +case 57: +YY_RULE_SETUP +#line 348 "util_expr_scan.l" +{ return T_OP_EQ; } + YY_BREAK +case 58: +YY_RULE_SETUP +#line 349 "util_expr_scan.l" +{ return T_OP_IN; } + YY_BREAK +case 59: +YY_RULE_SETUP +#line 351 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_UNARY; +} + YY_BREAK +case 60: +YY_RULE_SETUP +#line 356 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_BINARY; +} + YY_BREAK +/* + * Specials + */ +case 61: +YY_RULE_SETUP +#line 364 "util_expr_scan.l" +{ return T_TRUE; } + YY_BREAK +case 62: +YY_RULE_SETUP +#line 365 "util_expr_scan.l" +{ return T_FALSE; } + YY_BREAK +/* + * Digits + */ +case 63: +YY_RULE_SETUP +#line 370 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_DIGIT; +} + YY_BREAK +/* + * Identifiers + */ +case 64: +YY_RULE_SETUP +#line 378 "util_expr_scan.l" +{ + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + YY_BREAK +/* + * These are parts of the grammar and are returned as is + */ +case 65: +YY_RULE_SETUP +#line 386 "util_expr_scan.l" +{ + return yytext[0]; +} + YY_BREAK +/* + * Anything else is an error + */ +case 66: +/* rule 66 can match eol */ +YY_RULE_SETUP +#line 393 "util_expr_scan.l" +{ + char *msg = apr_psprintf(yyextra->pool, "Parse error near '%c'", yytext[0]); + PERROR(msg); +} + YY_BREAK +case 67: +YY_RULE_SETUP +#line 398 "util_expr_scan.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1523 "util_expr_scan.c" +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(regex): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * ap_expr_yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( ap_expr_yywrap(yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of ap_expr_yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = yyg->yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + ap_expr_yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + ap_expr_yyrestart(yyin ,yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) ap_expr_yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 124 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + register int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + register char *yy_cp = yyg->yy_c_buf_p; + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 124 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 123); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + ap_expr_yyrestart(yyin ,yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( ap_expr_yywrap(yyscanner ) ) + return EOF; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void ap_expr_yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + ap_expr_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ap_expr_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ap_expr_yy_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); + ap_expr_yy_load_buffer_state(yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void ap_expr_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * ap_expr_yypop_buffer_state(); + * ap_expr_yypush_buffer_state(new_buffer); + */ + ap_expr_yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + ap_expr_yy_load_buffer_state(yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (ap_expr_yywrap()) processing, but the only time this flag + * is looked at is after ap_expr_yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void ap_expr_yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE ap_expr_yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) ap_expr_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) ap_expr_yyalloc(b->yy_buf_size + 2 ,yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + ap_expr_yy_init_buffer(b,file ,yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with ap_expr_yy_create_buffer() + * @param yyscanner The scanner object. + */ + void ap_expr_yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + ap_expr_yyfree((void *) b->yy_ch_buf ,yyscanner ); + + ap_expr_yyfree((void *) b ,yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a ap_expr_yyrestart() or at EOF. + */ + static void ap_expr_yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + ap_expr_yy_flush_buffer(b ,yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then ap_expr_yy_init_buffer was _probably_ + * called from ap_expr_yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void ap_expr_yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + ap_expr_yy_load_buffer_state(yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void ap_expr_yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + ap_expr_yyensure_buffer_stack(yyscanner); + + /* This block is copied from ap_expr_yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from ap_expr_yy_switch_to_buffer. */ + ap_expr_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void ap_expr_yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + ap_expr_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + ap_expr_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void ap_expr_yyensure_buffer_stack (yyscan_t yyscanner) +{ + int num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ap_expr_yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ap_expr_yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ap_expr_yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) ap_expr_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + ap_expr_yy_switch_to_buffer(b ,yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to ap_expr_yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * ap_expr_yy_scan_bytes() instead. + */ +YY_BUFFER_STATE ap_expr_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner) +{ + + return ap_expr_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to ap_expr_yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ap_expr_yy_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) ap_expr_yyalloc(n ,yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in ap_expr_yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = ap_expr_yy_scan_buffer(buf,n ,yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in ap_expr_yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + + static void yy_push_state (int new_state , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( yyg->yy_start_stack_ptr >= yyg->yy_start_stack_depth ) + { + yy_size_t new_size; + + yyg->yy_start_stack_depth += YY_START_STACK_INCR; + new_size = yyg->yy_start_stack_depth * sizeof( int ); + + if ( ! yyg->yy_start_stack ) + yyg->yy_start_stack = (int *) ap_expr_yyalloc(new_size ,yyscanner ); + + else + yyg->yy_start_stack = (int *) ap_expr_yyrealloc((void *) yyg->yy_start_stack,new_size ,yyscanner ); + + if ( ! yyg->yy_start_stack ) + YY_FATAL_ERROR( "out of memory expanding start-condition stack" ); + } + + yyg->yy_start_stack[yyg->yy_start_stack_ptr++] = YY_START; + + BEGIN(new_state); +} + + static void yy_pop_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( --yyg->yy_start_stack_ptr < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN(yyg->yy_start_stack[yyg->yy_start_stack_ptr]); +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE ap_expr_yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int ap_expr_yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *ap_expr_yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *ap_expr_yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int ap_expr_yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *ap_expr_yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void ap_expr_yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param line_number + * @param yyscanner The scanner object. + */ +void ap_expr_yyset_lineno (int line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "ap_expr_yyset_lineno called with no buffer" , yyscanner); + + yylineno = line_number; +} + +/** Set the current column. + * @param line_number + * @param yyscanner The scanner object. + */ + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * @param yyscanner The scanner object. + * @see ap_expr_yy_switch_to_buffer + */ +void ap_expr_yyset_in (FILE * in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = in_str ; +} + +void ap_expr_yyset_out (FILE * out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = out_str ; +} + +int ap_expr_yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void ap_expr_yyset_debug (int bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * ap_expr_yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void ap_expr_yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +/* User-visible API */ + +/* ap_expr_yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ + +int ap_expr_yylex_init(yyscan_t* ptr_yy_globals) + +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) ap_expr_yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* ap_expr_yylex_init_extra has the same functionality as ap_expr_yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to ap_expr_yyalloc in + * the yyextra field. + */ + +int ap_expr_yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals ) + +{ + struct yyguts_t dummy_yyguts; + + ap_expr_yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) ap_expr_yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + ap_expr_yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from ap_expr_yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = 0; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = (char *) 0; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * ap_expr_yylex_init() + */ + return 0; +} + +/* ap_expr_yylex_destroy is for both reentrant and non-reentrant scanners. */ +int ap_expr_yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + ap_expr_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + ap_expr_yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + ap_expr_yyfree(yyg->yy_buffer_stack ,yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + ap_expr_yyfree(yyg->yy_start_stack ,yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * ap_expr_yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + ap_expr_yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *ap_expr_yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + return (void *) malloc( size ); +} + +void *ap_expr_yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void ap_expr_yyfree (void * ptr , yyscan_t yyscanner) +{ + free( (char *) ptr ); /* see ap_expr_yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 398 "util_expr_scan.l" + + + + + diff --git a/server/util_expr_scan.l b/server/util_expr_scan.l new file mode 100644 index 00000000..513236a9 --- /dev/null +++ b/server/util_expr_scan.l @@ -0,0 +1,400 @@ +/* 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. + */ + +/* + * ap_expr_scan.l, based on ssl_expr_scan.l from mod_ssl + */ + +/* _________________________________________________________________ +** +** Expression Scanner +** _________________________________________________________________ +*/ + +%pointer +%option batch +%option never-interactive +%option nodefault +%option noyywrap +%option reentrant +%option bison-bridge +%option warn +%option noinput nounput noyy_top_state +%option stack +%x str +%x var +%x vararg +%x regex regex_flags + +%{ +#include "util_expr_private.h" +#include "util_expr_parse.h" + +#undef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ +{ \ + if ((result = MIN(max_size, yyextra->inputbuf \ + + yyextra->inputlen \ + - yyextra->inputptr)) <= 0) \ + { \ + result = YY_NULL; \ + } \ + else { \ + memcpy(buf, yyextra->inputptr, result); \ + yyextra->inputptr += result; \ + } \ +} + +#define YY_EXTRA_TYPE ap_expr_parse_ctx_t* + +#define PERROR(msg) do { yyextra->error2 = msg ; return T_ERROR; } while (0) + +#define str_ptr (yyextra->scan_ptr) +#define str_buf (yyextra->scan_buf) +#define str_del (yyextra->scan_del) + +#define STR_APPEND(c) do { \ + *str_ptr++ = (c); \ + if (str_ptr >= str_buf + sizeof(str_buf)) \ + PERROR("String too long"); \ + } while (0) + +%} + + +%% + + char regex_buf[MAX_STRING_LEN]; + char *regex_ptr = NULL; + char regex_del = '\0'; + +%{ + /* + * Set initial state for string expressions + */ + if (yyextra->at_start) { + yyextra->at_start = 0; + if (yyextra->flags & AP_EXPR_FLAG_STRING_RESULT) { + BEGIN(str); + return T_EXPR_STRING; + } + else { + return T_EXPR_BOOL; + } + } +%} + + /* + * Whitespaces + */ +[ \t\n]+ { + /* NOP */ +} + + /* + * strings ("..." and '...') + */ +["'] { + str_ptr = str_buf; + str_del = yytext[0]; + BEGIN(str); + return T_STR_BEGIN; +} +<str>["'] { + if (yytext[0] == str_del) { + if (YY_START == var) { + PERROR("Unterminated variable in string"); + } + else if (str_ptr == str_buf) { + BEGIN(INITIAL); + return T_STR_END; + } + else { + /* return what we have so far and scan delimiter again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + } + else { + STR_APPEND(yytext[0]); + } +} +<str,var,vararg>\n { + PERROR("Unterminated string or variable"); +} +<var,vararg><<EOF>> { + PERROR("Unterminated string or variable"); +} +<str><<EOF>> { + if (!(yyextra->flags & AP_EXPR_FLAG_STRING_RESULT)) { + PERROR("Unterminated string or variable"); + } + else { + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + BEGIN(INITIAL); + return T_STRING; + } +} + +<str,vararg>\\[0-7]{1,3} { + int result; + + (void)sscanf(yytext+1, "%o", &result); + if (result > 0xff) { + PERROR("Escape sequence out of bound"); + } + else { + STR_APPEND(result); + } +} +<str,vararg>\\[0-9]+ { + PERROR("Bad escape sequence"); +} +<str,vararg>\\n { STR_APPEND('\n'); } +<str,vararg>\\r { STR_APPEND('\r'); } +<str,vararg>\\t { STR_APPEND('\t'); } +<str,vararg>\\b { STR_APPEND('\b'); } +<str,vararg>\\f { STR_APPEND('\f'); } +<str,vararg>\\(.|\n) { STR_APPEND(yytext[1]); } + + /* regexp backref inside string/arg */ +<str,vararg>[$][0-9] { + if (str_ptr != str_buf) { + /* return what we have so far and scan '$x' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; + } +} + +<str,vararg>[^\\\n"'%}$]+ { + char *cp = yytext; + while (*cp != '\0') { + STR_APPEND(*cp); + cp++; + } +} + + /* variable inside string/arg */ +<str,vararg>%\{ { + if (str_ptr != str_buf) { + /* return what we have so far and scan '%{' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + yyless(0); + str_ptr = str_buf; + return T_STRING; + } + else { + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; + } +} + +<vararg>[%$] { + STR_APPEND(yytext[0]); +} + +<str>[%}$] { + STR_APPEND(yytext[0]); +} + +%\{ { + yy_push_state(var, yyscanner); + return T_VAR_BEGIN; +} + +[$][0-9] { + yylval->num = yytext[1] - '0'; + return T_REGEX_BACKREF; +} + + /* + * fixed name variable expansion %{XXX} and function call in %{func:arg} syntax + */ +<var>[a-zA-Z][a-zA-Z0-9_]* { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + +<var>\} { + yy_pop_state(yyscanner); + return T_VAR_END; +} + +<var>: { + BEGIN(vararg); + return yytext[0]; +} + +<var>.|\n { + char *msg = apr_psprintf(yyextra->pool, + "Invalid character in variable name '%c'", yytext[0]); + PERROR(msg); +} + +<vararg>\} { + if (str_ptr != str_buf) { + /* return what we have so far and scan '}' again */ + *str_ptr = '\0'; + yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf); + str_ptr = str_buf; + yyless(0); + return T_STRING; + } + else { + yy_pop_state(yyscanner); + return T_VAR_END; + } +} + + /* + * Regular Expression + */ +"m"[/#$%^,;:_\?\|\^\-\!\.\'\"] { + regex_del = yytext[1]; + regex_ptr = regex_buf; + BEGIN(regex); +} +"/" { + regex_del = yytext[0]; + regex_ptr = regex_buf; + BEGIN(regex); +} +<regex>.|\n { + if (yytext[0] == regex_del) { + *regex_ptr = '\0'; + BEGIN(regex_flags); + } + else { + *regex_ptr++ = yytext[0]; + if (regex_ptr >= regex_buf + sizeof(regex_buf)) + PERROR("Regexp too long"); + } +} +<regex_flags>i { + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX_I; +} +<regex_flags>.|\n { + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + yyless(0); + BEGIN(INITIAL); + return T_REGEX; +} +<regex_flags><<EOF>> { + yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf); + BEGIN(INITIAL); + return T_REGEX; +} + + /* + * Operators + */ +==? { return T_OP_STR_EQ; } +"!=" { return T_OP_STR_NE; } +"<" { return T_OP_STR_LT; } +"<=" { return T_OP_STR_LE; } +">" { return T_OP_STR_GT; } +">=" { return T_OP_STR_GE; } +"=~" { return T_OP_REG; } +"!~" { return T_OP_NRE; } +"and" { return T_OP_AND; } +"&&" { return T_OP_AND; } +"or" { return T_OP_OR; } +"||" { return T_OP_OR; } +"not" { return T_OP_NOT; } +"!" { return T_OP_NOT; } +"." { return T_OP_CONCAT; } +"-in" { return T_OP_IN; } +"-eq" { return T_OP_EQ; } +"-ne" { return T_OP_NE; } +"-ge" { return T_OP_GE; } +"-le" { return T_OP_LE; } +"-gt" { return T_OP_GT; } +"-lt" { return T_OP_LT; } + + /* for compatibility with ssl_expr */ +"lt" { return T_OP_LT; } +"le" { return T_OP_LE; } +"gt" { return T_OP_GT; } +"ge" { return T_OP_GE; } +"ne" { return T_OP_NE; } +"eq" { return T_OP_EQ; } +"in" { return T_OP_IN; } + +"-"[a-zA-Z_] { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_UNARY; +} + +"-"[a-zA-Z_][a-zA-Z_0-9]+ { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1); + return T_OP_BINARY; +} + + /* + * Specials + */ +"true" { return T_TRUE; } +"false" { return T_FALSE; } + + /* + * Digits + */ +-?[0-9]+ { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_DIGIT; +} + + /* + * Identifiers + */ +[a-zA-Z][a-zA-Z0-9_]* { + yylval->cpVal = apr_pstrdup(yyextra->pool, yytext); + return T_ID; +} + + /* + * These are parts of the grammar and are returned as is + */ +[(){},:] { + return yytext[0]; +} + + /* + * Anything else is an error + */ +.|\n { + char *msg = apr_psprintf(yyextra->pool, "Parse error near '%c'", yytext[0]); + PERROR(msg); +} + +%% + + diff --git a/server/util_filter.c b/server/util_filter.c index b2e7b582..7121eced 100644 --- a/server/util_filter.c +++ b/server/util_filter.c @@ -21,6 +21,8 @@ #include "apr_strings.h" #include "httpd.h" +#include "http_config.h" +#include "http_core.h" #include "http_log.h" #include "util_filter.h" @@ -28,7 +30,7 @@ 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 */ +#include "ap_hooks.h" /* for apr_hook_global_pool */ /* ** This macro returns true/false if a given filter should be inserted BEFORE @@ -44,6 +46,10 @@ * filter names to filters */ +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + typedef struct filter_trie_node filter_trie_node; typedef struct { @@ -248,15 +254,15 @@ AP_DECLARE(ap_filter_rec_t *) ap_register_input_filter(const char *name, ®istered_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) ; + filter_init, ftype, 0); } + AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter_protocol( const char *name, ap_out_filter_func filter_func, @@ -279,7 +285,7 @@ static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx, ap_filter_t **p_filters, ap_filter_t **c_filters) { - apr_pool_t* p = r ? r->pool : c->pool; + apr_pool_t *p = frec->ftype < AP_FTYPE_CONNECTION && r ? r->pool : c->pool; ap_filter_t *f = apr_palloc(p, sizeof(*f)); ap_filter_t **outf; @@ -288,8 +294,8 @@ static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx, outf = r_filters; } else { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, - "a content filter was added without a request: %s", frec->name); + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00080) + "a content filter was added without a request: %s", frec->name); return NULL; } } @@ -298,8 +304,8 @@ static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx, outf = p_filters; } else { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, - "a protocol filter was added without a request: %s", frec->name); + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00081) + "a protocol filter was added without a request: %s", frec->name); return NULL; } } @@ -309,7 +315,8 @@ static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx, f->frec = frec; f->ctx = ctx; - f->r = r; + /* f->r must always be NULL for connection filters */ + f->r = frec->ftype < AP_FTYPE_CONNECTION ? r : NULL; f->c = c; f->next = NULL; @@ -394,8 +401,8 @@ static ap_filter_t *add_any_filter(const char *name, void *ctx, } } - ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, - "an unknown filter was not added: %s", name); + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, r ? r->connection : c, APLOGNO(00082) + "an unknown filter was not added: %s", name); return NULL; } @@ -528,6 +535,41 @@ AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *next, return AP_NOBODY_WROTE; } +/* Pass the buckets to the next filter in the filter stack + * checking return status for filter errors. + * returns: OK if ap_pass_brigade returns APR_SUCCESS + * AP_FILTER_ERROR if filter error exists + * HTTP_INTERNAL_SERVER_ERROR for all other cases + * logged with optional errmsg + */ +AP_DECLARE(apr_status_t) ap_pass_brigade_fchk(request_rec *r, + apr_bucket_brigade *bb, + const char *fmt, + ...) +{ + apr_status_t rv; + + rv = ap_pass_brigade(r->output_filters, bb); + if (rv != APR_SUCCESS) { + if (rv != AP_FILTER_ERROR) { + if (!fmt) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00083) + "ap_pass_brigade returned %d", rv); + else { + va_list ap; + const char *res; + va_start(ap, fmt); + res = apr_pvsprintf(r->pool, fmt, ap); + va_end(ap); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "%s", res); + } + return HTTP_INTERNAL_SERVER_ERROR; + } + return AP_FILTER_ERROR; + } + return OK; +} + AP_DECLARE(apr_status_t) ap_save_brigade(ap_filter_t *f, apr_bucket_brigade **saveto, apr_bucket_brigade **b, apr_pool_t *p) diff --git a/server/util_mutex.c b/server/util_mutex.c new file mode 100644 index 00000000..0ff9483d --- /dev/null +++ b/server/util_mutex.c @@ -0,0 +1,560 @@ +/* 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_mutex.c: Useful functions for determining allowable + * mutexes and mutex settings + */ + + +#include "apr.h" +#include "apr_hash.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "ap_config.h" +#include "httpd.h" +#include "http_main.h" +#include "http_config.h" +#include "http_core.h" +#include "http_log.h" +#include "util_mutex.h" +#if AP_NEED_SET_MUTEX_PERMS +#include "unixd.h" +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* getpid() */ +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +AP_DECLARE(apr_status_t) ap_parse_mutex(const char *arg, apr_pool_t *pool, + apr_lockmech_e *mutexmech, + const char **mutexfile) +{ + /* Split arg into meth and file */ + char *meth = apr_pstrdup(pool, arg); + char *file = strchr(meth, ':'); + if (file) { + *(file++) = '\0'; + if (!*file) { + file = NULL; + } + } + + /* APR determines temporary filename unless overridden below, + * we presume file indicates an mutexfile is a file path + * unless the method sets mutexfile=file and NULLs file + */ + *mutexfile = NULL; + + if (!strcasecmp(meth, "none") || !strcasecmp(meth, "no")) { + return APR_ENOLOCK; + } + + /* NOTE: previously, 'yes' implied 'sem' */ + if (!strcasecmp(meth, "default") || !strcasecmp(meth, "yes")) { + *mutexmech = APR_LOCK_DEFAULT; + } +#if APR_HAS_FCNTL_SERIALIZE + else if (!strcasecmp(meth, "fcntl") || !strcasecmp(meth, "file")) { + *mutexmech = APR_LOCK_FCNTL; + } +#endif +#if APR_HAS_FLOCK_SERIALIZE + else if (!strcasecmp(meth, "flock") || !strcasecmp(meth, "file")) { + *mutexmech = APR_LOCK_FLOCK; + } +#endif +#if APR_HAS_POSIXSEM_SERIALIZE + else if (!strcasecmp(meth, "posixsem") || !strcasecmp(meth, "sem")) { + *mutexmech = APR_LOCK_POSIXSEM; + /* Posix/SysV semaphores aren't file based, use the literal name + * if provided and fall back on APR's default if not. Today, APR + * will ignore it, but once supported it has an absurdly short limit. + */ + if (file) { + *mutexfile = apr_pstrdup(pool, file); + + file = NULL; + } + } +#endif +#if APR_HAS_SYSVSEM_SERIALIZE + else if (!strcasecmp(meth, "sysvsem") || !strcasecmp(meth, "sem")) { + *mutexmech = APR_LOCK_SYSVSEM; + } +#endif +#if APR_HAS_PROC_PTHREAD_SERIALIZE + else if (!strcasecmp(meth, "pthread")) { + *mutexmech = APR_LOCK_PROC_PTHREAD; + } +#endif + else { + return APR_ENOTIMPL; + } + + /* Unless the method above assumed responsibility for setting up + * mutexfile and NULLing out file, presume it is a file we + * are looking to use + */ + if (file) { + *mutexfile = ap_server_root_relative(pool, file); + if (!*mutexfile) { + return APR_BADARG; + } + } + + return APR_SUCCESS; +} + +typedef struct { + apr_int32_t options; + int set; + int none; + int omit_pid; + apr_lockmech_e mech; + const char *dir; +} mutex_cfg_t; + +/* hash is created the first time a module calls ap_mutex_register(), + * rather than attempting to be the REALLY_REALLY_FIRST pre-config + * hook; it is cleaned up when the associated pool goes away; assume + * pconf is the pool passed to ap_mutex_register() + */ +static apr_hash_t *mxcfg_by_type; + +AP_DECLARE_NONSTD(void) ap_mutex_init(apr_pool_t *p) +{ + mutex_cfg_t *def; + + if (mxcfg_by_type) { + return; + } + + mxcfg_by_type = apr_hash_make(p); + apr_pool_cleanup_register(p, &mxcfg_by_type, ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + + /* initialize default mutex configuration */ + def = apr_pcalloc(p, sizeof *def); + def->mech = APR_LOCK_DEFAULT; + def->dir = DEFAULT_REL_RUNTIMEDIR; + apr_hash_set(mxcfg_by_type, "default", APR_HASH_KEY_STRING, def); +} + +AP_DECLARE_NONSTD(const char *)ap_set_mutex(cmd_parms *cmd, void *dummy, + const char *arg) +{ + apr_pool_t *p = cmd->pool; + const char **elt; + const char *mechdir; + int no_mutex = 0, omit_pid = 0; + apr_array_header_t *type_list; + apr_lockmech_e mech; + apr_status_t rv; + const char *mutexdir; + mutex_cfg_t *mxcfg; + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + + if (err != NULL) { + return err; + } + + mechdir = ap_getword_conf(cmd->pool, &arg); + if (*mechdir == '\0') { + return "Mutex requires at least a mechanism argument (" + AP_ALL_AVAILABLE_MUTEXES_STRING ")"; + } + + rv = ap_parse_mutex(mechdir, p, &mech, &mutexdir); + if (rv == APR_ENOTIMPL) { + return apr_pstrcat(p, "Invalid Mutex argument ", mechdir, + " (" AP_ALL_AVAILABLE_MUTEXES_STRING ")", NULL); + } + else if (rv == APR_BADARG + || (mutexdir && !ap_is_directory(p, mutexdir))) { + return apr_pstrcat(p, "Invalid Mutex directory in argument ", + mechdir, NULL); + } + else if (rv == APR_ENOLOCK) { /* "none" */ + no_mutex = 1; + } + + /* "OmitPID" can appear at the end of the list, so build a list of + * mutex type names while looking for "OmitPID" (anywhere) or the end + */ + type_list = apr_array_make(cmd->pool, 4, sizeof(const char *)); + while (*arg) { + const char *s = ap_getword_conf(cmd->pool, &arg); + + if (!strcasecmp(s, "omitpid")) { + omit_pid = 1; + } + else { + const char **new_type = (const char **)apr_array_push(type_list); + *new_type = s; + } + } + + if (apr_is_empty_array(type_list)) { /* no mutex type? assume "default" */ + const char **new_type = (const char **)apr_array_push(type_list); + *new_type = "default"; + } + + while ((elt = (const char **)apr_array_pop(type_list)) != NULL) { + const char *type = *elt; + mxcfg = apr_hash_get(mxcfg_by_type, type, APR_HASH_KEY_STRING); + if (!mxcfg) { + return apr_psprintf(p, "Mutex type %s is not valid", type); + } + + mxcfg->none = 0; /* in case that was the default */ + mxcfg->omit_pid = omit_pid; + + mxcfg->set = 1; + if (no_mutex) { + if (!(mxcfg->options & AP_MUTEX_ALLOW_NONE)) { + return apr_psprintf(p, + "None is not allowed for mutex type %s", + type); + } + mxcfg->none = 1; + } + else { + mxcfg->mech = mech; + if (mutexdir) { /* retain mutex default if not configured */ + mxcfg->dir = mutexdir; + } + } + } + + return NULL; +} + +AP_DECLARE(apr_status_t) ap_mutex_register(apr_pool_t *pconf, + const char *type, + const char *default_dir, + apr_lockmech_e default_mech, + apr_int32_t options) +{ + mutex_cfg_t *mxcfg = apr_pcalloc(pconf, sizeof *mxcfg); + + if ((options & ~(AP_MUTEX_ALLOW_NONE | AP_MUTEX_DEFAULT_NONE))) { + return APR_EINVAL; + } + + ap_mutex_init(pconf); /* in case this mod's pre-config ran before core's */ + + mxcfg->options = options; + if (options & AP_MUTEX_DEFAULT_NONE) { + mxcfg->none = 1; + } + mxcfg->dir = default_dir; /* usually NULL */ + mxcfg->mech = default_mech; /* usually APR_LOCK_DEFAULT */ + apr_hash_set(mxcfg_by_type, type, APR_HASH_KEY_STRING, mxcfg); + + return APR_SUCCESS; +} + +static int mutex_needs_file(apr_lockmech_e mech) +{ + if (mech != APR_LOCK_FLOCK + && mech != APR_LOCK_FCNTL +#if APR_USE_FLOCK_SERIALIZE || APR_USE_FCNTL_SERIALIZE + && mech != APR_LOCK_DEFAULT +#endif + ) { + return 0; + } + return 1; +} + +static const char *get_mutex_filename(apr_pool_t *p, mutex_cfg_t *mxcfg, + const char *type, + const char *instance_id) +{ + const char *pid_suffix = ""; + + if (!mutex_needs_file(mxcfg->mech)) { + return NULL; + } + +#if HAVE_UNISTD_H + if (!mxcfg->omit_pid) { + pid_suffix = apr_psprintf(p, ".%" APR_PID_T_FMT, getpid()); + } +#endif + + return ap_server_root_relative(p, + apr_pstrcat(p, + mxcfg->dir, + "/", + type, + instance_id ? "-" : "", + instance_id ? instance_id : "", + pid_suffix, + NULL)); +} + +static mutex_cfg_t *mxcfg_lookup(apr_pool_t *p, const char *type) +{ + mutex_cfg_t *defcfg, *mxcfg, *newcfg; + + defcfg = apr_hash_get(mxcfg_by_type, "default", APR_HASH_KEY_STRING); + + /* MUST exist in table, or wasn't registered */ + mxcfg = apr_hash_get(mxcfg_by_type, type, APR_HASH_KEY_STRING); + if (!mxcfg) { + return NULL; + } + + /* order of precedence: + * 1. Mutex directive for this mutex + * 2. Mutex directive for "default" + * 3. Defaults for this mutex from ap_mutex_register() + * 4. Global defaults + */ + + if (mxcfg->set) { + newcfg = mxcfg; + } + else if (defcfg->set) { + newcfg = defcfg; + } + else if (mxcfg->none || mxcfg->mech != APR_LOCK_DEFAULT) { + newcfg = mxcfg; + } + else { + newcfg = defcfg; + } + + if (!newcfg->none && mutex_needs_file(newcfg->mech) && !newcfg->dir) { + /* a file-based mutex mechanism was configured, but + * without a mutex file directory; go back through + * the chain to find the directory, store in new + * mutex cfg structure + */ + newcfg = apr_pmemdup(p, newcfg, sizeof *newcfg); + + /* !true if dir not already set: mxcfg->set && defcfg->dir */ + if (defcfg->set && defcfg->dir) { + newcfg->dir = defcfg->dir; + } + else if (mxcfg->dir) { + newcfg->dir = mxcfg->dir; + } + else { + newcfg->dir = defcfg->dir; + } + } + + return newcfg; +} + +static void log_bad_create_options(server_rec *s, const char *type) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(00021) + "Invalid options were specified when creating the %s mutex", + type); +} + +static void log_unknown_type(server_rec *s, const char *type) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(00022) + "Can't create mutex of unknown type %s", type); +} + +static void log_create_failure(apr_status_t rv, server_rec *s, const char *type, + const char *fname) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00023) + "Couldn't create the %s mutex %s%s%s", type, + fname ? "(file " : "", + fname ? fname : "", + fname ? ")" : ""); +} + +#ifdef AP_NEED_SET_MUTEX_PERMS +static void log_perms_failure(apr_status_t rv, server_rec *s, const char *type) +{ + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00024) + "Couldn't set permissions on the %s mutex; " + "check User and Group directives", + type); +} +#endif + +AP_DECLARE(apr_status_t) ap_global_mutex_create(apr_global_mutex_t **mutex, + const char **name, + const char *type, + const char *instance_id, + server_rec *s, apr_pool_t *p, + apr_int32_t options) +{ + apr_status_t rv; + const char *fname; + mutex_cfg_t *mxcfg = mxcfg_lookup(p, type); + + if (options) { + log_bad_create_options(s, type); + return APR_EINVAL; + } + + if (!mxcfg) { + log_unknown_type(s, type); + return APR_EINVAL; + } + + if (mxcfg->none) { + *mutex = NULL; + return APR_SUCCESS; + } + + fname = get_mutex_filename(p, mxcfg, type, instance_id); + + rv = apr_global_mutex_create(mutex, fname, mxcfg->mech, p); + if (rv != APR_SUCCESS) { + log_create_failure(rv, s, type, fname); + return rv; + } + + if (name) + *name = fname; + +#ifdef AP_NEED_SET_MUTEX_PERMS + rv = ap_unixd_set_global_mutex_perms(*mutex); + if (rv != APR_SUCCESS) { + log_perms_failure(rv, s, type); + } +#endif + + return rv; +} + +AP_DECLARE(apr_status_t) ap_proc_mutex_create(apr_proc_mutex_t **mutex, + const char **name, + const char *type, + const char *instance_id, + server_rec *s, apr_pool_t *p, + apr_int32_t options) +{ + apr_status_t rv; + const char *fname; + mutex_cfg_t *mxcfg = mxcfg_lookup(p, type); + + if (options) { + log_bad_create_options(s, type); + return APR_EINVAL; + } + + if (!mxcfg) { + log_unknown_type(s, type); + return APR_EINVAL; + } + + if (mxcfg->none) { + *mutex = NULL; + return APR_SUCCESS; + } + + fname = get_mutex_filename(p, mxcfg, type, instance_id); + + rv = apr_proc_mutex_create(mutex, fname, mxcfg->mech, p); + if (rv != APR_SUCCESS) { + log_create_failure(rv, s, type, fname); + return rv; + } + + if (name) + *name = fname; + +#ifdef AP_NEED_SET_MUTEX_PERMS + rv = ap_unixd_set_proc_mutex_perms(*mutex); + if (rv != APR_SUCCESS) { + log_perms_failure(rv, s, type); + } +#endif + + return rv; +} + +AP_CORE_DECLARE(void) ap_dump_mutexes(apr_pool_t *p, server_rec *s, apr_file_t *out) +{ + apr_hash_index_t *idx; + mutex_cfg_t *defcfg = apr_hash_get(mxcfg_by_type, "default", APR_HASH_KEY_STRING); + for (idx = apr_hash_first(p, mxcfg_by_type); idx; idx = apr_hash_next(idx)) + { + mutex_cfg_t *mxcfg; + const char *name, *mech; + const void *name_; + const char *dir = ""; + apr_hash_this(idx, &name_, NULL, NULL); + name = name_; + mxcfg = mxcfg_lookup(p, name); + if (mxcfg == defcfg && strcmp(name, "default") != 0) { + apr_file_printf(out, "Mutex %s: using_defaults\n", name); + continue; + } + if (mxcfg->none) { + apr_file_printf(out, "Mutex %s: none\n", name); + continue; + } + switch (mxcfg->mech) { + case APR_LOCK_DEFAULT: + mech = "default"; + break; +#if APR_HAS_FCNTL_SERIALIZE + case APR_LOCK_FCNTL: + mech = "fcntl"; + break; +#endif +#if APR_HAS_FLOCK_SERIALIZE + case APR_LOCK_FLOCK: + mech = "flock"; + break; +#endif +#if APR_HAS_POSIXSEM_SERIALIZE + case APR_LOCK_POSIXSEM: + mech = "posixsem"; + break; +#endif +#if APR_HAS_SYSVSEM_SERIALIZE + case APR_LOCK_SYSVSEM: + mech = "sysvsem"; + break; +#endif +#if APR_HAS_PROC_PTHREAD_SERIALIZE + case APR_LOCK_PROC_PTHREAD: + mech = "pthread"; + break; +#endif + default: + ap_assert(0); + } + + if (mxcfg->dir) + dir = ap_server_root_relative(p, mxcfg->dir); + + apr_file_printf(out, "Mutex %s: dir=\"%s\" mechanism=%s %s\n", name, dir, mech, + mxcfg->omit_pid ? "[OmitPid]" : ""); + } +} diff --git a/server/util_pcre.c b/server/util_pcre.c index c69c9785..7196878d 100644 --- a/server/util_pcre.c +++ b/server/util_pcre.c @@ -1,6 +1,6 @@ /************************************************* -* Perl-Compatible Regular Expressions * -*************************************************/ + * Perl-Compatible Regular Expressions * + *************************************************/ /* This is a library of functions to support regular expressions whose syntax @@ -55,176 +55,196 @@ POSSIBILITY OF SUCH DAMAGE. #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. */ + * 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 */ + "", /* 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; + 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, errbuf_size, "%s%s%-6d", message, addmessage, + (int)preg->re_erroffset); + else + apr_cpystrn(errbuf, message, errbuf_size); } - } -return length + addlength; + return length + addlength; } /************************************************* -* Free store held by a regex * -*************************************************/ + * Free store held by a regex * + *************************************************/ AP_DECLARE(void) ap_regfree(ap_regex_t *preg) { -(pcre_free)(preg->re_pcre); + (pcre_free)(preg->re_pcre); } /************************************************* -* Compile a regular expression * -*************************************************/ + * 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 + * 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) +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; + 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; + if ((cflags & AP_REG_DOTALL) != 0) + options |= PCRE_DOTALL; + + 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 * -*************************************************/ + * 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. */ - + * 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[], + 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); + return ap_regexec_len(preg, string, strlen(string), nmatch, pmatch, + eflags); +} -if (rc == 0) rc = nmatch; /* All captured slots were filled in */ +AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, + apr_size_t len, 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; + } + } -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]; + rc = pcre_exec((const pcre *)preg->re_pcre, NULL, buff, (int)len, + 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; } - 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; + + 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; + case PCRE_ERROR_MATCHLIMIT: + return AP_REG_ESPACE; #endif #ifdef PCRE_ERROR_BADUTF8 - case PCRE_ERROR_BADUTF8: return AP_REG_INVARG; + case PCRE_ERROR_BADUTF8: + return AP_REG_INVARG; #endif #ifdef PCRE_ERROR_BADUTF8_OFFSET - case PCRE_ERROR_BADUTF8_OFFSET: return AP_REG_INVARG; + case PCRE_ERROR_BADUTF8_OFFSET: + return AP_REG_INVARG; #endif - default: return AP_REG_ASSERT; + default: + return AP_REG_ASSERT; + } } - } } /* End of pcreposix.c */ diff --git a/server/util_regex.c b/server/util_regex.c new file mode 100644 index 00000000..73eccec7 --- /dev/null +++ b/server/util_regex.c @@ -0,0 +1,210 @@ +/* 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_pools.h" +#include "apr_strings.h" +#include "ap_config.h" +#include "ap_regex.h" +#include "httpd.h" + +static apr_status_t rxplus_cleanup(void *preg) +{ + ap_regfree((ap_regex_t *) preg); + return APR_SUCCESS; +} + +AP_DECLARE(ap_rxplus_t*) ap_rxplus_compile(apr_pool_t *pool, + const char *pattern) +{ + /* perl style patterns + * add support for more as and when wanted + * substitute: s/rx/subs/ + * match: m/rx/ or just /rx/ + */ + + /* allow any nonalnum delimiter as first or second char. + * If we ever use this with non-string pattern we'll need an extra check + */ + const char *endp = 0; + const char *str = pattern; + const char *rxstr; + ap_rxplus_t *ret = apr_pcalloc(pool, sizeof(ap_rxplus_t)); + char delim = 0; + enum { SUBSTITUTE = 's', MATCH = 'm'} action = MATCH; + if (!apr_isalnum(pattern[0])) { + delim = *str++; + } + else if (pattern[0] == 's' && !apr_isalnum(pattern[1])) { + action = SUBSTITUTE; + delim = pattern[1]; + str += 2; + } + else if (pattern[0] == 'm' && !apr_isalnum(pattern[1])) { + delim = pattern[1]; + str += 2; + } + /* TODO: support perl's after/before */ + /* FIXME: fix these simplminded delims */ + + /* we think there's a delimiter. Allow for it not to be if unmatched */ + if (delim) { + endp = ap_strchr_c(str, delim); + } + if (!endp) { /* there's no delim or flags */ + if (ap_regcomp(&ret->rx, pattern, 0) == 0) { + apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, + apr_pool_cleanup_null); + return ret; + } + else { + return NULL; + } + } + + /* We have a delimiter. Use it to extract the regexp */ + rxstr = apr_pstrndup(pool, str, endp-str); + + /* If it's a substitution, we need the replacement string + * TODO: possible future enhancement - support other parsing + * in the replacement string. + */ + if (action == SUBSTITUTE) { + str = endp+1; + if (!*str || (endp = ap_strchr_c(str, delim), !endp)) { + /* missing replacement string is an error */ + return NULL; + } + ret->subs = apr_pstrndup(pool, str, (endp-str)); + } + + /* anything after the current delimiter is flags */ + while (*++endp) { + switch (*endp) { + case 'i': ret->flags |= AP_REG_ICASE; break; + case 'm': ret->flags |= AP_REG_NEWLINE; break; + case 'n': ret->flags |= AP_REG_NOMEM; break; + case 'g': ret->flags |= AP_REG_MULTI; break; + case 's': ret->flags |= AP_REG_DOTALL; break; + case '^': ret->flags |= AP_REG_NOTBOL; break; + case '$': ret->flags |= AP_REG_NOTEOL; break; + default: break; /* we should probably be stricter here */ + } + } + if (ap_regcomp(&ret->rx, rxstr, ret->flags) == 0) { + apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, + apr_pool_cleanup_null); + } + else { + return NULL; + } + if (!(ret->flags & AP_REG_NOMEM)) { + /* count size of memory required, starting at 1 for the whole-match + * Simpleminded should be fine 'cos regcomp already checked syntax + */ + ret->nmatch = 1; + while (*rxstr) { + switch (*rxstr++) { + case '\\': /* next char is escaped - skip it */ + if (*rxstr != 0) { + ++rxstr; + } + break; + case '(': /* unescaped bracket implies memory */ + ++ret->nmatch; + break; + default: + break; + } + } + ret->pmatch = apr_palloc(pool, ret->nmatch*sizeof(ap_regmatch_t)); + } + return ret; +} + +AP_DECLARE(int) ap_rxplus_exec(apr_pool_t *pool, ap_rxplus_t *rx, + const char *pattern, char **newpattern) +{ + int ret = 1; + int startl, oldl, newl, diffsz; + const char *remainder; + char *subs; +/* snrf process_regexp from mod_headers */ + if (ap_regexec(&rx->rx, pattern, rx->nmatch, rx->pmatch, rx->flags) != 0) { + rx->match = NULL; + return 0; /* no match, nothing to do */ + } + rx->match = pattern; + if (rx->subs) { + *newpattern = ap_pregsub(pool, rx->subs, pattern, + rx->nmatch, rx->pmatch); + if (!*newpattern) { + return 0; /* FIXME - should we do more to handle error? */ + } + startl = rx->pmatch[0].rm_so; + oldl = rx->pmatch[0].rm_eo - startl; + newl = strlen(*newpattern); + diffsz = newl - oldl; + remainder = pattern + startl + oldl; + if (rx->flags & AP_REG_MULTI) { + /* recurse to do any further matches */ + char *subs; + ret += ap_rxplus_exec(pool, rx, remainder, &subs); + if (ret > 1) { + /* a further substitution happened */ + diffsz += strlen(subs) - strlen(remainder); + remainder = subs; + } + } + subs = apr_palloc(pool, strlen(pattern) + 1 + diffsz); + memcpy(subs, pattern, startl); + memcpy(subs+startl, *newpattern, newl); + strcpy(subs+startl+newl, remainder); + *newpattern = subs; + } + return ret; +} +#ifdef DOXYGEN +AP_DECLARE(int) ap_rxplus_nmatch(ap_rxplus_t *rx) +{ + return (rx->match != NULL) ? rx->nmatch : 0; +} +#endif + +/* If this blows up on you, see the notes in the header/apidoc + * rx->match is a pointer and it's your responsibility to ensure + * it hasn't gone out-of-scope since the last ap_rxplus_exec + */ +AP_DECLARE(void) ap_rxplus_match(ap_rxplus_t *rx, int n, int *len, + const char **match) +{ + if (n >= 0 && n < ap_rxplus_nmatch(rx)) { + *match = rx->match + rx->pmatch[n].rm_so; + *len = rx->pmatch[n].rm_eo - rx->pmatch[n].rm_so; + } + else { + *len = -1; + *match = NULL; + } +} +AP_DECLARE(char*) ap_rxplus_pmatch(apr_pool_t *pool, ap_rxplus_t *rx, int n) +{ + int len; + const char *match; + ap_rxplus_match(rx, n, &len, &match); + return (match != NULL) ? apr_pstrndup(pool, match, len) : NULL; +} diff --git a/server/util_script.c b/server/util_script.c index 1300951b..38dfa0ef 100644 --- a/server/util_script.c +++ b/server/util_script.c @@ -25,7 +25,6 @@ #include <stdlib.h> #endif -#define CORE_PRIVATE #include "ap_config.h" #include "httpd.h" #include "http_config.h" @@ -50,12 +49,13 @@ * where there don't have to be). */ -#define MALFORMED_MESSAGE "malformed header from script. Bad header=" -#define MALFORMED_HEADER_LENGTH_TO_SHOW 30 +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX -static char *http2env(apr_pool_t *a, const char *w) +static char *http2env(request_rec *r, const char *w) { - char *res = (char *)apr_palloc(a, sizeof("HTTP_") + strlen(w)); + char *res = (char *)apr_palloc(r->pool, sizeof("HTTP_") + strlen(w)); char *cp = res; char c; @@ -66,11 +66,17 @@ static char *http2env(apr_pool_t *a, const char *w) *cp++ = '_'; while ((c = *w++) != 0) { - if (!apr_isalnum(c)) { + if (apr_isalnum(c)) { + *cp++ = apr_toupper(c); + } + else if (c == '-') { *cp++ = '_'; } else { - *cp++ = apr_toupper(c); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "Not exporting header with invalid name as envvar: %s", + ap_escape_logitem(r->pool, w)); + return NULL; } } *cp = 0; @@ -78,6 +84,18 @@ static char *http2env(apr_pool_t *a, const char *w) return res; } +static void add_unless_null(apr_table_t *table, const char *name, const char *val) +{ + if (name && val) { + apr_table_addn(table, name, val); + } +} + +static void env2env(apr_table_t *table, const char *name) +{ + add_unless_null(table, name, getenv(name)); +} + 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); @@ -121,12 +139,7 @@ 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 char *env_temp; 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; @@ -176,69 +189,61 @@ AP_DECLARE(void) ap_add_common_vars(request_rec *r) 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")) != NULL) { - apr_table_addn(e, "SystemRoot", env_temp); - } - if ((env_temp = getenv("COMSPEC")) != NULL) { - apr_table_addn(e, "COMSPEC", env_temp); - } - if ((env_temp = getenv("PATHEXT")) != NULL) { - apr_table_addn(e, "PATHEXT", env_temp); - } - if ((env_temp = getenv("WINDIR")) != NULL) { - 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); - } + else + add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); + } + + env_temp = apr_table_get(r->subprocess_env, "PATH"); + if (env_temp == NULL) { + env_temp = getenv("PATH"); + } + if (env_temp == NULL) { + env_temp = DEFAULT_PATH; + } + apr_table_addn(e, "PATH", apr_pstrdup(r->pool, env_temp)); + +#if defined(WIN32) + env2env(e, "SystemRoot"); + env2env(e, "COMSPEC"); + env2env(e, "PATHEXT"); + env2env(e, "WINDIR"); +#elif defined(OS2) + env2env(e, "COMSPEC"); + env2env(e, "ETC"); + env2env(e, "DPATH"); + env2env(e, "PERLLIB_PREFIX"); +#elif defined(BEOS) + env2env(e, "LIBRARY_PATH"); +#elif defined(DARWIN) + env2env(e, "DYLD_LIBRARY_PATH"); +#elif defined(_AIX) + env2env(e, "LIBPATH"); +#elif defined(__HPUX__) + /* HPUX PARISC 2.0W knows both, otherwise redundancy is harmless */ + env2env(e, "SHLIB_PATH"); + env2env(e, "LD_LIBRARY_PATH"); +#else /* Some Unix */ + env2env(e, "LD_LIBRARY_PATH"); #endif apr_table_addn(e, "SERVER_SIGNATURE", ap_psignature("", r)); apr_table_addn(e, "SERVER_SOFTWARE", ap_get_server_banner()); apr_table_addn(e, "SERVER_NAME", - ap_escape_html(r->pool, ap_get_server_name(r))); + ap_escape_html(r->pool, ap_get_server_name_for_url(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); + add_unless_null(e, "REMOTE_HOST", + ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST, NULL)); + apr_table_addn(e, "REMOTE_ADDR", r->useragent_ip); apr_table_addn(e, "DOCUMENT_ROOT", ap_document_root(r)); /* Apache */ + apr_table_setn(e, "REQUEST_SCHEME", ap_http_scheme(r)); + apr_table_addn(e, "CONTEXT_PREFIX", ap_context_prefix(r)); + apr_table_addn(e, "CONTEXT_DOCUMENT_ROOT", ap_context_document_root(r)); apr_table_addn(e, "SERVER_ADMIN", s->server_admin); /* Apache */ apr_table_addn(e, "SCRIPT_FILENAME", r->filename); /* Apache */ - rport = c->remote_addr->port; + rport = c->client_addr->port; apr_table_addn(e, "REMOTE_PORT", apr_itoa(r->pool, rport)); if (r->user) { @@ -255,23 +260,17 @@ AP_DECLARE(void) ap_add_common_vars(request_rec *r) 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)); + add_unless_null(e, "AUTH_TYPE", r->ap_auth_type); + env_temp = ap_get_remote_logname(r); + if (env_temp) { + apr_table_addn(e, "REMOTE_IDENT", apr_pstrdup(r->pool, env_temp)); } /* 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); - } + add_unless_null(e, "REDIRECT_QUERY_STRING", r->prev->args); + add_unless_null(e, "REDIRECT_URL", r->prev->uri); } if (e != r->subprocess_env) { @@ -399,10 +398,12 @@ static int set_cookie_doo_doo(void *v, const char *key, const char *val) } #define HTTP_UNSET (-HTTP_OK) +#define SCRIPT_LOG_MARK __FILE__,__LINE__,module_index -AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, +AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer, int (*getsfunc) (char *, int, void *), - void *getsfunc_data) + void *getsfunc_data, + int module_index) { char x[MAX_STRING_LEN]; char *w, *l; @@ -410,6 +411,8 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, int cgi_status = HTTP_UNSET; apr_table_t *merge; apr_table_t *cookie_table; + int trace_log = APLOG_R_MODULE_IS_LEVEL(r, module_index, APLOG_TRACE1); + int first_header = 1; if (buffer) { *buffer = '\0'; @@ -432,13 +435,16 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, int rv = (*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data); if (rv == 0) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, - "Premature end of script headers: %s", + const char *msg = "Premature end of script headers"; + if (first_header) + msg = "End of script output before headers"; + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "%s: %s", msg, apr_filepath_name_get(r->filename)); return HTTP_INTERNAL_SERVER_ERROR; } else if (rv == -1) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, "Script timed out before returning headers: %s", apr_filepath_name_get(r->filename)); return HTTP_GATEWAY_TIME_OUT; @@ -475,17 +481,17 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, 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. - */ + /* 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); } @@ -500,6 +506,14 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, return cond_status; } + if (trace_log) { + if (first_header) + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE4, 0, r, + "Headers from script '%s':", + apr_filepath_name_get(r->filename)); + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE4, 0, r, " %s", w); + } + /* if we see a bogus header don't ignore it. Shout and scream */ #if APR_CHARSET_EBCDIC @@ -519,7 +533,7 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, ++maybeASCII; } if (maybeASCII > maybeEBCDIC) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + ap_log_error(SCRIPT_LOG_MARK, APLOG_ERR, 0, r->server, "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)", r->filename); inbytes_left = outbytes_left = cp - w; @@ -529,12 +543,6 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, } #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)) { @@ -542,9 +550,9 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, } } - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, - "%s: %s", malformed, - apr_filepath_name_get(r->filename)); + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "malformed header from script '%s': Bad header: %.30s", + apr_filepath_name_get(r->filename), w); return HTTP_INTERNAL_SERVER_ERROR; } @@ -573,6 +581,14 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, */ else if (!strcasecmp(w, "Status")) { r->status = cgi_status = atoi(l); + if (!ap_is_HTTP_VALID_RESPONSE(cgi_status)) + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, + "Invalid status line from script '%s': %s", + apr_filepath_name_get(r->filename), w); + else + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, + "Status line from script '%s': %s", + apr_filepath_name_get(r->filename), w); r->status_line = apr_pstrdup(r->pool, l); } else if (!strcasecmp(w, "Location")) { @@ -587,6 +603,9 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, else if (!strcasecmp(w, "Transfer-Encoding")) { apr_table_set(r->headers_out, w, l); } + else if (!strcasecmp(w, "ETag")) { + 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. @@ -601,11 +620,21 @@ AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, else { apr_table_add(merge, w, l); } + first_header = 0; } - + /* never reached - we leave this function within the while loop above */ return OK; } +AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, + int (*getsfunc) (char *, int, void *), + void *getsfunc_data) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc, + getsfunc_data, + APLOG_MODULE_INDEX); +} + static int getsfunc_FILE(char *buf, int len, void *f) { return apr_file_gets(buf, len, (apr_file_t *) f) == APR_SUCCESS; @@ -614,9 +643,18 @@ static int getsfunc_FILE(char *buf, int len, void *f) 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); + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_FILE, f, + APLOG_MODULE_INDEX); +} + +AP_DECLARE(int) ap_scan_script_header_err_ex(request_rec *r, apr_file_t *f, + char *buffer, int module_index) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_FILE, f, + module_index); } + static int getsfunc_BRIGADE(char *buf, int len, void *arg) { apr_bucket_brigade *bb = (apr_bucket_brigade *)arg; @@ -666,9 +704,20 @@ 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); + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_BRIGADE, bb, + APLOG_MODULE_INDEX); +} + +AP_DECLARE(int) ap_scan_script_header_err_brigade_ex(request_rec *r, + apr_bucket_brigade *bb, + char *buffer, + int module_index) +{ + return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_BRIGADE, bb, + module_index); } + struct vastrs { va_list args; int arg; @@ -707,6 +756,28 @@ static int getsfunc_STRING(char *w, int len, void *pvastrs) * character is returned to **arg, **data. (The first optional arg is * counted as 0.) */ +AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs_ex(request_rec *r, + char *buffer, + int module_index, + 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_ex(r, buffer, getsfunc_STRING, + (void *) &strs, module_index); + if (termch) + *termch = strs.curpos; + if (termarg) + *termarg = strs.arg; + va_end(strs.args); + return res; +} + AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, char *buffer, const char **termch, @@ -718,7 +789,8 @@ AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, 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); + res = ap_scan_script_header_err_core_ex(r, buffer, getsfunc_STRING, + (void *) &strs, APLOG_MODULE_INDEX); if (termch) *termch = strs.curpos; if (termarg) @@ -726,3 +798,38 @@ AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, va_end(strs.args); return res; } + +static void +argstr_to_table(char *str, apr_table_t *parms) +{ + char *key; + char *value; + char *strtok_state; + + if (str == NULL) { + return; + } + + key = apr_strtok(str, "&", &strtok_state); + while (key) { + value = strchr(key, '='); + if (value) { + *value = '\0'; /* Split the string in two */ + value++; /* Skip passed the = */ + } + else { + value = "1"; + } + ap_unescape_url(key); + ap_unescape_url(value); + apr_table_set(parms, key, value); + key = apr_strtok(NULL, "&", &strtok_state); + } +} + +AP_DECLARE(void) ap_args_to_table(request_rec *r, apr_table_t **table) +{ + apr_table_t *t = apr_table_make(r->pool, 10); + argstr_to_table(apr_pstrdup(r->pool, r->args), t); + *table = t; +} diff --git a/server/util_time.c b/server/util_time.c index 172cfa52..3632d0d5 100644 --- a/server/util_time.c +++ b/server/util_time.c @@ -16,6 +16,16 @@ #include "util_time.h" + +/* Number of characters needed to format the microsecond part of a timestamp. + * Microseconds have 6 digits plus one separator character makes 7. + * */ +#define AP_CTIME_USEC_LENGTH 7 + +/* Length of ISO 8601 date/time */ +#define AP_CTIME_COMPACT_LEN 20 + + /* Cache for exploded values of recent timestamps */ @@ -145,27 +155,73 @@ AP_DECLARE(apr_status_t) ap_explode_recent_gmt(apr_time_exp_t * tm, AP_DECLARE(apr_status_t) ap_recent_ctime(char *date_str, apr_time_t t) { + int len = APR_CTIME_LEN; + return ap_recent_ctime_ex(date_str, t, AP_CTIME_OPTION_NONE, &len); +} + +AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, + int option, int *len) +{ /* ### 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; + int needed; + + + /* Calculate the needed buffer length */ + if (option & AP_CTIME_OPTION_COMPACT) + needed = AP_CTIME_COMPACT_LEN; + else + needed = APR_CTIME_LEN; + + if (option & AP_CTIME_OPTION_USEC) { + needed += AP_CTIME_USEC_LENGTH; + } + + /* Check the provided buffer length */ + if (len && *len >= needed) { + *len = needed; + } + else { + if (len != NULL) { + *len = 0; + } + return APR_ENOMEM; + } - /* example: "Wed Jun 30 21:49:08 1993" */ - /* 123456789012345678901234 */ + /* example without options: "Wed Jun 30 21:49:08 1993" */ + /* 123456789012345678901234 */ + /* example for compact format: "1993-06-30 21:49:08" */ + /* 1234567890123456789 */ 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++ = ' '; + real_year = 1900 + xt.tm_year; + if (option & AP_CTIME_OPTION_COMPACT) { + int real_month = xt.tm_mon + 1; + *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++ = real_month / 10 + '0'; + *date_str++ = real_month % 10 + '0'; + *date_str++ = '-'; + } + else { + 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++ = ' '; @@ -177,12 +233,22 @@ AP_DECLARE(apr_status_t) ap_recent_ctime(char *date_str, apr_time_t t) *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'; + if (option & AP_CTIME_OPTION_USEC) { + int div; + int usec = (int)xt.tm_usec; + *date_str++ = '.'; + for (div=100000; div>0; div=div/10) { + *date_str++ = usec / div + '0'; + usec = usec % div; + } + } + if (!(option & AP_CTIME_OPTION_COMPACT)) { + *date_str++ = ' '; + *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; diff --git a/server/util_xml.c b/server/util_xml.c index dc88b3dd..26f1c6e8 100644 --- a/server/util_xml.c +++ b/server/util_xml.c @@ -29,6 +29,10 @@ #define READ_BLOCKSIZE 2048 +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) { apr_xml_parser *parser; @@ -81,7 +85,7 @@ AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) total_read += len; if (limit_xml_body && total_read > limit_xml_body) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00539) "XML request body is larger than the configured " "limit of %lu", (unsigned long)limit_xml_body); result = HTTP_REQUEST_ENTITY_TOO_LARGE; @@ -107,7 +111,7 @@ AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) *pdoc = NULL; return OK; } - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00540) "XML parser error (at end). status=%d", status); return HTTP_BAD_REQUEST; } @@ -119,7 +123,7 @@ AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) parser_error: (void) apr_xml_parser_geterror(parser, errbuf, sizeof(errbuf)); - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00541) "XML Parser Error: %s", errbuf); /* FALLTHRU */ diff --git a/server/vhost.c b/server/vhost.c index b8e9ca75..302e4084 100644 --- a/server/vhost.c +++ b/server/vhost.c @@ -27,7 +27,6 @@ #define APR_WANT_STRFUNC #include "apr_want.h" -#define CORE_PRIVATE #include "ap_config.h" #include "httpd.h" #include "http_config.h" @@ -40,6 +39,10 @@ #include <arpa/inet.h> #endif +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + /* * After all the definitions there's an explanation of how it's all put * together. @@ -68,6 +71,8 @@ struct ipaddr_chain { 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 */ + name_chain *initialnames; /* no runtime use, temporary storage of first + * NVH'es names */ }; /* This defines the size of the hash table used for hashing ip addresses @@ -92,9 +97,12 @@ static ipaddr_chain *iphash_table[IPHASH_TABLE_SIZE]; /* 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; +/* whether a config error was seen */ +static int config_error = 0; + +/* config check function */ +static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s); /* * How it's used: @@ -124,8 +132,7 @@ 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; + ap_hook_check_config(vhost_check_config, NULL, NULL, APR_HOOK_MIDDLE); } @@ -151,9 +158,9 @@ static const char *get_addresses(apr_pool_t *p, const char *w_, if (*w_ == '\0') return NULL; - w = apr_pstrdup(p, w_); + wlen = strlen(w_); /* wlen must be > 0 at this point */ + w = apr_pstrmemdup(p, w_, wlen); /* 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) { @@ -181,25 +188,17 @@ static const char *get_addresses(apr_pool_t *p, const char *w_, 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 (strcmp(host, "*") == 0 || strcasecmp(host, "_default_") == 0) { + rv = apr_sockaddr_info_get(&my_addr, NULL, APR_UNSPEC, port, 0, p); if (rv) { - return "Could not resolve address '255.255.255.255' -- " + return "Could not determine a wildcard address ('0.0.0.0') -- " "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, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00547) "Could not resolve host name %s -- ignoring!", host); return NULL; } @@ -250,12 +249,20 @@ const char *ap_parse_vhost_addrs(apr_pool_t *p, } -const char *ap_set_name_virtual_host(cmd_parms *cmd, void *dummy, - const char *arg) +AP_DECLARE_NONSTD(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); + static int warnonce = 0; + if (++warnonce == 1) { + ap_log_error(APLOG_MARK, APLOG_NOTICE|APLOG_STARTUP, APR_SUCCESS, NULL, APLOGNO(00548) + "NameVirtualHost has no effect and will be removed in the " + "next release %s:%d", + cmd->directive->filename, + cmd->directive->line_num); + } + + return NULL; } @@ -347,6 +354,7 @@ static ipaddr_chain *new_ipaddr_chain(apr_pool_t *p, new = apr_palloc(p, sizeof(*new)); new->names = NULL; + new->initialnames = NULL; new->server = s; new->sar = sar; new->next = NULL; @@ -370,7 +378,8 @@ static name_chain *new_name_chain(apr_pool_t *p, static APR_INLINE ipaddr_chain *find_ipaddr(apr_sockaddr_t *sa) { unsigned bucket; - ipaddr_chain *trav; + ipaddr_chain *trav = NULL; + ipaddr_chain *wild_match = NULL; /* scan the hash table for an exact match first */ bucket = hash_addr(sa); @@ -378,30 +387,48 @@ static APR_INLINE ipaddr_chain *find_ipaddr(apr_sockaddr_t *sa) 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 (cur->port == sa->port) { if (apr_sockaddr_equal(cur, sa)) { return trav; } } + if (wild_match == NULL && (cur->port == 0 || sa->port == 0)) { + if (apr_sockaddr_equal(cur, sa)) { + /* don't break, continue looking for an exact match */ + wild_match = trav; + } + } } - return NULL; + return wild_match; } static ipaddr_chain *find_default_server(apr_port_t port) { server_addr_rec *sar; - ipaddr_chain *trav; + ipaddr_chain *trav = NULL; + ipaddr_chain *wild_match = NULL; for (trav = default_list; trav; trav = trav->next) { sar = trav->sar; - if (sar->host_port == 0 || sar->host_port == port) { + if (sar->host_port == port) { /* match! */ return trav; } + if (wild_match == NULL && sar->host_port == 0) { + /* don't break, continue looking for an exact match */ + wild_match = trav; + } } - return NULL; + return wild_match; } +#if APR_HAVE_IPV6 +#define IS_IN6_ANYADDR(ad) ((ad)->family == APR_INET6 \ + && IN6_IS_ADDR_UNSPECIFIED(&(ad)->sa.sin6.sin6_addr)) +#else +#define IS_IN6_ANYADDR(ad) (0) +#endif + static void dump_a_vhost(apr_file_t *f, ipaddr_chain *ic) { name_chain *nc; @@ -409,13 +436,8 @@ static void dump_a_vhost(apr_file_t *f, ipaddr_chain *ic) 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) { + if ((ha->family == APR_INET && ha->sa.sin.sin_addr.s_addr == INADDR_ANY) + || IS_IN6_ANYADDR(ha)) { len = apr_snprintf(buf, sizeof(buf), "*:%u", ic->sar->host_port); } @@ -445,6 +467,26 @@ static void dump_a_vhost(apr_file_t *f, ipaddr_chain *ic) apr_file_printf(f, "namevhost %s (%s:%u)\n", nc->server->server_hostname, nc->server->defn_name, nc->server->defn_line_number); + if (nc->server->names) { + apr_array_header_t *names = nc->server->names; + char **name = (char **)names->elts; + int i; + for (i = 0; i < names->nelts; ++i) { + if (name[i]) { + apr_file_printf(f, "%16s alias %s\n", "", name[i]); + } + } + } + if (nc->server->wild_names) { + apr_array_header_t *names = nc->server->wild_names; + char **name = (char **)names->elts; + int i; + for (i = 0; i < names->nelts; ++i) { + if (name[i]) { + apr_file_printf(f, "%16s wild alias %s\n", "", name[i]); + } + } + } } } @@ -454,67 +496,57 @@ static void dump_vhost_config(apr_file_t *f) int i; apr_file_printf(f, "VirtualHost configuration:\n"); + + /* non-wildcard servers */ 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); - } + + /* wildcard servers */ + for (ic = default_list; ic; ic = ic->next) { + dump_a_vhost(f, ic); } } + /* - * Two helper functions for ap_fini_vhost_config() + * When a second or later virtual host maps to the same IP chain, + * add the relevant server names to the chain. Special care is taken + * to avoid adding ic->names until we're sure there are multiple VH'es. */ -static int add_name_vhost_config(apr_pool_t *p, server_rec *main_s, +static void 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; - } - } + name_chain *nc = new_name_chain(p, s, sar); + nc->next = ic->names; + + /* iterating backwards, so each one we see becomes the current default server */ + ic->server = s; + + if (ic->names == NULL) { + if (ic->initialnames == NULL) { + /* first pass, set these names aside in case we see another VH. + * Until then, this looks like an IP-based VH to runtime. + */ + ic->initialnames = nc; + } + else { + /* second pass through this chain -- this really is an NVH, and we + * have two sets of names to link in. + */ + nc->next = ic->initialnames; + ic->names = nc; + ic->initialnames = NULL; + } + } + else { + /* 3rd or more -- just keep stacking the names */ + ic->names = nc; + } } /* compile the tables and such we need to do the run-time vhost lookups */ @@ -526,9 +558,6 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_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; @@ -541,33 +570,6 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) 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 @@ -580,17 +582,13 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) 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)) { + if (!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); - } + if (!ic || sar->host_port != ic->sar->host_port) { + /* No default server, or we found a default server but + ** exactly one of us is a wildcard port, which means we want + ** two ip-based vhosts not an NVH with two names + */ ic = new_ipaddr_chain(p, s, sar); ic->next = default_list; default_list = ic; @@ -601,25 +599,18 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) /* see if it matches something we've already got */ ic = find_ipaddr(sar->host_addr); - if (!ic) { + if (!ic || sar->host_port != ic->sar->host_port) { + /* No matching server, or we found a matching server but + ** exactly one of us is a wildcard port, which means we want + ** two ip-based vhosts not an NVH with two names + */ 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; - } } + add_name_vhost_config(p, main_s, s, sar, ic); } /* Ok now we want to set up a server_hostname if the user was @@ -652,7 +643,7 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) char *ipaddr_str; apr_sockaddr_ip_get(&ipaddr_str, s->addrs->host_addr); - ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_s, + ap_log_error(APLOG_MARK, APLOG_ERR, rv, main_s, APLOGNO(00549) "Failed to resolve server name " "for %s (check DNS) -- or specify an explicit " "ServerName", @@ -664,14 +655,6 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) } } - /* 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 @@ -682,6 +665,11 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) } } +static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + return config_error ? !OK : OK; +} /***************************************************************************** * run-time vhost matching functions @@ -766,7 +754,7 @@ static void fix_hostname(request_rec *r) bad: r->status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00550) "Client sent malformed Host header"); return; } @@ -872,9 +860,11 @@ static void check_hostalias(request_rec *r) const char *host = r->hostname; apr_port_t port; server_rec *s; + server_rec *virthost_s; server_rec *last_s; name_chain *src; + virthost_s = NULL; last_s = NULL; port = r->connection->local_addr->port; @@ -901,23 +891,34 @@ static void check_hostalias(request_rec *r) 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; + /* If we still need to do ServerName and ServerAlias checks for this + * server, do them now. + */ + if (s != last_s) { + /* does it match any ServerName or ServerAlias directive? */ + if (matches_aliases(s, host)) { + goto found; + } } last_s = s; - if (matches_aliases(s, host)) { - goto found; + /* Fallback: does it match the virthost from the sar? */ + if (!strcasecmp(host, sar->virthost)) { + /* only the first match is used */ + if (virthost_s == NULL) { + virthost_s = s; + } } } + + /* If ServerName and ServerAlias check failed, we end up here. If it + * matches a VirtualHost, virthost_s is set. Use that as fallback + */ + if (virthost_s) { + s = virthost_s; + goto found; + } + return; found: |