From 270412818e83e503d78985e1d156728ed9144ccd Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 29 2020 09:01:34 +0000 Subject: Apply patch mod_fcgid-2.3.9-r1847624.patch patch_name: mod_fcgid-2.3.9-r1847624.patch present_in_specfile: true --- diff --git a/modules/fcgid/fcgid_proc_unix.c b/modules/fcgid/fcgid_proc_unix.c index 218f3f7..7f37495 100644 --- a/modules/fcgid/fcgid_proc_unix.c +++ b/modules/fcgid/fcgid_proc_unix.c @@ -762,14 +762,18 @@ apr_status_t proc_write_ipc(fcgid_ipc *ipc_handle, struct iovec vec[FCGID_VEC_COUNT]; int nvec = 0; apr_bucket *e; + apr_bucket_brigade *tmpbb = apr_brigade_create(output_brigade->p, + output_brigade->bucket_alloc); + + while (!APR_BRIGADE_EMPTY(output_brigade)) + { + e = APR_BRIGADE_FIRST(output_brigade); - 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)) { + apr_bucket_delete(e); continue; } @@ -780,6 +784,9 @@ apr_status_t proc_write_ipc(fcgid_ipc *ipc_handle, return rv; } + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(tmpbb, e); + vec[nvec].iov_len = len; vec[nvec].iov_base = (char*) base; if (nvec == (FCGID_VEC_COUNT - 1)) { @@ -789,6 +796,7 @@ apr_status_t proc_write_ipc(fcgid_ipc *ipc_handle, FCGID_VEC_COUNT)) != APR_SUCCESS) return rv; nvec = 0; + apr_brigade_cleanup(tmpbb); } else nvec++; @@ -800,6 +808,7 @@ apr_status_t proc_write_ipc(fcgid_ipc *ipc_handle, return rv; } + apr_brigade_destroy(tmpbb); return APR_SUCCESS; } diff --git a/modules/fcgid/fcgid_proc_unix.c.r1847624 b/modules/fcgid/fcgid_proc_unix.c.r1847624 new file mode 100644 index 0000000..218f3f7 --- /dev/null +++ b/modules/fcgid/fcgid_proc_unix.c.r1847624 @@ -0,0 +1,879 @@ +/* + * 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); +} diff --git a/modules/fcgid/fcgid_proc_win.c b/modules/fcgid/fcgid_proc_win.c index 60b26a6..5b3b33a 100644 --- a/modules/fcgid/fcgid_proc_win.c +++ b/modules/fcgid/fcgid_proc_win.c @@ -380,19 +380,22 @@ apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, apr_bucket *bucket_request; apr_status_t rv; DWORD transferred; + apr_bucket_brigade *tmpbb = apr_brigade_create(birgade_send->p, + birgade_send->bucket_alloc); handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; - for (bucket_request = APR_BRIGADE_FIRST(birgade_send); - bucket_request != APR_BRIGADE_SENTINEL(birgade_send); - bucket_request = APR_BUCKET_NEXT(bucket_request)) - { + while (!APR_BRIGADE_EMPTY(birgade_send)) { const char *write_buf; apr_size_t write_buf_len; apr_size_t has_write; - if (APR_BUCKET_IS_METADATA(bucket_request)) + bucket_request = APR_BRIGADE_FIRST(birgade_send); + + if (APR_BUCKET_IS_METADATA(bucket_request)) { + apr_bucket_delete(bucket_request); continue; + } if ((rv = apr_bucket_read(bucket_request, &write_buf, &write_buf_len, APR_BLOCK_READ)) != APR_SUCCESS) { @@ -401,6 +404,9 @@ apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, return rv; } + APR_BUCKET_REMOVE(bucket_request); + APR_BRIGADE_INSERT_TAIL(tmpbb, bucket_request); + /* Write the buffer to fastcgi server */ has_write = 0; while (has_write < write_buf_len) { @@ -411,6 +417,7 @@ apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, write_buf_len - has_write, &byteswrite, &handle_info->overlap_write)) { has_write += byteswrite; + apr_brigade_cleanup(tmpbb); continue; } else if ((rv = GetLastError()) != ERROR_IO_PENDING) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, @@ -437,6 +444,7 @@ apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, return APR_ESPIPE; } has_write += transferred; + apr_brigade_cleanup(tmpbb); continue; } else { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, @@ -448,6 +456,7 @@ apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, } } + apr_brigade_destroy(tmpbb); return APR_SUCCESS; } diff --git a/modules/fcgid/fcgid_proc_win.c.r1847624 b/modules/fcgid/fcgid_proc_win.c.r1847624 new file mode 100644 index 0000000..60b26a6 --- /dev/null +++ b/modules/fcgid/fcgid_proc_win.c.r1847624 @@ -0,0 +1,493 @@ +/* + * 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 "apr_thread_proc.h" +#include "apr_strings.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "util_script.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" +#define SHUTDOWN_EVENT_NAME "_FCGI_SHUTDOWN_EVENT_" +#define FINISH_EVENT_DATA_NAME "finish_event" +#ifndef SIGKILL +#define SIGKILL 9 +#endif + +/* It's tested on WinNT ONLY, if it work on the other MS platform, let me know */ +#if WINVER < 0x0400 +#error It is tested on WinNT only +#endif + +typedef struct { + HANDLE handle_pipe; + OVERLAPPED overlap_read; + OVERLAPPED overlap_write; +} fcgid_namedpipe_handle; + +static int g_process_counter = 0; + +static apr_status_t close_finish_event(void *finishevent) +{ + HANDLE *finish_event = finishevent; + + CloseHandle(*finish_event); + return APR_SUCCESS; +} + +apr_status_t proc_spawn_process(const char *cmdline, fcgid_proc_info *procinfo, + fcgid_procnode *procnode) +{ + HANDLE *finish_event, listen_handle; + SECURITY_ATTRIBUTES SecurityAttributes; + fcgid_server_conf *sconf; + apr_procattr_t *proc_attr; + apr_status_t rv; + apr_file_t *file; + const char * const *proc_environ; + char sock_path[FCGID_PATH_MAX]; + const char **wargv; + + /* Build wrapper args */ + apr_tokenize_to_argv(cmdline, (char ***)&wargv, procnode->proc_pool); + + memset(&SecurityAttributes, 0, sizeof(SecurityAttributes)); + + /* Prepare finish event */ + finish_event = apr_palloc(procnode->proc_pool, sizeof(HANDLE)); + *finish_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (*finish_event == NULL + || !SetHandleInformation(*finish_event, HANDLE_FLAG_INHERIT, TRUE)) + { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: can't create mutex for subprocess"); + return APR_ENOLOCK; + } + apr_pool_cleanup_register(procnode->proc_pool, finish_event, + close_finish_event, apr_pool_cleanup_null); + + /* For proc_kill_gracefully() */ + apr_pool_userdata_set(finish_event, FINISH_EVENT_DATA_NAME, + NULL, procnode->proc_pool); + + /* Pass the finish event id to subprocess */ + apr_table_setn(procinfo->proc_environ, SHUTDOWN_EVENT_NAME, + apr_ltoa(procnode->proc_pool, (long) *finish_event)); + + /* Prepare the listen namedpipe file name (no check for truncation) */ + apr_snprintf(sock_path, sizeof sock_path, + "\\\\.\\pipe\\fcgidpipe-%lu.%d", + GetCurrentProcessId(), g_process_counter++); + + /* Prepare the listen namedpipe handle */ + SecurityAttributes.bInheritHandle = TRUE; + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.lpSecurityDescriptor = NULL; + listen_handle = CreateNamedPipe(sock_path, + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | + PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, + 8192, 8192, 0, &SecurityAttributes); + if (listen_handle == INVALID_HANDLE_VALUE) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: can't create namedpipe for subprocess"); + return APR_ENOSOCKET; + } + apr_cpystrn(procnode->socket_path, sock_path, sizeof(procnode->socket_path)); + apr_cpystrn(procnode->executable_path, wargv[0], + sizeof(procnode->executable_path)); + + /* Build environment variables */ + proc_environ = (const char * const *) + 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"); + return APR_ENOMEM; + } + + /* Create process now */ + if ((rv = apr_procattr_create(&proc_attr, procnode->proc_pool)) + != APR_SUCCESS + || (rv = apr_procattr_dir_set(proc_attr, + ap_make_dirstr_parent(procnode->proc_pool, + wargv[0]))) != APR_SUCCESS + || (rv = apr_procattr_cmdtype_set(proc_attr, APR_PROGRAM)) + != APR_SUCCESS + || (rv = apr_procattr_detach_set(proc_attr, 1)) != APR_SUCCESS + || (rv = apr_procattr_io_set(proc_attr, APR_NO_PIPE, + APR_NO_FILE, APR_NO_FILE)) != APR_SUCCESS + || (rv = apr_os_file_put(&file, &listen_handle, 0, + procnode->proc_pool)) != APR_SUCCESS + || (rv = apr_procattr_child_in_set(proc_attr, file, NULL)) + != APR_SUCCESS) + { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, procinfo->main_server, + "mod_fcgid: can't create FastCGI process attribute"); + CloseHandle(listen_handle); + return APR_ENOPROC; + } + + /* fork and exec now */ + rv = apr_proc_create(&(procnode->proc_id), wargv[0], wargv, + proc_environ, + proc_attr, procnode->proc_pool); + + /* OK, I created the process, now put it back to idle list */ + CloseHandle(listen_handle); + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, procinfo->main_server, + "mod_fcgid: can't run %s", wargv[0]); + return rv; + } + + /* FcgidWin32PreventOrphans feature */ + sconf = ap_get_module_config(procinfo->main_server->module_config, + &fcgid_module); + + if (sconf->hJobObjectForAutoCleanup != NULL) { + /* Associate cgi process to current process */ + if (AssignProcessToJobObject(sconf->hJobObjectForAutoCleanup, + procnode->proc_id.hproc) == 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + procinfo->main_server, + "mod_fcgid: unable to assign child process to " + "job object"); + } + } + + return APR_SUCCESS; +} + +apr_status_t proc_kill_gracefully(fcgid_procnode *procnode, + server_rec *main_server) +{ + HANDLE *finish_event = NULL; + + apr_pool_userdata_get((void **) &finish_event, + FINISH_EVENT_DATA_NAME, procnode->proc_pool); + + if (finish_event != NULL) + SetEvent(*finish_event); + return APR_SUCCESS; +} + +apr_status_t proc_kill_force(fcgid_procnode *procnode, + server_rec *main_server) +{ + return apr_proc_kill(&(procnode->proc_id), 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; + + if ((rv = apr_proc_wait(&(procnode->proc_id), &exitcode, &exitwhy, + APR_NOWAIT)) == APR_CHILD_DONE) { + /* 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; + } + + return rv; +} + +static apr_status_t ipc_handle_cleanup(void *thehandle) +{ + fcgid_namedpipe_handle *handle = thehandle; + + /* Sanity check */ + if (handle) { + if (handle->handle_pipe != INVALID_HANDLE_VALUE) + CloseHandle(handle->handle_pipe); + if (handle->overlap_read.hEvent != NULL) + CloseHandle(handle->overlap_read.hEvent); + if (handle->overlap_write.hEvent != NULL) + CloseHandle(handle->overlap_write.hEvent); + handle->handle_pipe = INVALID_HANDLE_VALUE; + handle->overlap_read.hEvent = NULL; + handle->overlap_write.hEvent = NULL; + } + + return APR_SUCCESS; +} + +apr_status_t proc_connect_ipc(fcgid_procnode *procnode, fcgid_ipc *ipc_handle) +{ + /* Prepare the ipc struct */ + fcgid_namedpipe_handle *handle_info; + + 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; + + /* Prepare OVERLAPPED struct for non-block I/O */ + handle_info->overlap_read.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + handle_info->overlap_write.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + handle_info->handle_pipe = INVALID_HANDLE_VALUE; + + apr_pool_cleanup_register(ipc_handle->request->pool, + handle_info, + ipc_handle_cleanup, apr_pool_cleanup_null); + + if (handle_info->overlap_read.hEvent == NULL + || handle_info->overlap_write.hEvent == NULL) + return APR_ENOMEM; + + /* Connect to name pipe */ + handle_info->handle_pipe = CreateFile(procnode->socket_path, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (handle_info->handle_pipe == INVALID_HANDLE_VALUE + && ipc_handle->connect_timeout != 0 + && GetLastError() == ERROR_PIPE_BUSY) + { + /* XXX - there appears to be a race, here + * Wait for pipe to be ready for connect, and try again + */ + if (WaitNamedPipe(procnode->socket_path, ipc_handle->connect_timeout)) + { + handle_info->handle_pipe = CreateFile(procnode->socket_path, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + } + } + + if (handle_info->handle_pipe == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) /* The process has exited */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ipc_handle->request, + "mod_fcgid: can't connect to named pipe, FastCGI" + " server %" APR_PID_T_FMT " has been terminated", + procnode->proc_id.pid); + else + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, apr_get_os_error(), + ipc_handle->request, + "mod_fcgid: can't connect to named pipe, FastCGI" + " server pid %" APR_PID_T_FMT, + procnode->proc_id.pid); + return APR_ESPIPE; + } + + /* Now named pipe connected */ + 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) +{ + apr_status_t rv; + fcgid_namedpipe_handle *handle_info; + DWORD bytesread; + + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + + if (ReadFile(handle_info->handle_pipe, (LPVOID) buffer, + *size, &bytesread, &handle_info->overlap_read)) { + *size = bytesread; + return APR_SUCCESS; + } else if ((rv = GetLastError()) != ERROR_IO_PENDING) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_FROM_OS_ERROR(rv), + ipc_handle->request, + "mod_fcgid: can't read from pipe"); + return rv; + } else { + /* it's ERROR_IO_PENDING */ + DWORD transferred; + DWORD dwWaitResult + = WaitForSingleObject(handle_info->overlap_read.hEvent, + ipc_handle->communation_timeout * 1000); + + if (dwWaitResult == WAIT_OBJECT_0) { + if (!GetOverlappedResult(handle_info->handle_pipe, + &handle_info->overlap_read, + &transferred, FALSE /* don't wait */ ) + || transferred == 0) { + rv = apr_get_os_error(); + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, + ipc_handle->request, + "mod_fcgid: get overlap result error"); + return rv; + } + + *size = transferred; + return APR_SUCCESS; + } else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ipc_handle->request, + "mod_fcgid: read timeout from pipe"); + return APR_ETIMEDOUT; + } + } +} + +apr_status_t proc_write_ipc(fcgid_ipc * ipc_handle, + apr_bucket_brigade * birgade_send) +{ + fcgid_namedpipe_handle *handle_info; + apr_bucket *bucket_request; + apr_status_t rv; + DWORD transferred; + + handle_info = (fcgid_namedpipe_handle *) ipc_handle->ipc_handle_info; + + for (bucket_request = APR_BRIGADE_FIRST(birgade_send); + bucket_request != APR_BRIGADE_SENTINEL(birgade_send); + bucket_request = APR_BUCKET_NEXT(bucket_request)) + { + const char *write_buf; + apr_size_t write_buf_len; + apr_size_t has_write; + + if (APR_BUCKET_IS_METADATA(bucket_request)) + continue; + + if ((rv = apr_bucket_read(bucket_request, &write_buf, &write_buf_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; + } + + /* Write the buffer to fastcgi server */ + has_write = 0; + while (has_write < write_buf_len) { + DWORD byteswrite; + + if (WriteFile(handle_info->handle_pipe, + write_buf + has_write, + write_buf_len - has_write, + &byteswrite, &handle_info->overlap_write)) { + has_write += byteswrite; + continue; + } else if ((rv = GetLastError()) != ERROR_IO_PENDING) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, + APR_FROM_OS_ERROR(rv), ipc_handle->request, + "mod_fcgid: can't write to pipe"); + return rv; + } else { + /* + it's ERROR_IO_PENDING on write + */ + DWORD dwWaitResult = + WaitForSingleObject(handle_info->overlap_write.hEvent, + ipc_handle->communation_timeout * 1000); + if (dwWaitResult == WAIT_OBJECT_0) { + if (!GetOverlappedResult(handle_info->handle_pipe, + &handle_info->overlap_write, + &transferred, + FALSE /* don't wait */ ) + || transferred == 0) + { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ipc_handle->request, + "mod_fcgid: get overlap result error"); + return APR_ESPIPE; + } + has_write += transferred; + continue; + } else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, + ipc_handle->request, + "mod_fcgid: write timeout to pipe"); + return APR_ESPIPE; + } + } + } + } + + return APR_SUCCESS; +} + +void proc_print_exit_info(fcgid_procnode * procnode, int exitcode, + apr_exit_why_e exitwhy, server_rec * main_server) +{ + char *diewhy = NULL; + + /* Reasons to exit */ + switch (procnode->diewhy) { + case FCGID_DIE_KILLSELF: + if (exitwhy == APR_PROC_EXIT) + diewhy = "normal exit"; + else + diewhy = "access violation"; + 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, server may has exited"; + break; + case FCGID_DIE_COMM_ERROR: + diewhy = "communication error"; + break; + case FCGID_DIE_SHUTDOWN: + diewhy = "shutting down"; + break; + default: + diewhy = "unknown"; + } + + /* Print log now */ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server, + "mod_fcgid: process %s(%" APR_PID_T_FMT ") exit(%s), return code %d", + procnode->executable_path, procnode->proc_id.pid, diewhy, exitcode); +}