/* * 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 #include #include #include /* For TCP_NODELAY */ #include #include #define CORE_PRIVATE #include "httpd.h" #include "apr_version.h" #include "apr_thread_proc.h" #include "apr_strings.h" #include "apr_portable.h" #include "apr_pools.h" #include "apr_network_io.h" #include "ap_mpm.h" #include "http_config.h" #include "mpm_common.h" #include "util_script.h" #include "unixd.h" #include "mod_core.h" #include "mod_cgi.h" #include "apr_tables.h" #include "fcgid_proc.h" #include "fcgid_proctbl.h" #include "fcgid_protocol.h" #include "fcgid_conf.h" #include "fcgid_pm.h" #include "fcgid_spawn_ctl.h" #if MODULE_MAGIC_NUMBER_MAJOR < 20081201 #define ap_unixd_config unixd_config #endif #if APR_MAJOR_VERSION < 1 #define APR_FPROT_UWRITE APR_UWRITE #define APR_FPROT_UREAD APR_UREAD #define APR_FPROT_UEXECUTE APR_UEXECUTE #endif #define DEFAULT_FCGID_LISTENBACKLOG 5 typedef struct { int handle_socket; } fcgid_namedpipe_handle; static int g_process_counter = 0; static apr_status_t ap_unix_create_privileged_process(apr_proc_t *newproc, const char *progname, const char *const *args, const char *const *env, apr_procattr_t *attr, ap_unix_identity_t *ugid, apr_pool_t *p) { int i = 0; const char **newargs; const char *newprogname; const char *execuser, *execgroup; const char *argv0; if (!ap_unixd_config.suexec_enabled) { return apr_proc_create(newproc, progname, args, env, attr, p); } argv0 = ap_strrchr_c(progname, '/'); /* Allow suexec's "/" check to succeed */ if (argv0 != NULL) { argv0++; } else { argv0 = progname; } if (ugid->userdir) { execuser = apr_psprintf(p, "~%ld", (long) ugid->uid); } else { execuser = apr_psprintf(p, "%ld", (long) ugid->uid); } execgroup = apr_psprintf(p, "%ld", (long) ugid->gid); if (!execuser || !execgroup) { return APR_ENOMEM; } i = 0; while (args[i]) { i++; } /* allocate space for 4 new args, the input args, and a null terminator */ newargs = apr_palloc(p, sizeof(char *) * (i + 4)); newprogname = SUEXEC_BIN; newargs[0] = SUEXEC_BIN; newargs[1] = execuser; newargs[2] = execgroup; newargs[3] = apr_pstrdup(p, argv0); /* ** using a shell to execute suexec makes no sense thus ** we force everything to be APR_PROGRAM, and never ** APR_SHELLCMD */ if (apr_procattr_cmdtype_set(attr, APR_PROGRAM) != APR_SUCCESS) { return APR_EGENERAL; } i = 1; do { newargs[i + 3] = args[i]; } while (args[i++]); return apr_proc_create(newproc, newprogname, newargs, env, attr, p); } static apr_status_t fcgid_create_privileged_process(apr_proc_t *newproc, const char *progname, const char *const *args, const char *const *env, apr_procattr_t *attr, fcgid_proc_info *procinfo, apr_pool_t *p) { ap_unix_identity_t ugid; if (!ap_unixd_config.suexec_enabled || (procinfo->uid == (uid_t) - 1 && procinfo->gid == (gid_t) - 1)) { return apr_proc_create(newproc, progname, args, env, attr, p); } ugid.gid = procinfo->gid; ugid.uid = procinfo->uid; ugid.userdir = procinfo->userdir; return ap_unix_create_privileged_process(newproc, progname, args, env, attr, &ugid, p); } static apr_status_t socket_file_cleanup(void *theprocnode) { fcgid_procnode *procnode = (fcgid_procnode *) theprocnode; unlink(procnode->socket_path); return APR_SUCCESS; } static void log_setid_failure(const char *proc_type, const char *id_type, uid_t user_id) { char errno_desc[120]; char errmsg[240]; apr_strerror(errno, errno_desc, sizeof errno_desc); apr_snprintf(errmsg, sizeof errmsg, "(%d)%s: %s unable to set %s to %ld\n", errno, errno_desc, proc_type, id_type, (long)user_id); write(STDERR_FILENO, errmsg, strlen(errmsg)); } /* When suexec is enabled, this runs in the forked child * process prior to exec(). */ static apr_status_t exec_setuid_cleanup(void *dummy) { if (seteuid(0) == -1) { log_setid_failure("mod_fcgid child", "effective uid", 0); _exit(1); } if (setuid(ap_unixd_config.user_id) == -1) { log_setid_failure("mod_fcgid child", "uid", ap_unixd_config.user_id); _exit(1); } return APR_SUCCESS; } apr_status_t proc_spawn_process(const char *cmdline, fcgid_proc_info *procinfo, fcgid_procnode *procnode) { server_rec *main_server = procinfo->main_server; fcgid_server_conf *sconf = ap_get_module_config(main_server->module_config, &fcgid_module); apr_status_t rv = APR_SUCCESS; apr_file_t *file; apr_proc_t tmpproc; int omask, retcode, unix_socket; char **proc_environ; struct sockaddr_un unix_addr; apr_procattr_t *procattr = NULL; int len; const char **wargv; /* Build wrapper args */ apr_tokenize_to_argv(cmdline, (char ***)&wargv, procnode->proc_pool); /* Create UNIX domain socket before spawn */ /* Generate a UNIX domain socket file path */ memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sun_family = AF_UNIX; len = apr_snprintf(unix_addr.sun_path, sizeof(unix_addr.sun_path), "%s/%" APR_PID_T_FMT ".%d", sconf->sockname_prefix, getpid(), g_process_counter++); /* check for truncation of the socket path * * cheap but overly zealous check for sun_path overflow: if length of * prepared string is at the limit, assume truncation */ if (len + 1 == sizeof(unix_addr.sun_path) || len >= sizeof procnode->socket_path) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server, "mod_fcgid: socket path length exceeds compiled-in limits"); return APR_EGENERAL; } apr_cpystrn(procnode->socket_path, unix_addr.sun_path, sizeof(procnode->socket_path)); /* truncation already checked for in handler or FcgidWrapper parser */ AP_DEBUG_ASSERT(wargv[0] != NULL); AP_DEBUG_ASSERT(strlen(wargv[0]) < sizeof(procnode->executable_path)); apr_cpystrn(procnode->executable_path, wargv[0], sizeof(procnode->executable_path)); /* Unlink the file just in case */ unlink(unix_addr.sun_path); /* Create the socket */ if ((unix_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, "mod_fcgid: couldn't create unix domain socket"); return errno; } /* Register cleanups to * 1. Unlink the socket when the process exits * 2. (suexec mode only, in the child cleanup) Switch to the configured uid */ if (ap_unixd_config.suexec_enabled) { apr_pool_cleanup_register(procnode->proc_pool, procnode, socket_file_cleanup, exec_setuid_cleanup); } else { apr_pool_cleanup_register(procnode->proc_pool, procnode, socket_file_cleanup, apr_pool_cleanup_null); } /* Bind the socket */ omask = umask(0077); retcode = bind(unix_socket, (struct sockaddr *) &unix_addr, sizeof(unix_addr)); umask(omask); if (retcode < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, "mod_fcgid: couldn't bind unix domain socket %s", unix_addr.sun_path); close(unix_socket); return errno; } /* IPC directory permissions are safe, but avoid confusion */ /* Not all flavors of unix use the current umask for AF_UNIX perms */ rv = apr_file_perms_set(unix_addr.sun_path, APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, main_server, "mod_fcgid: Couldn't set permissions on unix domain socket %s", unix_addr.sun_path); return rv; } /* Listen the socket */ if (listen(unix_socket, DEFAULT_FCGID_LISTENBACKLOG) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, "mod_fcgid: couldn't listen on unix domain socket"); close(unix_socket); return errno; } /* Correct the file owner */ if (!geteuid()) { if (chown(unix_addr.sun_path, ap_unixd_config.user_id, -1) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server, "mod_fcgid: couldn't change owner of unix domain socket %s", unix_addr.sun_path); close(unix_socket); return errno; } } { int oldflags = fcntl(unix_socket, F_GETFD, 0); if (oldflags < 0) { ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), procinfo->main_server, "mod_fcgid: fcntl F_GETFD failed"); close(unix_socket); return errno; } oldflags |= FD_CLOEXEC; if (fcntl(unix_socket, F_SETFD, oldflags) < 0) { ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), procinfo->main_server, "mod_fcgid: fcntl F_SETFD failed"); close(unix_socket); return errno; } } /* Build environment variables */ proc_environ = ap_create_environment(procnode->proc_pool, procinfo->proc_environ); if (!proc_environ) { ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), procinfo->main_server, "mod_fcgid: can't build environment variables"); close(unix_socket); return APR_ENOMEM; } /* Prepare the fork */ if ((rv = apr_procattr_create(&procattr, procnode->proc_pool)) != APR_SUCCESS || (rv = apr_procattr_child_err_set(procattr, procinfo->main_server->error_log, NULL)) != APR_SUCCESS || (rv = apr_procattr_child_out_set(procattr, procinfo->main_server->error_log, NULL)) != APR_SUCCESS || (rv = apr_procattr_dir_set(procattr, ap_make_dirstr_parent(procnode->proc_pool, wargv[0]))) != APR_SUCCESS || (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) != APR_SUCCESS || (rv = apr_os_file_put(&file, &unix_socket, 0, procnode->proc_pool)) != APR_SUCCESS || (rv = apr_procattr_child_in_set(procattr, file, NULL)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server, "mod_fcgid: couldn't set child process attributes: %s", unix_addr.sun_path); close(unix_socket); return rv; } /* fork and exec now */ /* Note, don't pass &(procnode->proc_id) to fcgid_create_privileged_process(), * for it's a share memory address, both parent and child process may modify * procnode->proc_id->pid, so sometimes it's 0 and sometimes it's >0 */ rv = fcgid_create_privileged_process(&tmpproc, wargv[0], wargv, (const char *const *)proc_environ, procattr, procinfo, procnode->proc_pool); if (ap_unixd_config.suexec_enabled) { /* Prior to creating the child process, a child cleanup was registered * to switch the uid in the child. No-op the child cleanup for this * pool so that it won't run again as other child processes are created. * (The cleanup will be registered for the pool associated with those * processes too.) */ apr_pool_child_cleanup_set(procnode->proc_pool, procnode, socket_file_cleanup, apr_pool_cleanup_null); } /* Close socket before try to connect to it */ close(unix_socket); procnode->proc_id = tmpproc; if (rv != APR_SUCCESS) { memset(&procnode->proc_id, 0, sizeof(procnode->proc_id)); ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server, "mod_fcgid: can't run %s", wargv[0]); } return rv; } static apr_status_t proc_kill_internal(fcgid_procnode *procnode, int sig) { /* su as root before sending signal, for suEXEC */ apr_status_t rv; if (procnode->proc_id.pid == 0) { /* procnode->proc_id.pid be 0 while fcgid_create_privileged_process() fail */ return APR_SUCCESS; } if (ap_unixd_config.suexec_enabled && seteuid(0) != 0) { /* can't gain privileges to send signal (should not occur); do NOT * proceed, as something is broken with current identity */ log_setid_failure("mod_fcgid PM", "effective uid", 0); _exit(1); } rv = apr_proc_kill(&(procnode->proc_id), sig); if (ap_unixd_config.suexec_enabled && seteuid(ap_unixd_config.user_id) != 0) { /* can't drop privileges after signalling (should not occur); do NOT * proceed any further as euid(0)! */ log_setid_failure("mod_fcgid PM", "effective uid", ap_unixd_config.user_id); _exit(1); } return rv; } apr_status_t proc_kill_gracefully(fcgid_procnode *procnode, server_rec *main_server) { return proc_kill_internal(procnode, SIGTERM); } apr_status_t proc_kill_force(fcgid_procnode * procnode, server_rec * main_server) { return proc_kill_internal(procnode, SIGKILL); } apr_status_t proc_wait_process(server_rec *main_server, fcgid_procnode *procnode) { apr_status_t rv; int exitcode; apr_exit_why_e exitwhy; rv = apr_proc_wait(&(procnode->proc_id), &exitcode, &exitwhy, APR_NOWAIT); if (rv == APR_CHILD_DONE || rv == APR_EGENERAL) { /* Log why and how it die */ proc_print_exit_info(procnode, exitcode, exitwhy, main_server); /* Register the death */ register_termination(main_server, procnode); /* Destroy pool */ apr_pool_destroy(procnode->proc_pool); procnode->proc_pool = NULL; memset(&procnode->proc_id, 0, sizeof(procnode->proc_id)); return APR_CHILD_DONE; } return rv; } static apr_status_t ipc_handle_cleanup(void *thesocket) { fcgid_namedpipe_handle *handle_info = (fcgid_namedpipe_handle *) thesocket; if (handle_info) { if (handle_info->handle_socket != -1) { close(handle_info->handle_socket); handle_info->handle_socket = -1; } } return APR_SUCCESS; } static apr_status_t set_socket_nonblock(int sd) { #ifndef BEOS int fd_flags; fd_flags = fcntl(sd, F_GETFL, 0); #if defined(O_NONBLOCK) fd_flags |= O_NONBLOCK; #elif defined(O_NDELAY) fd_flags |= O_NDELAY; #elif defined(FNDELAY) fd_flags |= FNDELAY; #else #error Please teach APR how to make sockets non-blocking on your platform. #endif if (fcntl(sd, F_SETFL, fd_flags) == -1) { return errno; } #else int on = 1; if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0) return errno; #endif /* BEOS */ return APR_SUCCESS; } apr_status_t proc_connect_ipc(fcgid_procnode *procnode, fcgid_ipc *ipc_handle) { fcgid_namedpipe_handle *handle_info; struct sockaddr_un unix_addr; apr_status_t rv; /* Alloc memory for unix domain socket */ ipc_handle->ipc_handle_info = (fcgid_namedpipe_handle *) apr_pcalloc(ipc_handle->request->pool, sizeof (fcgid_namedpipe_handle)); handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; handle_info->handle_socket = socket(AF_UNIX, SOCK_STREAM, 0); apr_pool_cleanup_register(ipc_handle->request->pool, handle_info, ipc_handle_cleanup, apr_pool_cleanup_null); /* Connect to fastcgi server */ memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sun_family = AF_UNIX; /* PM already made this check for truncation */ AP_DEBUG_ASSERT(sizeof unix_addr.sun_path > strlen(procnode->socket_path)); apr_cpystrn(unix_addr.sun_path, procnode->socket_path, sizeof(unix_addr.sun_path)); /* I am the only one who connecting the server So I don't have to worry about ECONNREFUSED(listen queue overflow) problem, and I will never retry on error */ if (connect(handle_info->handle_socket, (struct sockaddr *) &unix_addr, sizeof(unix_addr)) < 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, apr_get_os_error(), ipc_handle->request, "mod_fcgid: can't connect unix domain socket: %s", procnode->socket_path); return APR_ECONNREFUSED; } /* Set nonblock option */ if ((rv = set_socket_nonblock(handle_info->handle_socket)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, ipc_handle->request, "mod_fcgid: can't make unix domain socket nonblocking"); return rv; } return APR_SUCCESS; } apr_status_t proc_close_ipc(fcgid_ipc * ipc_handle) { apr_status_t rv; rv = apr_pool_cleanup_run(ipc_handle->request->pool, ipc_handle->ipc_handle_info, ipc_handle_cleanup); ipc_handle->ipc_handle_info = NULL; return rv; } apr_status_t proc_read_ipc(fcgid_ipc *ipc_handle, const char *buffer, apr_size_t *size) { int retcode, unix_socket; fcgid_namedpipe_handle *handle_info; struct pollfd pollfds[1]; handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; unix_socket = handle_info->handle_socket; do { if ((retcode = read(unix_socket, (void *) buffer, *size)) > 0) { *size = retcode; return APR_SUCCESS; } } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); if (retcode == -1 && !APR_STATUS_IS_EAGAIN(errno)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, ipc_handle->request, "mod_fcgid: error reading data from FastCGI server"); return errno; } /* I have to wait a while */ pollfds[0].fd = unix_socket; pollfds[0].events = POLLIN; do { retcode = poll(pollfds, 1, ipc_handle->communation_timeout * 1000); } while (retcode <= 0 && APR_STATUS_IS_EINTR(errno)); if (retcode == -1) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, ipc_handle->request, "mod_fcgid: error polling unix domain socket"); return errno; } else if (retcode == 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ipc_handle->request, "mod_fcgid: read data timeout in %d seconds", ipc_handle->communation_timeout); return APR_ETIMEDOUT; } do { if ((retcode = read(unix_socket, (void *) buffer, *size)) > 0) { *size = retcode; return APR_SUCCESS; } } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); if (retcode == 0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ipc_handle->request, "mod_fcgid: error reading data, FastCGI server closed connection"); return APR_EPIPE; } ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, ipc_handle->request, "mod_fcgid: error reading data from FastCGI server"); return errno; } static apr_status_t socket_writev(fcgid_ipc *ipc_handle, struct iovec *vec, int nvec, int *writecnt) { apr_status_t rv; int retcode, unix_socket; fcgid_namedpipe_handle *handle_info; struct pollfd pollfds[1]; handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; unix_socket = handle_info->handle_socket; /* Try nonblock write */ do { if ((retcode = writev(unix_socket, vec, nvec)) > 0) { *writecnt = retcode; return APR_SUCCESS; } } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); rv = errno; if (APR_STATUS_IS_EAGAIN(rv)) { /* poll() */ pollfds[0].fd = unix_socket; pollfds[0].events = POLLOUT; do { retcode = poll(pollfds, 1, ipc_handle->communation_timeout * 1000); } while (retcode < 0 && APR_STATUS_IS_EINTR(errno)); if (retcode < 0) { rv = errno; } else if (retcode == 0) { rv = APR_TIMEUP; } else { /* Write again */ do { if ((retcode = writev(unix_socket, vec, nvec)) > 0) { *writecnt = retcode; return APR_SUCCESS; } } while (retcode == -1 && APR_STATUS_IS_EINTR(errno)); rv = errno; } } if (APR_STATUS_IS_EAGAIN(rv)) { /* socket is writable, but we can't write the entire buffer; try to write a * smaller amount, and if even that fails then sleep */ size_t to_write = vec[0].iov_len; int slept = 0; const apr_interval_time_t sleep_time = APR_USEC_PER_SEC / 4; const int max_sleeps = 8; do { if ((retcode = write(unix_socket, vec[0].iov_base, to_write)) > 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ipc_handle->request, "wrote %d byte(s) and slept %d time(s) after EAGAIN", retcode, slept); *writecnt = retcode; return APR_SUCCESS; } if (APR_STATUS_IS_EAGAIN(errno)) { if (to_write == 1) { apr_sleep(sleep_time); ++slept; } else { to_write /= 2; } } } while ((APR_STATUS_IS_EINTR(errno) || APR_STATUS_IS_EAGAIN(errno)) && slept < max_sleeps); rv = errno; } ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, ipc_handle->request, "mod_fcgid: error writing data to FastCGI server"); return rv; } static apr_status_t writev_it_all(fcgid_ipc *ipc_handle, struct iovec *vec, int nvec) { apr_size_t bytes_written = 0; apr_status_t rv; apr_size_t len = 0; int i = 0; int writecnt = 0; /* Calculate the total size */ for (i = 0; i < nvec; i++) { len += vec[i].iov_len; } i = 0; while (bytes_written != len) { rv = socket_writev(ipc_handle, vec + i, nvec - i, &writecnt); if (rv != APR_SUCCESS) return rv; bytes_written += writecnt; if (bytes_written < len) { /* Skip over the vectors that have already been written */ apr_size_t cnt = vec[i].iov_len; while (writecnt >= cnt && i + 1 < nvec) { i++; cnt += vec[i].iov_len; } if (writecnt < cnt) { /* Handle partial write of vec i */ vec[i].iov_base = (char *) vec[i].iov_base + (vec[i].iov_len - (cnt - writecnt)); vec[i].iov_len = cnt - writecnt; } } } return APR_SUCCESS; } #define FCGID_VEC_COUNT 8 apr_status_t proc_write_ipc(fcgid_ipc *ipc_handle, apr_bucket_brigade *output_brigade) { apr_status_t rv; struct iovec vec[FCGID_VEC_COUNT]; int nvec = 0; apr_bucket *e; for (e = APR_BRIGADE_FIRST(output_brigade); e != APR_BRIGADE_SENTINEL(output_brigade); e = APR_BUCKET_NEXT(e)) { apr_size_t len; const char* base; if (APR_BUCKET_IS_METADATA(e)) { continue; } if ((rv = apr_bucket_read(e, &base, &len, APR_BLOCK_READ)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, ipc_handle->request, "mod_fcgid: can't read request from bucket"); return rv; } vec[nvec].iov_len = len; vec[nvec].iov_base = (char*) base; if (nvec == (FCGID_VEC_COUNT - 1)) { /* It's time to write now */ if ((rv = writev_it_all(ipc_handle, vec, FCGID_VEC_COUNT)) != APR_SUCCESS) return rv; nvec = 0; } else nvec++; } /* There are something left */ if (nvec != 0) { if ((rv = writev_it_all(ipc_handle, vec, nvec)) != APR_SUCCESS) return rv; } return APR_SUCCESS; } void proc_print_exit_info(fcgid_procnode *procnode, int exitcode, apr_exit_why_e exitwhy, server_rec *main_server) { const char *diewhy = NULL; char signal_info[HUGE_STRING_LEN]; int signum = exitcode; int loglevel = APLOG_INFO; memset(signal_info, 0, HUGE_STRING_LEN); /* Reasons to exit */ switch (procnode->diewhy) { case FCGID_DIE_KILLSELF: diewhy = "normal exit"; break; case FCGID_DIE_IDLE_TIMEOUT: diewhy = "idle timeout"; break; case FCGID_DIE_LIFETIME_EXPIRED: diewhy = "lifetime expired"; break; case FCGID_DIE_BUSY_TIMEOUT: diewhy = "busy timeout"; break; case FCGID_DIE_CONNECT_ERROR: diewhy = "connect error"; break; case FCGID_DIE_COMM_ERROR: diewhy = "communication error"; break; case FCGID_DIE_SHUTDOWN: diewhy = "shutting down"; break; default: loglevel = APLOG_ERR; diewhy = "unknown"; } /* Get signal info */ if (APR_PROC_CHECK_SIGNALED(exitwhy)) { switch (signum) { case SIGTERM: case SIGHUP: case AP_SIG_GRACEFUL: case SIGKILL: apr_snprintf(signal_info, HUGE_STRING_LEN - 1, "get stop signal %d", signum); break; default: loglevel = APLOG_ERR; if (APR_PROC_CHECK_CORE_DUMP(exitwhy)) { apr_snprintf(signal_info, HUGE_STRING_LEN - 1, "get signal %d, possible coredump generated", signum); } else { apr_snprintf(signal_info, HUGE_STRING_LEN - 1, "get unexpected signal %d", signum); } } } else if (APR_PROC_CHECK_EXIT(exitwhy)) { apr_snprintf(signal_info, HUGE_STRING_LEN - 1, "terminated by calling exit(), return code: %d", exitcode); if (procnode->diewhy == FCGID_DIE_CONNECT_ERROR) diewhy = "server exited"; } /* Print log now */ ap_log_error(APLOG_MARK, loglevel, 0, main_server, "mod_fcgid: process %s(%" APR_PID_T_FMT ") exit(%s), %s", procnode->executable_path, procnode->proc_id.pid, diewhy, signal_info); }