| /* |
| * 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; |
| apr_procattr_t *proc_attr; |
| apr_status_t rv; |
| apr_file_t *file; |
| const char * const *proc_environ; |
| char sock_path[FCGID_PATH_MAX]; |
| int argc; |
| char const * wargv[APACHE_ARG_MAX + 1], *word; /* For wrapper */ |
| const char *tmp; |
| |
| /* Build wrapper args */ |
| argc = 0; |
| tmp = cmdline; |
| while (1) { |
| word = ap_getword_white(procnode->proc_pool, &tmp); |
| if (word == NULL || *word == '\0') |
| break; |
| if (argc >= APACHE_ARG_MAX) |
| break; |
| wargv[argc++] = word; |
| } |
| wargv[argc] = NULL; |
| |
| 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; |
| } |
| |
| 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); |
| } |