/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . * * Portions of this software are based upon public domain software * originally written at the National Center for Supercomputing Applications, * University of Illinois, Urbana-Champaign. */ /* The purpose of this file is to store the code that MOST mpm's will need * this does not mean a function only goes into this file if every MPM needs * it. It means that if a function is needed by more than one MPM, and * future maintenance would be served by making the code common, then the * function belongs here. * * This is going in src/main because it is not platform specific, it is * specific to multi-process servers, but NOT to Unix. Which is why it * does not belong in src/os/unix */ #include "apr.h" #include "apr_thread_proc.h" #include "apr_signal.h" #include "apr_strings.h" #define APR_WANT_STRFUNC #include "apr_want.h" #include "apr_getopt.h" #include "apr_optional.h" #include "apr_allocator.h" #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_main.h" #include "mpm.h" #include "mpm_common.h" #include "ap_mpm.h" #include "ap_listen.h" #include "mpm_default.h" #ifdef AP_MPM_WANT_SET_SCOREBOARD #include "scoreboard.h" #endif #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_GRP_H #include #endif #ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES void ap_reclaim_child_processes(int terminate) { int i; long int waittime = 1024 * 16; /* in usecs */ apr_status_t waitret; int tries; int not_dead_yet; int max_daemons; ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons); for (tries = terminate ? 4 : 1; tries <= 9; ++tries) { /* don't want to hold up progress any more than * necessary, but we need to allow children a few moments to exit. * Set delay with an exponential backoff. */ apr_sleep(waittime); waittime = waittime * 4; /* now see who is done */ not_dead_yet = 0; for (i = 0; i < max_daemons; ++i) { pid_t pid = MPM_CHILD_PID(i); apr_proc_t proc; if (pid == 0) continue; proc.pid = pid; waitret = apr_proc_wait(&proc, NULL, NULL, APR_NOWAIT); if (waitret != APR_CHILD_NOTDONE) { MPM_NOTE_CHILD_KILLED(i); continue; } ++not_dead_yet; switch (tries) { case 1: /* 16ms */ case 2: /* 82ms */ case 3: /* 344ms */ case 4: /* 16ms */ break; case 5: /* 82ms */ case 6: /* 344ms */ case 7: /* 1.4sec */ /* ok, now it's being annoying */ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, "child process %ld still did not exit, " "sending a SIGTERM", (long)pid); kill(pid, SIGTERM); break; case 8: /* 6 sec */ /* die child scum */ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "child process %ld still did not exit, " "sending a SIGKILL", (long)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 9: /* 14 sec */ /* gave it our best shot, but alas... If this really * is a child we are trying to kill and it really hasn't * exited, we will likely fail to bind to the port * after the restart. */ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "could not make child process %ld exit, " "attempting to continue anyway", (long)pid); break; } } #if APR_HAS_OTHER_CHILD apr_proc_other_child_check(); #endif if (!not_dead_yet) { /* nothing left to wait for */ break; } } } #endif /* AP_MPM_WANT_RECLAIM_CHILD_PROCESSES */ #ifdef AP_MPM_WANT_WAIT_OR_TIMEOUT /* number of calls to wait_or_timeout between writable probes */ #ifndef INTERVAL_OF_WRITABLE_PROBES #define INTERVAL_OF_WRITABLE_PROBES 10 #endif static int wait_or_timeout_counter; void ap_wait_or_timeout(apr_exit_why_e *status, int *exitcode, apr_proc_t *ret, apr_pool_t *p) { apr_status_t rv; ++wait_or_timeout_counter; if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES) { wait_or_timeout_counter = 0; } rv = apr_proc_wait_all_procs(ret, exitcode, status, APR_NOWAIT, p); if (APR_STATUS_IS_EINTR(rv)) { ret->pid = -1; return; } if (APR_STATUS_IS_CHILD_DONE(rv)) { return; } #ifdef NEED_WAITPID if ((ret = reap_children(exitcode, status)) > 0) { return; } #endif apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); ret->pid = -1; return; } #endif /* AP_MPM_WANT_WAIT_OR_TIMEOUT */ #ifdef AP_MPM_WANT_PROCESS_CHILD_STATUS int ap_process_child_status(apr_proc_t *pid, apr_exit_why_e why, int status) { int signum = status; const char *sigdesc = apr_signal_description_get(signum); /* Child died... if it died due to a fatal error, * we should simply bail out. The caller needs to * check for bad rc from us and exit, running any * appropriate cleanups. * * If the child died due to a resource shortage, * the parent should limit the rate of forking */ if (APR_PROC_CHECK_EXIT(why)) { if (status == APEXIT_CHILDSICK) { return status; } if (status == APEXIT_CHILDFATAL) { ap_log_error(APLOG_MARK, APLOG_ALERT, 0, ap_server_conf, "Child %" APR_PID_T_FMT " returned a Fatal error..." APR_EOL_STR "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) && !defined(WIN32) void ap_sock_disable_nagle(apr_socket_t *s) { /* The Nagle algorithm says that we should delay sending partial * packets in hopes of getting more data. We don't want to do * this; we are not telnet. There are bad interactions between * persistent connections and Nagle's algorithm that have very severe * performance penalties. (Failing to disable Nagle is not much of a * problem with simple HTTP.) * * In spite of these problems, failure here is not a shooting offense. */ apr_status_t status = apr_socket_opt_set(s, APR_TCP_NODELAY, 1); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_WARNING, status, ap_server_conf, "apr_socket_opt_set: (TCP_NODELAY)"); } } #endif #ifdef HAVE_GETPWNAM AP_DECLARE(uid_t) ap_uname2id(const char *name) { struct passwd *ent; if (name[0] == '#') return (atoi(&name[1])); if (!(ent = getpwnam(name))) { ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "%s: bad user name %s", ap_server_argv0, name); exit(1); } return (ent->pw_uid); } #endif #ifdef HAVE_GETGRNAM AP_DECLARE(gid_t) ap_gname2id(const char *name) { struct group *ent; if (name[0] == '#') return (atoi(&name[1])); if (!(ent = getgrnam(name))) { ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "%s: bad group name %s", ap_server_argv0, name); exit(1); } return (ent->gr_gid); } #endif #ifndef HAVE_INITGROUPS int initgroups(const char *name, gid_t basegid) { #if defined(QNX) || defined(MPE) || defined(BEOS) || defined(_OSD_POSIX) || defined(TPF) || defined(__TANDEM) || defined(OS2) || defined(WIN32) || defined(NETWARE) /* QNX, MPE and BeOS do not appear to support supplementary groups. */ return 0; #else /* ndef QNX */ gid_t groups[NGROUPS_MAX]; struct group *g; int index = 0; setgrent(); groups[index++] = basegid; while (index < NGROUPS_MAX && ((g = getgrent()) != NULL)) { if (g->gr_gid != basegid) { char **names; for (names = g->gr_mem; *names != NULL; ++names) { if (!strcmp(*names, name)) groups[index++] = g->gr_gid; } } } endgrent(); return setgroups(index, groups); #endif /* def QNX */ } #endif /* def NEED_INITGROUPS */ #ifdef AP_MPM_USES_POD AP_DECLARE(apr_status_t) ap_mpm_pod_open(apr_pool_t *p, ap_pod_t **pod) { apr_status_t rv; *pod = apr_palloc(p, sizeof(**pod)); rv = apr_file_pipe_create(&((*pod)->pod_in), &((*pod)->pod_out), p); if (rv != APR_SUCCESS) { return rv; } apr_file_pipe_timeout_set((*pod)->pod_in, 0); (*pod)->p = p; apr_sockaddr_info_get(&(*pod)->sa, ap_listeners->bind_addr->hostname, APR_UNSPEC, ap_listeners->bind_addr->port, 0, p); 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) { apr_status_t rv; apr_socket_t *sock; apr_pool_t *p; /* 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, pod->sa->family, SOCK_STREAM, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, "get socket to connect to listener"); 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); return rv; } rv = apr_connect(sock, pod->sa); 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"); } apr_socket_close(sock); apr_pool_destroy(p); return rv; } AP_DECLARE(apr_status_t) ap_mpm_pod_signal(ap_pod_t *pod) { apr_status_t rv; rv = pod_signal_internal(pod); if (rv != APR_SUCCESS) { return rv; } return dummy_connection(pod); } void ap_mpm_pod_killpg(ap_pod_t *pod, int num) { int i; apr_status_t rv = APR_SUCCESS; /* we don't write anything to the pod here... we assume * that the would-be reader of the pod has another way to * see that it is time to die once we wake it up * * writing lots of things to the pod at once is very * problematic... we can fill the kernel pipe buffer and * be blocked until somebody consumes some bytes or * we hit a timeout... if we hit a timeout we can't just * keep trying because maybe we'll never successfully * write again... but then maybe we'll leave would-be * readers stranded (a number of them could be tied up for * a while serving time-consuming requests) */ for (i = 0; i < num && rv == APR_SUCCESS; i++) { rv = dummy_connection(pod); } } #endif /* #ifdef AP_MPM_USES_POD */ /* standard mpm configuration handling */ #ifdef AP_MPM_WANT_SET_PIDFILE const char *ap_pid_fname = NULL; const char *ap_mpm_set_pidfile(cmd_parms *cmd, void *dummy, const char *arg) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } if (cmd->server->is_virtual) { return "PidFile directive not allowed in "; } ap_pid_fname = arg; return NULL; } #endif #ifdef AP_MPM_WANT_SET_SCOREBOARD const char * ap_mpm_set_scoreboard(cmd_parms *cmd, void *dummy, const char *arg) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } ap_scoreboard_fname = arg; return NULL; } #endif #ifdef AP_MPM_WANT_SET_LOCKFILE const char *ap_lock_fname = NULL; const char *ap_mpm_set_lockfile(cmd_parms *cmd, void *dummy, const char *arg) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } ap_lock_fname = arg; return NULL; } #endif #ifdef AP_MPM_WANT_SET_MAX_REQUESTS int ap_max_requests_per_child = 0; const char *ap_mpm_set_max_requests(cmd_parms *cmd, void *dummy, const char *arg) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } ap_max_requests_per_child = atoi(arg); return NULL; } #endif #ifdef AP_MPM_WANT_SET_COREDUMPDIR char ap_coredump_dir[MAX_STRING_LEN]; const char *ap_mpm_set_coredumpdir(cmd_parms *cmd, void *dummy, const char *arg) { apr_status_t rv; apr_finfo_t finfo; const char *fname; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } fname = ap_server_root_relative(cmd->pool, arg); if (!fname) { return apr_pstrcat(cmd->pool, "Invalid CoreDumpDirectory path ", arg, NULL); } if ((rv = apr_stat(&finfo, fname, APR_FINFO_TYPE, cmd->pool)) != APR_SUCCESS) { return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, " does not exist", NULL); } if (finfo.filetype != APR_DIR) { return apr_pstrcat(cmd->pool, "CoreDumpDirectory ", fname, " is not a directory", NULL); } apr_cpystrn(ap_coredump_dir, fname, sizeof(ap_coredump_dir)); return NULL; } #endif #ifdef AP_MPM_WANT_SET_ACCEPT_LOCK_MECH apr_lockmech_e ap_accept_lock_mech = APR_LOCK_DEFAULT; const char ap_valid_accept_mutex_string[] = "Valid accept mutexes for this platform and MPM are: default" #if APR_HAS_FLOCK_SERIALIZE ", flock" #endif #if APR_HAS_FCNTL_SERIALIZE ", fcntl" #endif #if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM) ", sysvsem" #endif #if APR_HAS_POSIXSEM_SERIALIZE ", posixsem" #endif #if APR_HAS_PROC_PTHREAD_SERIALIZE ", pthread" #endif "."; AP_DECLARE(const char *) ap_mpm_set_accept_lock_mech(cmd_parms *cmd, void *dummy, const char *arg) { const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } if (!strcasecmp(arg, "default")) { ap_accept_lock_mech = APR_LOCK_DEFAULT; } #if APR_HAS_FLOCK_SERIALIZE else if (!strcasecmp(arg, "flock")) { ap_accept_lock_mech = APR_LOCK_FLOCK; } #endif #if APR_HAS_FCNTL_SERIALIZE else if (!strcasecmp(arg, "fcntl")) { ap_accept_lock_mech = APR_LOCK_FCNTL; } #endif /* perchild can't use SysV sems because the permissions on the accept * mutex can't be set to allow all processes to use the mutex and * at the same time keep all users from being able to dink with the * mutex */ #if APR_HAS_SYSVSEM_SERIALIZE && !defined(PERCHILD_MPM) else if (!strcasecmp(arg, "sysvsem")) { ap_accept_lock_mech = APR_LOCK_SYSVSEM; } #endif #if APR_HAS_POSIXSEM_SERIALIZE else if (!strcasecmp(arg, "posixsem")) { ap_accept_lock_mech = APR_LOCK_POSIXSEM; } #endif #if APR_HAS_PROC_PTHREAD_SERIALIZE else if (!strcasecmp(arg, "pthread")) { ap_accept_lock_mech = APR_LOCK_PROC_PTHREAD; } #endif else { return apr_pstrcat(cmd->pool, arg, " is an invalid mutex mechanism; ", ap_valid_accept_mutex_string, NULL); } return NULL; } #endif #ifdef AP_MPM_WANT_SIGNAL_SERVER static const char *dash_k_arg; static int send_signal(pid_t pid, int sig) { if (kill(pid, sig) < 0) { ap_log_error(APLOG_MARK, APLOG_STARTUP, errno, NULL, "sending signal to server"); return 1; } return 0; } int ap_signal_server(int *exit_status, apr_pool_t *pconf) { apr_status_t rv; pid_t otherpid; int running = 0; int have_pid_file = 0; const char *status; *exit_status = 0; rv = ap_read_pid(pconf, ap_pid_fname, &otherpid); if (rv != APR_SUCCESS) { if (rv != APR_ENOENT) { ap_log_error(APLOG_MARK, APLOG_STARTUP, rv, NULL, "Error retrieving pid file %s", ap_pid_fname); *exit_status = 1; return 1; } status = "httpd (no pid file) not running"; } else { have_pid_file = 1; if (kill(otherpid, 0) == 0) { running = 1; status = apr_psprintf(pconf, "httpd (pid %" APR_PID_T_FMT ") already " "running", otherpid); } else { status = apr_psprintf(pconf, "httpd (pid %" APR_PID_T_FMT "?) not running", otherpid); } } if (!strcmp(dash_k_arg, "start")) { if (running) { printf("%s\n", status); return 1; } } if (!strcmp(dash_k_arg, "stop")) { if (!running) { printf("%s\n", status); } else { send_signal(otherpid, SIGTERM); } return 1; } if (!strcmp(dash_k_arg, "restart")) { if (!running) { printf("httpd not running, trying to start\n"); } else { *exit_status = send_signal(otherpid, SIGHUP); return 1; } } if (!strcmp(dash_k_arg, "graceful")) { if (!running) { printf("httpd not running, trying to start\n"); } else { *exit_status = send_signal(otherpid, SIGUSR1); return 1; } } return 0; } void ap_mpm_rewrite_args(process_rec *process) { apr_array_header_t *mpm_new_argv; apr_status_t rv; apr_getopt_t *opt; char optbuf[3]; const char *optarg; int fixed_args; mpm_new_argv = apr_array_make(process->pool, process->argc, sizeof(const char **)); *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; fixed_args = mpm_new_argv->nelts; apr_getopt_init(&opt, process->pool, process->argc, process->argv); opt->errfn = NULL; optbuf[0] = '-'; /* option char returned by apr_getopt() will be stored in optbuf[1] */ optbuf[2] = '\0'; while ((rv = apr_getopt(opt, "k:" AP_SERVER_BASEARGS, optbuf + 1, &optarg)) == APR_SUCCESS) { switch(optbuf[1]) { case 'k': if (!dash_k_arg) { if (!strcmp(optarg, "start") || !strcmp(optarg, "stop") || !strcmp(optarg, "restart") || !strcmp(optarg, "graceful")) { dash_k_arg = optarg; break; } } default: *(const char **)apr_array_push(mpm_new_argv) = apr_pstrdup(process->pool, optbuf); if (optarg) { *(const char **)apr_array_push(mpm_new_argv) = optarg; } } } /* back up to capture the bad argument */ if (rv == APR_BADCH || rv == APR_BADARG) { opt->ind--; } while (opt->ind < opt->argc) { *(const char **)apr_array_push(mpm_new_argv) = apr_pstrdup(process->pool, opt->argv[opt->ind++]); } process->argc = mpm_new_argv->nelts; process->argv = (const char * const *)mpm_new_argv->elts; if (dash_k_arg) { APR_REGISTER_OPTIONAL_FN(ap_signal_server); } } #endif /* AP_MPM_WANT_SIGNAL_SERVER */ #ifdef AP_MPM_WANT_SET_MAX_MEM_FREE apr_uint32_t ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED; const char *ap_mpm_set_max_mem_free(cmd_parms *cmd, void *dummy, const char *arg) { long value; const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } value = strtol(arg, NULL, 0); if (value < 0 || errno == ERANGE) return apr_pstrcat(cmd->pool, "Invalid MaxMemFree value: ", arg, NULL); ap_max_mem_free = (apr_uint32_t)value * 1024; return NULL; } #endif /* AP_MPM_WANT_SET_MAX_MEM_FREE */