diff options
Diffstat (limited to '')
-rw-r--r-- | server/mpm/winnt/Makefile.in | 1 | ||||
-rw-r--r-- | server/mpm/winnt/child.c | 1289 | ||||
-rw-r--r-- | server/mpm/winnt/config.m4 | 10 | ||||
-rw-r--r-- | server/mpm/winnt/config3.m4 | 2 | ||||
-rw-r--r-- | server/mpm/winnt/mpm_default.h | 60 | ||||
-rw-r--r-- | server/mpm/winnt/mpm_winnt.c | 1786 | ||||
-rw-r--r-- | server/mpm/winnt/mpm_winnt.h | 96 | ||||
-rw-r--r-- | server/mpm/winnt/nt_eventlog.c | 171 | ||||
-rw-r--r-- | server/mpm/winnt/service.c | 1241 |
9 files changed, 4656 insertions, 0 deletions
diff --git a/server/mpm/winnt/Makefile.in b/server/mpm/winnt/Makefile.in new file mode 100644 index 0000000..f34af9c --- /dev/null +++ b/server/mpm/winnt/Makefile.in @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c new file mode 100644 index 0000000..21755f3 --- /dev/null +++ b/server/mpm/winnt/child.c @@ -0,0 +1,1289 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 + +#include "apr.h" +#include <process.h> +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "http_vhost.h" /* for ap_update_vhost_given_ip */ +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" +#include "apr_buckets.h" +#include "scoreboard.h" + +#ifdef __MINGW32__ +#include <mswsock.h> + +#ifndef WSAID_ACCEPTEX +#define WSAID_ACCEPTEX \ + {0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +typedef BOOL (WINAPI *LPFN_ACCEPTEX)(SOCKET, SOCKET, PVOID, DWORD, DWORD, DWORD, LPDWORD, LPOVERLAPPED); +#endif /* WSAID_ACCEPTEX */ + +#ifndef WSAID_GETACCEPTEXSOCKADDRS +#define WSAID_GETACCEPTEXSOCKADDRS \ + {0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +typedef VOID (WINAPI *LPFN_GETACCEPTEXSOCKADDRS)(PVOID, DWORD, DWORD, DWORD, + struct sockaddr **, LPINT, + struct sockaddr **, LPINT); +#endif /* WSAID_GETACCEPTEXSOCKADDRS */ + +#endif /* __MINGW32__ */ + +/* + * The Windows MPM uses a queue of completion contexts that it passes + * between the accept threads and the worker threads. Declare the + * functions to access the queue and the structures passed on the + * queue in the header file to enable modules to access them + * if necessary. The queue resides in the MPM. + */ +#ifdef CONTAINING_RECORD +#undef CONTAINING_RECORD +#endif +#define CONTAINING_RECORD(address, type, field) ((type *)( \ + (char *)(address) - \ + (char *)(&((type *)0)->field))) +#if APR_HAVE_IPV6 +#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN6)+16) +#else +#define PADDED_ADDR_SIZE (sizeof(SOCKADDR_IN)+16) +#endif + +APLOG_USE_MODULE(mpm_winnt); + +/* Queue for managing the passing of winnt_conn_ctx_t between + * the accept and worker threads. + */ +typedef struct winnt_conn_ctx_t_s { + struct winnt_conn_ctx_t_s *next; + OVERLAPPED overlapped; + apr_socket_t *sock; + SOCKET accept_socket; + char buff[2*PADDED_ADDR_SIZE]; + struct sockaddr *sa_server; + int sa_server_len; + struct sockaddr *sa_client; + int sa_client_len; + apr_pool_t *ptrans; + apr_bucket_alloc_t *ba; + apr_bucket *data; +#if APR_HAVE_IPV6 + short socket_family; +#endif +} winnt_conn_ctx_t; + +typedef enum { + IOCP_CONNECTION_ACCEPTED = 1, + IOCP_WAIT_FOR_RECEIVE = 2, + IOCP_WAIT_FOR_TRANSMITFILE = 3, + IOCP_SHUTDOWN = 4 +} io_state_e; + +static apr_pool_t *pchild; +static int shutdown_in_progress = 0; +static int workers_may_exit = 0; +static unsigned int g_blocked_threads = 0; +static HANDLE max_requests_per_child_event; + +static apr_thread_mutex_t *child_lock; +static apr_thread_mutex_t *qlock; +static winnt_conn_ctx_t *qhead = NULL; +static winnt_conn_ctx_t *qtail = NULL; +static apr_uint32_t num_completion_contexts = 0; +static apr_uint32_t max_num_completion_contexts = 0; +static HANDLE ThreadDispatchIOCP = NULL; +static HANDLE qwait_event = NULL; + +static void mpm_recycle_completion_context(winnt_conn_ctx_t *context) +{ + /* Recycle the completion context. + * - clear the ptrans pool + * - put the context on the queue to be consumed by the accept thread + * Note: + * context->accept_socket may be in a disconnected but reusable + * state so -don't- close it. + */ + if (context) { + HANDLE saved_event; + + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->next = NULL; + + saved_event = context->overlapped.hEvent; + memset(&context->overlapped, 0, sizeof(context->overlapped)); + context->overlapped.hEvent = saved_event; + ResetEvent(context->overlapped.hEvent); + + apr_thread_mutex_lock(qlock); + if (qtail) { + qtail->next = context; + } else { + qhead = context; + SetEvent(qwait_event); + } + qtail = context; + apr_thread_mutex_unlock(qlock); + } +} + +static winnt_conn_ctx_t *mpm_get_completion_context(int *timeout) +{ + apr_status_t rv; + winnt_conn_ctx_t *context = NULL; + + *timeout = 0; + while (1) { + /* Grab a context off the queue */ + apr_thread_mutex_lock(qlock); + if (qhead) { + context = qhead; + qhead = qhead->next; + if (!qhead) + qtail = NULL; + } else { + ResetEvent(qwait_event); + } + apr_thread_mutex_unlock(qlock); + + if (!context) { + /* We failed to grab a context off the queue, consider allocating + * a new one out of the child pool. There may be up to + * (ap_threads_per_child + num_listeners) contexts in the system + * at once. + */ + if (num_completion_contexts >= max_num_completion_contexts) { + /* All workers are busy, need to wait for one */ + static int reported = 0; + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00326) + "Server ran out of threads to serve " + "requests. Consider raising the " + "ThreadsPerChild setting"); + reported = 1; + } + + /* Wait for a worker to free a context. Once per second, give + * the caller a chance to check for shutdown. If the wait + * succeeds, get the context off the queue. It must be + * available, since there's only one consumer. + */ + rv = WaitForSingleObject(qwait_event, 1000); + if (rv == WAIT_OBJECT_0) + continue; + else { + if (rv == WAIT_TIMEOUT) { + /* somewhat-normal condition where threads are busy */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00327) + "mpm_get_completion_context: Failed to get a " + "free context within 1 second"); + *timeout = 1; + } + else { + /* should be the unexpected, generic WAIT_FAILED */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00328) + "mpm_get_completion_context: " + "WaitForSingleObject failed to get free context"); + } + return NULL; + } + } else { + /* Allocate another context. + * Note: Multiple failures in the next two steps will cause + * the pchild pool to 'leak' storage. I don't think this + * is worth fixing... + */ + apr_allocator_t *allocator; + + apr_thread_mutex_lock(child_lock); + context = (winnt_conn_ctx_t *)apr_pcalloc(pchild, + sizeof(winnt_conn_ctx_t)); + + + context->overlapped.hEvent = CreateEvent(NULL, TRUE, + FALSE, NULL); + if (context->overlapped.hEvent == NULL) { + /* Hopefully this is a temporary condition ... */ + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), + ap_server_conf, APLOGNO(00329) + "mpm_get_completion_context: " + "CreateEvent failed."); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + + /* Create the transaction pool */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + rv = apr_pool_create_ex(&context->ptrans, pchild, NULL, + allocator); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00330) + "mpm_get_completion_context: Failed " + "to create the transaction pool."); + CloseHandle(context->overlapped.hEvent); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + + context->accept_socket = INVALID_SOCKET; + context->ba = apr_bucket_alloc_create(context->ptrans); + apr_atomic_inc32(&num_completion_contexts); + + apr_thread_mutex_unlock(child_lock); + break; + } + } else { + /* Got a context from the queue */ + break; + } + } + + return context; +} + +typedef enum { + ACCEPT_FILTER_NONE = 0, + ACCEPT_FILTER_CONNECT = 1 +} accept_filter_e; + +static const char * accept_filter_to_string(accept_filter_e accf) +{ + switch (accf) { + case ACCEPT_FILTER_NONE: + return "none"; + case ACCEPT_FILTER_CONNECT: + return "connect"; + default: + return ""; + } +} + +static accept_filter_e get_accept_filter(const char *protocol) +{ + core_server_config *core_sconf; + const char *name; + + core_sconf = ap_get_core_module_config(ap_server_conf->module_config); + name = apr_table_get(core_sconf->accf_map, protocol); + if (!name) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + APLOGNO(02531) "winnt_accept: Listen protocol '%s' has " + "no known accept filter. Using 'none' instead", + protocol); + return ACCEPT_FILTER_NONE; + } + else if (strcmp(name, "data") == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + APLOGNO(03458) "winnt_accept: 'data' accept filter is no " + "longer supported. Using 'connect' instead"); + return ACCEPT_FILTER_CONNECT; + } + else if (strcmp(name, "connect") == 0) { + return ACCEPT_FILTER_CONNECT; + } + else if (strcmp(name, "none") == 0) { + return ACCEPT_FILTER_NONE; + } + else { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(00331) + "winnt_accept: unrecognized AcceptFilter '%s', " + "only 'data', 'connect' or 'none' are valid. " + "Using 'none' instead", name); + return ACCEPT_FILTER_NONE; + } +} + +/* Windows NT/2000 specific code... + * Accept processing for on Windows NT uses a producer/consumer queue + * model. An accept thread accepts connections off the network then issues + * PostQueuedCompletionStatus() to awake a thread blocked on the ThreadDispatch + * IOCompletionPort. + * + * winnt_accept() + * One or more accept threads run in this function, each of which accepts + * connections off the network and calls PostQueuedCompletionStatus() to + * queue an io completion packet to the ThreadDispatch IOCompletionPort. + * winnt_get_connection() + * Worker threads block on the ThreadDispatch IOCompletionPort awaiting + * connections to service. + */ +#define MAX_ACCEPTEX_ERR_COUNT 10 + +static unsigned int __stdcall winnt_accept(void *lr_) +{ + ap_listen_rec *lr = (ap_listen_rec *)lr_; + apr_os_sock_info_t sockinfo; + winnt_conn_ctx_t *context = NULL; + DWORD BytesRead; + SOCKET nlsd; + LPFN_ACCEPTEX lpfnAcceptEx = NULL; + LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL; + GUID GuidAcceptEx = WSAID_ACCEPTEX; + GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; + int rv; + accept_filter_e accf; + int err_count = 0; + HANDLE events[3]; +#if APR_HAVE_IPV6 + SOCKADDR_STORAGE ss_listen; + int namelen = sizeof(ss_listen); +#endif + u_long zero = 0; + + apr_os_sock_get(&nlsd, lr->sd); + +#if APR_HAVE_IPV6 + if (getsockname(nlsd, (struct sockaddr *)&ss_listen, &namelen) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(00332) + "winnt_accept: getsockname error on listening socket, " + "is IPv6 available?"); + return 1; + } +#endif + + accf = get_accept_filter(lr->protocol); + if (accf == ACCEPT_FILTER_CONNECT) + { + if (WSAIoctl(nlsd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidAcceptEx, sizeof GuidAcceptEx, + &lpfnAcceptEx, sizeof lpfnAcceptEx, + &BytesRead, NULL, NULL) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(02322) + "winnt_accept: failed to retrieve AcceptEx, try 'AcceptFilter none'"); + return 1; + } + if (WSAIoctl(nlsd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidGetAcceptExSockaddrs, sizeof GuidGetAcceptExSockaddrs, + &lpfnGetAcceptExSockaddrs, sizeof lpfnGetAcceptExSockaddrs, + &BytesRead, NULL, NULL) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), + ap_server_conf, APLOGNO(02323) + "winnt_accept: failed to retrieve GetAcceptExSockaddrs, try 'AcceptFilter none'"); + return 1; + } + /* first, high priority event is an already accepted connection */ + events[1] = exit_event; + events[2] = max_requests_per_child_event; + } + else /* accf == ACCEPT_FILTER_NONE */ + { +reinit: /* target of connect upon too many AcceptEx failures */ + + /* last, low priority event is a not yet accepted connection */ + events[0] = exit_event; + events[1] = max_requests_per_child_event; + events[2] = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* The event needs to be removed from the accepted socket, + * if not removed from the listen socket prior to accept(), + */ + rv = WSAEventSelect(nlsd, events[2], FD_ACCEPT); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, + apr_get_netos_error(), ap_server_conf, APLOGNO(00333) + "WSAEventSelect() failed."); + CloseHandle(events[2]); + return 1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00334) + "Child: Accept thread listening on %pI using AcceptFilter %s", + lr->bind_addr, accept_filter_to_string(accf)); + + while (!shutdown_in_progress) { + if (!context) { + int timeout; + + context = mpm_get_completion_context(&timeout); + if (!context) { + if (!timeout) { + /* Hopefully a temporary condition in the provider? */ + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00335) + "winnt_accept: Too many failures grabbing a " + "connection ctx. Aborting."); + break; + } + } + Sleep(100); + continue; + } + } + + if (accf == ACCEPT_FILTER_CONNECT) + { + char *buf; + + /* Create and initialize the accept socket */ +#if APR_HAVE_IPV6 + if (context->accept_socket == INVALID_SOCKET) { + context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, + IPPROTO_TCP); + context->socket_family = ss_listen.ss_family; + } + else if (context->socket_family != ss_listen.ss_family) { + closesocket(context->accept_socket); + context->accept_socket = socket(ss_listen.ss_family, SOCK_STREAM, + IPPROTO_TCP); + context->socket_family = ss_listen.ss_family; + } +#else + if (context->accept_socket == INVALID_SOCKET) + context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + + if (context->accept_socket == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00336) + "winnt_accept: Failed to allocate an accept socket. " + "Temporary resource constraint? Try again."); + Sleep(100); + continue; + } + + buf = context->buff; + + /* AcceptEx on the completion context. The completion context will be + * signaled when a connection is accepted. + */ + if (!lpfnAcceptEx(nlsd, context->accept_socket, buf, 0, + PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, &BytesRead, + &context->overlapped)) { + rv = apr_get_netos_error(); + if ((rv == APR_FROM_OS_ERROR(WSAECONNRESET)) || + (rv == APR_FROM_OS_ERROR(WSAEACCES))) { + /* We can get here when: + * 1) the client disconnects early + * 2) handshake was incomplete + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + continue; + } + else if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) || + (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) { + /* We can get here when: + * 1) TransmitFile does not properly recycle the accept socket (typically + * because the client disconnected) + * 2) there is VPN or Firewall software installed with + * buggy WSAAccept or WSADuplicateSocket implementation + * 3) the dynamic address / adapter has changed + * Give five chances, then fall back on AcceptFilter 'none' + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00337) + "Child: Encountered too many AcceptEx " + "faults accepting client connections. " + "Possible causes: dynamic address renewal, " + "or incompatible VPN or firewall software. "); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00338) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = ACCEPT_FILTER_NONE; + } + continue; + } + else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) && + (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) { + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00339) + "Child: Encountered too many AcceptEx " + "faults accepting client connections."); + ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, ap_server_conf, APLOGNO(00340) + "winnt_mpm: falling back to " + "'AcceptFilter none'."); + err_count = 0; + accf = ACCEPT_FILTER_NONE; + goto reinit; + } + continue; + } + + err_count = 0; + events[0] = context->overlapped.hEvent; + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + if (rv == WAIT_OBJECT_0) { + if ((context->accept_socket != INVALID_SOCKET) && + !GetOverlappedResult((HANDLE)context->accept_socket, + &context->overlapped, + &BytesRead, FALSE)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ap_server_conf, APLOGNO(00341) + "winnt_accept: Asynchronous AcceptEx failed."); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + } + else { + /* exit_event triggered or event handle was closed */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + break; + } + + if (context->accept_socket == INVALID_SOCKET) { + continue; + } + } + err_count = 0; + + /* Potential optimization; consider handing off to the worker */ + + /* Inherit the listen socket settings. Required for + * shutdown() to work + */ + if (setsockopt(context->accept_socket, SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd, + sizeof(nlsd))) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), + ap_server_conf, APLOGNO(00342) + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed."); + /* Not a failure condition. Keep running. */ + } + + /* Get the local & remote address + * TODO; error check + */ + lpfnGetAcceptExSockaddrs(buf, 0, PADDED_ADDR_SIZE, PADDED_ADDR_SIZE, + &context->sa_server, &context->sa_server_len, + &context->sa_client, &context->sa_client_len); + } + else /* accf == ACCEPT_FILTER_NONE */ + { + /* There is no socket reuse without AcceptEx() */ + if (context->accept_socket != INVALID_SOCKET) + closesocket(context->accept_socket); + + /* This could be a persistent event per-listener rather than + * per-accept. However, the event needs to be removed from + * the target socket if not removed from the listen socket + * prior to accept(), or the event select is inherited. + * and must be removed from the accepted socket. + */ + + do { + rv = WaitForMultipleObjectsEx(3, events, FALSE, INFINITE, TRUE); + } while (rv == WAIT_IO_COMPLETION); + + + if (rv != WAIT_OBJECT_0 + 2) { + /* not FD_ACCEPT; + * exit_event triggered or event handle was closed + */ + break; + } + + context->sa_server = (void *) context->buff; + context->sa_server_len = sizeof(context->buff) / 2; + context->sa_client_len = context->sa_server_len; + context->sa_client = (void *) (context->buff + + context->sa_server_len); + + context->accept_socket = accept(nlsd, context->sa_server, + &context->sa_server_len); + + if (context->accept_socket == INVALID_SOCKET) { + + rv = apr_get_netos_error(); + if ( rv == APR_FROM_OS_ERROR(WSAECONNRESET) + || rv == APR_FROM_OS_ERROR(WSAEINPROGRESS) + || rv == APR_FROM_OS_ERROR(WSAEWOULDBLOCK) ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, + rv, ap_server_conf, APLOGNO(00343) + "accept() failed, retrying."); + continue; + } + + /* A more serious error than 'retry', log it */ + ap_log_error(APLOG_MARK, APLOG_WARNING, + rv, ap_server_conf, APLOGNO(00344) + "accept() failed."); + + if ( rv == APR_FROM_OS_ERROR(WSAEMFILE) + || rv == APR_FROM_OS_ERROR(WSAENOBUFS) ) { + /* Hopefully a temporary condition in the provider? */ + Sleep(100); + ++err_count; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00345) + "Child: Encountered too many accept() " + "resource faults, aborting."); + break; + } + continue; + } + break; + } + /* Per MSDN, cancel the inherited association of this socket + * to the WSAEventSelect API, and restore the state corresponding + * to apr_os_sock_make's default assumptions (really, a flaw within + * os_sock_make and os_sock_put that it does not query). + */ + WSAEventSelect(context->accept_socket, 0, 0); + err_count = 0; + + context->sa_server_len = sizeof(context->buff) / 2; + if (getsockname(context->accept_socket, context->sa_server, + &context->sa_server_len) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00346) + "getsockname failed"); + continue; + } + if ((getpeername(context->accept_socket, context->sa_client, + &context->sa_client_len)) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00347) + "getpeername failed"); + memset(&context->sa_client, '\0', sizeof(context->sa_client)); + } + } + + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = context->sa_server->sa_family; + sockinfo.type = SOCK_STREAM; + sockinfo.protocol = IPPROTO_TCP; + /* Restore the state corresponding to apr_os_sock_make's default + * assumption of timeout -1 (really, a flaw of os_sock_make and + * os_sock_put that it does not query to determine ->timeout). + * XXX: Upon a fix to APR, these three statements should disappear. + */ + ioctlsocket(context->accept_socket, FIONBIO, &zero); + setsockopt(context->accept_socket, SOL_SOCKET, SO_RCVTIMEO, + (char *) &zero, sizeof(zero)); + setsockopt(context->accept_socket, SOL_SOCKET, SO_SNDTIMEO, + (char *) &zero, sizeof(zero)); + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + /* When a connection is received, send an io completion notification + * to the ThreadDispatchIOCP. + */ + PostQueuedCompletionStatus(ThreadDispatchIOCP, BytesRead, + IOCP_CONNECTION_ACCEPTED, + &context->overlapped); + context = NULL; + } + if (accf == ACCEPT_FILTER_NONE) + CloseHandle(events[2]); + + if (!shutdown_in_progress) { + /* Yow, hit an irrecoverable error! Tell the child to die. */ + SetEvent(exit_event); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00348) + "Child: Accept thread exiting."); + return 0; +} + + +static winnt_conn_ctx_t *winnt_get_connection(winnt_conn_ctx_t *context) +{ + int rc; + DWORD BytesRead; + LPOVERLAPPED pol; +#ifdef _WIN64 + ULONG_PTR CompKey; +#else + DWORD CompKey; +#endif + + mpm_recycle_completion_context(context); + + apr_atomic_inc32(&g_blocked_threads); + while (1) { + if (workers_may_exit) { + apr_atomic_dec32(&g_blocked_threads); + return NULL; + } + rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, + &CompKey, &pol, INFINITE); + if (!rc) { + rc = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, ap_server_conf, APLOGNO(00349) + "Child: GetQueuedCompletionStatus returned %d", + rc); + continue; + } + + switch (CompKey) { + case IOCP_CONNECTION_ACCEPTED: + context = CONTAINING_RECORD(pol, winnt_conn_ctx_t, overlapped); + break; + case IOCP_SHUTDOWN: + apr_atomic_dec32(&g_blocked_threads); + return NULL; + default: + apr_atomic_dec32(&g_blocked_threads); + return NULL; + } + break; + } + apr_atomic_dec32(&g_blocked_threads); + + return context; +} + +/* + * worker_main() + * Main entry point for the worker threads. Worker threads block in + * win*_get_connection() awaiting a connection to service. + */ +static DWORD __stdcall worker_main(void *thread_num_val) +{ + apr_thread_t *thd; + apr_os_thread_t osthd; + static int requests_this_child = 0; + winnt_conn_ctx_t *context = NULL; + int thread_num = (int)thread_num_val; + ap_sb_handle_t *sbh; + conn_rec *c; + apr_int32_t disconnected; + + osthd = apr_os_thread_current(); + + while (1) { + + ap_update_child_status_from_indexes(0, thread_num, SERVER_READY, NULL); + + /* Grab a connection off the network */ + context = winnt_get_connection(context); + + if (!context) { + /* Time for the thread to exit */ + break; + } + + /* Have we hit MaxConnectionsPerChild connections? */ + if (ap_max_requests_per_child) { + requests_this_child++; + if (requests_this_child > ap_max_requests_per_child) { + SetEvent(max_requests_per_child_event); + } + } + + ap_create_sb_handle(&sbh, context->ptrans, 0, thread_num); + c = ap_run_create_connection(context->ptrans, ap_server_conf, + context->sock, thread_num, sbh, + context->ba); + + if (!c) { + /* ap_run_create_connection closes the socket on failure */ + context->accept_socket = INVALID_SOCKET; + continue; + } + + thd = NULL; + apr_os_thread_put(&thd, &osthd, context->ptrans); + c->current_thread = thd; + + ap_process_connection(c, context->sock); + + ap_lingering_close(c); + + apr_socket_opt_get(context->sock, APR_SO_DISCONNECTED, &disconnected); + if (!disconnected) { + context->accept_socket = INVALID_SOCKET; + } + } + + ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD, NULL); + + return 0; +} + + +static void cleanup_thread(HANDLE *handles, int *thread_cnt, + int thread_to_clean) +{ + int i; + + CloseHandle(handles[thread_to_clean]); + for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++) + handles[i] = handles[i + 1]; + (*thread_cnt)--; +} + + +/* + * child_main() + * Entry point for the main control thread for the child process. + * This thread creates the accept thread, worker threads and + * monitors the child process for maintenance and shutdown + * events. + */ +static void create_listener_thread(void) +{ + unsigned tid; + int num_listeners = 0; + /* Start an accept thread per listener + * XXX: Why would we have a NULL sd in our listeners? + */ + ap_listen_rec *lr; + + /* Number of completion_contexts allowed in the system is + * (ap_threads_per_child + num_listeners). We need the additional + * completion contexts to prevent server hangs when ThreadsPerChild + * is configured to something less than or equal to the number + * of listeners. This is not a usual case, but people have + * encountered it. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + num_listeners++; + } + max_num_completion_contexts = ap_threads_per_child + num_listeners; + + /* Now start a thread per listener */ + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + /* A smaller stack is sufficient. + * To convert to CreateThread, the returned handle cannot be + * ignored, it must be closed/joined. + */ + _beginthreadex(NULL, 65536, winnt_accept, + (void *) lr, stack_res_flag, &tid); + } + } +} + + +void child_main(apr_pool_t *pconf, DWORD parent_pid) +{ + apr_status_t status; + apr_hash_t *ht; + ap_listen_rec *lr; + HANDLE child_events[3]; + HANDLE *child_handles; + int listener_started = 0; + int threads_created = 0; + int watch_thread; + int time_remains; + int cld; + DWORD tid; + int rv; + int i; + int num_events; + + /* Get a sub context for global allocations in this child, so that + * we can have cleanups occur when the child exits. + */ + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + ht = apr_hash_make(pchild); + + /* Initialize the child_events */ + max_requests_per_child_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!max_requests_per_child_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00350) + "Child: Failed to create a max_requests event."); + exit(APEXIT_CHILDINIT); + } + child_events[0] = exit_event; + child_events[1] = max_requests_per_child_event; + + if (parent_pid != my_pid) { + child_events[2] = OpenProcess(SYNCHRONIZE, FALSE, parent_pid); + if (child_events[2] == NULL) { + num_events = 2; + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(02643) + "Child: Failed to open handle to parent process %ld; " + "will not react to abrupt parent termination", parent_pid); + } + else { + num_events = 3; + } + } + else { + /* presumably -DONE_PROCESS */ + child_events[2] = NULL; + num_events = 2; + } + + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. + */ + status = apr_proc_mutex_lock(start_mutex); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, ap_server_conf, APLOGNO(00351) + "Child: Failed to acquire the start_mutex. " + "Process will exit."); + exit(APEXIT_CHILDINIT); + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00352) + "Child: Acquired the start mutex."); + + /* + * Create the worker thread dispatch IOCompletionPort + */ + /* Create the worker thread dispatch IOCP */ + ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, 0, 0); + apr_thread_mutex_create(&qlock, APR_THREAD_MUTEX_DEFAULT, pchild); + qwait_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!qwait_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00353) + "Child: Failed to create a qwait event."); + exit(APEXIT_CHILDINIT); + } + + /* + * Create the pool of worker threads + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00354) + "Child: Starting %d worker threads.", ap_threads_per_child); + child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child + * sizeof(HANDLE)); + apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild); + + while (1) { + for (i = 0; i < ap_threads_per_child; i++) { + int *score_idx; + int status = ap_scoreboard_image->servers[0][i].status; + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL); + + child_handles[i] = CreateThread(NULL, ap_thread_stacksize, + worker_main, (void *) i, + stack_res_flag, &tid); + if (child_handles[i] == 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00355) + "Child: CreateThread failed. Unable to " + "create all worker threads. Created %d of the %d " + "threads requested with the ThreadsPerChild " + "configuration directive.", + threads_created, ap_threads_per_child); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + goto shutdown; + } + threads_created++; + /* Save the score board index in ht keyed to the thread handle. + * We need this when cleaning up threads down below... + */ + apr_thread_mutex_lock(child_lock); + score_idx = apr_pcalloc(pchild, sizeof(int)); + *score_idx = i; + apr_hash_set(ht, &child_handles[i], sizeof(HANDLE), score_idx); + apr_thread_mutex_unlock(child_lock); + } + /* Start the listener only when workers are available */ + if (!listener_started && threads_created) { + create_listener_thread(); + listener_started = 1; + winnt_mpm_state = AP_MPMQ_RUNNING; + } + if (threads_created == ap_threads_per_child) { + break; + } + /* Check to see if the child has been told to exit */ + if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { + break; + } + /* wait for previous generation to clean up an entry in the scoreboard + */ + apr_sleep(1 * APR_USEC_PER_SEC); + } + + /* Wait for one of these events: + * exit_event: + * The exit_event is signaled by the parent process to notify + * the child that it is time to exit. + * + * max_requests_per_child_event: + * This event is signaled by the worker threads to indicate that + * the process has handled MaxConnectionsPerChild connections. + * + * parent process exiting + * + * TIMEOUT: + * To do periodic maintenance on the server (check for thread exits, + * number of completion contexts, etc.) + * + * XXX: thread exits *aren't* being checked. + * + * XXX: other_child - we need the process handles to the other children + * in order to map them to apr_proc_other_child_read (which is not + * named well, it's more like a_p_o_c_died.) + * + * XXX: however - if we get a_p_o_c handle inheritance working, and + * the parent process creates other children and passes the pipes + * to our worker processes, then we have no business doing such + * things in the child_main loop, but should happen in master_main. + */ + while (1) { +#if !APR_HAS_OTHER_CHILD + rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; +#else + /* THIS IS THE EXPECTED BUILD VARIATION -- APR_HAS_OTHER_CHILD */ + rv = WaitForMultipleObjects(num_events, (HANDLE *)child_events, FALSE, 1000); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_TIMEOUT) { + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + } + else +#endif + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(00356) + "Child: WAIT_FAILED -- shutting down server"); + /* check handle validity to identify a possible culprit */ + for (i = 0; i < num_events; i++) { + DWORD out_flags; + + if (0 == GetHandleInformation(child_events[i], &out_flags)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), + ap_server_conf, APLOGNO(02644) + "Child: Event handle #%d (%pp) is invalid", + i, child_events[i]); + } + } + break; + } + else if (cld == 0) { + /* Exit event was signaled */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00357) + "Child: Exit event signaled. Child process is " + "ending."); + break; + } + else if (cld == 2) { + /* The parent is dead. Shutdown the child process. */ + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(02538) + "Child: Parent process exited abruptly. Child process " + "is ending"); + break; + } + else { + /* MaxConnectionsPerChild event set by the worker threads. + * Signal the parent to restart + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00358) + "Child: Process exiting because it reached " + "MaxConnectionsPerChild. Signaling the parent to " + "restart a new child process."); + ap_signal_parent(SIGNAL_PARENT_RESTART); + break; + } + } + + /* + * Time to shutdown the child process + */ + + shutdown: + + winnt_mpm_state = AP_MPMQ_STOPPING; + + /* Close the listening sockets. Note, we must close the listeners + * before closing any accept sockets pending in AcceptEx to prevent + * memory leaks in the kernel. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + apr_socket_close(lr->sd); + } + + /* Shutdown listener threads and pending AcceptEx sockets + * but allow the worker threads to continue consuming from + * the queue of accepted connections. + */ + shutdown_in_progress = 1; + + Sleep(1000); + + /* Tell the worker threads to exit */ + workers_may_exit = 1; + + /* Release the start_mutex to let the new process (in the restart + * scenario) a chance to begin accepting and servicing requests + */ + rv = apr_proc_mutex_unlock(start_mutex); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00359) + "Child: Released the start mutex"); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00360) + "Child: Failure releasing the start mutex"); + } + + /* Shutdown the worker threads + * Post worker threads blocked on the ThreadDispatch IOCompletion port + */ + while (g_blocked_threads > 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00361) + "Child: %d threads blocked on the completion port", + g_blocked_threads); + for (i=g_blocked_threads; i > 0; i--) { + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, + IOCP_SHUTDOWN, NULL); + } + Sleep(1000); + } + /* Empty the accept queue of completion contexts */ + apr_thread_mutex_lock(qlock); + while (qhead) { + CloseHandle(qhead->overlapped.hEvent); + closesocket(qhead->accept_socket); + qhead = qhead->next; + } + apr_thread_mutex_unlock(qlock); + + /* Give busy threads a chance to service their connections + * (no more than the global server timeout period which + * we track in msec remaining). + */ + watch_thread = 0; + time_remains = (int)(ap_server_conf->timeout / APR_TIME_C(1000)); + + while (threads_created) + { + int nFailsafe = MAXIMUM_WAIT_OBJECTS; + DWORD dwRet; + + /* Every time we roll over to wait on the first group + * of MAXIMUM_WAIT_OBJECTS threads, take a breather, + * and infrequently update the error log. + */ + if (watch_thread >= threads_created) { + if ((time_remains -= 100) < 0) + break; + + /* Every 30 seconds give an update */ + if ((time_remains % 30000) == 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, + ap_server_conf, APLOGNO(00362) + "Child: Waiting %d more seconds " + "for %d worker threads to finish.", + time_remains / 1000, threads_created); + } + /* We'll poll from the top, 10 times per second */ + Sleep(100); + watch_thread = 0; + } + + /* Fairness, on each iteration we will pick up with the thread + * after the one we just removed, even if it's a single thread. + * We don't block here. + */ + dwRet = WaitForMultipleObjects(min(threads_created - watch_thread, + MAXIMUM_WAIT_OBJECTS), + child_handles + watch_thread, 0, 0); + + if (dwRet == WAIT_FAILED) { + break; + } + if (dwRet == WAIT_TIMEOUT) { + /* none ready */ + watch_thread += MAXIMUM_WAIT_OBJECTS; + continue; + } + else if (dwRet >= WAIT_ABANDONED_0) { + /* We just got the ownership of the object, which + * should happen at most MAXIMUM_WAIT_OBJECTS times. + * It does NOT mean that the object is signaled. + */ + if ((nFailsafe--) < 1) + break; + } + else { + watch_thread += (dwRet - WAIT_OBJECT_0); + if (watch_thread >= threads_created) + break; + cleanup_thread(child_handles, &threads_created, watch_thread); + } + } + + /* Kill remaining threads off the hard way */ + if (threads_created) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00363) + "Child: Terminating %d threads that failed to exit.", + threads_created); + } + for (i = 0; i < threads_created; i++) { + int *idx; + TerminateThread(child_handles[i], 1); + CloseHandle(child_handles[i]); + /* Reset the scoreboard entry for the thread we just whacked */ + idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE)); + if (idx) { + ap_update_child_status_from_indexes(0, *idx, SERVER_DEAD, NULL); + } + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00364) + "Child: All worker threads have exited."); + + apr_thread_mutex_destroy(child_lock); + apr_thread_mutex_destroy(qlock); + CloseHandle(qwait_event); + CloseHandle(ThreadDispatchIOCP); + + apr_pool_destroy(pchild); + CloseHandle(exit_event); + if (child_events[2] != NULL) { + CloseHandle(child_events[2]); + } +} + +#endif /* def WIN32 */ diff --git a/server/mpm/winnt/config.m4 b/server/mpm/winnt/config.m4 new file mode 100644 index 0000000..5c1fd42 --- /dev/null +++ b/server/mpm/winnt/config.m4 @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(if WinNT MPM supports this platform) +case $host in + *mingw32*) + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(winnt, no, yes) + ;; + *) + AC_MSG_RESULT(no) + ;; +esac diff --git a/server/mpm/winnt/config3.m4 b/server/mpm/winnt/config3.m4 new file mode 100644 index 0000000..f937e40 --- /dev/null +++ b/server/mpm/winnt/config3.m4 @@ -0,0 +1,2 @@ +winnt_objects="child.lo mpm_winnt.lo nt_eventlog.lo service.lo" +APACHE_MPM_MODULE(winnt, $enable_mpm_winnt, $winnt_objects) diff --git a/server/mpm/winnt/mpm_default.h b/server/mpm/winnt/mpm_default.h new file mode 100644 index 0000000..b5350d4 --- /dev/null +++ b/server/mpm/winnt/mpm_default.h @@ -0,0 +1,60 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file winnt/mpm_default.h + * @brief win32 MPM defaults + * + * @defgroup APACHE_MPM_WINNT WinNT MPM + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Default limit on the maximum setting of the ThreadsPerChild configuration + * directive. This limit can be overridden with the ThreadLimit directive. + * This limit directly influences the amount of shared storage that is allocated + * for the scoreboard. DEFAULT_THREAD_LIMIT represents a good compromise + * between scoreboard size and the ability of the server to handle the most + * common installation requirements. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 1920 +#endif + +/* The ThreadLimit directive can be used to override the DEFAULT_THREAD_LIMIT. + * ThreadLimit cannot be tuned larger than MAX_THREAD_LIMIT. + * This is a sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 15000 +#endif + +/* Number of threads started in the child process in the absence + * of a ThreadsPerChild configuration directive + */ +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 64 +#endif + +/* Max number of child processes allowed. + */ +#define HARD_SERVER_LIMIT 1 + +#endif /* AP_MPM_DEFAULT_H */ +/** @} */ diff --git a/server/mpm/winnt/mpm_winnt.c b/server/mpm/winnt/mpm_winnt.c new file mode 100644 index 0000000..3d71f80 --- /dev/null +++ b/server/mpm/winnt/mpm_winnt.c @@ -0,0 +1,1786 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WIN32 + +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "apr_general.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" +#include "scoreboard.h" + +#ifdef __WATCOMC__ +#define _environ environ +#endif + +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION /* missing on MinGW */ +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 +#endif + +/* Because ap_setup_listeners() is skipped in the child, any merging + * of [::]:80 and 0.0.0.0:80 for AP_ENABLE_V4_MAPPED in the parent + * won't have taken place in the child, so the child will expect to + * read two sockets for "Listen 80" but the parent will send only + * one. + */ +#ifdef AP_ENABLE_V4_MAPPED +#error The WinNT MPM does not currently support AP_ENABLE_V4_MAPPED +#endif + +/* scoreboard.c does the heavy lifting; all we do is create the child + * score by moving a handle down the pipe into the child's stdin. + */ +extern apr_shm_t *ap_scoreboard_shm; + +/* my_generation is returned to the scoreboard code */ +static volatile ap_generation_t my_generation=0; + +/* Definitions of WINNT MPM specific config globals */ +static HANDLE shutdown_event; /* used to signal the parent to shutdown */ +static HANDLE restart_event; /* used to signal the parent to restart */ + +static int one_process = 0; +static char const* signal_arg = NULL; + +OSVERSIONINFO osver; /* VER_PLATFORM_WIN32_NT */ + +/* set by child_main to STACK_SIZE_PARAM_IS_A_RESERVATION for NT >= 5.1 (XP/2003) */ +DWORD stack_res_flag; + +static DWORD parent_pid; +DWORD my_pid; + +/* used by parent to signal the child to start and exit */ +apr_proc_mutex_t *start_mutex; +HANDLE exit_event; + +int ap_threads_per_child = 0; +static int thread_limit = 0; +static int first_thread_limit = 0; +int winnt_mpm_state = AP_MPMQ_STARTING; + +/* shared by service.c as global, although + * perhaps it should be private. + */ +apr_pool_t *pconf; + +/* Only one of these, the pipe from our parent, meant only for + * one child worker's consumption (not to be inherited!) + * XXX: decorate this name for the trunk branch, was left simplified + * only to make the 2.2 patch trivial to read. + */ +static HANDLE pipe; + +/* + * Command processors + */ + +static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_per_child = atoi(arg); + return NULL; +} +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + thread_limit = atoi(arg); + return NULL; +} + +static const command_rec winnt_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates" ), +AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum worker threads in a server for this run of Apache"), +{ NULL } +}; + +static void winnt_note_child_started(int slot, pid_t pid) +{ + ap_scoreboard_image->parent[slot].pid = pid; + ap_scoreboard_image->parent[slot].generation = my_generation; + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + my_generation, slot, MPM_CHILD_STARTED); +} + +static void winnt_note_child_killed(int slot) +{ + ap_run_child_status(ap_server_conf, + ap_scoreboard_image->parent[slot].pid, + ap_scoreboard_image->parent[slot].generation, + slot, MPM_CHILD_EXITED); + ap_scoreboard_image->parent[slot].pid = 0; +} + +/* + * Signalling Apache on NT. + * + * Under Unix, Apache can be told to shutdown or restart by sending various + * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so + * we use "events" instead. The parent apache process goes into a loop + * where it waits forever for a set of events. Two of those events are + * called + * + * apPID_shutdown + * apPID_restart + * + * (where PID is the PID of the apache parent process). When one of these + * is signalled, the Apache parent performs the appropriate action. The events + * can become signalled through internal Apache methods (e.g. if the child + * finds a fatal error and needs to kill its parent), via the service + * control manager (the control thread will signal the shutdown event when + * requested to stop the Apache service), from the -k Apache command line, + * or from any external program which finds the Apache PID from the + * httpd.pid file. + * + * The signal_parent() function, below, is used to signal one of these events. + * It can be called by any child or parent process, since it does not + * rely on global variables. + * + * On entry, type gives the event to signal. 0 means shutdown, 1 means + * graceful restart. + */ +/* + * Initialise the signal names, in the global variables signal_name_prefix, + * signal_restart_name and signal_shutdown_name. + */ +#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */ +static char signal_name_prefix[MAX_SIGNAL_NAME]; +static char signal_restart_name[MAX_SIGNAL_NAME]; +static char signal_shutdown_name[MAX_SIGNAL_NAME]; +static void setup_signal_names(char *prefix) +{ + apr_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix); + apr_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), + "%s_shutdown", signal_name_prefix); + apr_snprintf(signal_restart_name, sizeof(signal_restart_name), + "%s_restart", signal_name_prefix); +} + +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) +{ + HANDLE e; + char *signal_name; + + if (parent_pid == my_pid) { + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + SetEvent(shutdown_event); + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + SetEvent(restart_event); + break; + } + } + return; + } + + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + signal_name = signal_shutdown_name; + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + signal_name = signal_restart_name; + break; + } + default: + return; + } + + e = OpenEvent(EVENT_MODIFY_STATE, FALSE, signal_name); + if (!e) { + /* Um, problem, can't signal the parent, which means we can't + * signal ourselves to die. Ignore for now... + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00382) + "OpenEvent on %s event", signal_name); + return; + } + if (SetEvent(e) == 0) { + /* Same problem as above */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, APLOGNO(00383) + "SetEvent on %s event", signal_name); + CloseHandle(e); + return; + } + CloseHandle(e); +} + + +/* + * Passed the following handles [in sync with send_handles_to_child()] + * + * ready event [signal the parent immediately, then close] + * exit event [save to poll later] + * start mutex [signal from the parent to begin accept()] + * scoreboard shm handle [to recreate the ap_scoreboard] + */ +static void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event, + apr_proc_mutex_t **child_start_mutex, + apr_shm_t **scoreboard_shm) +{ + HANDLE hScore; + HANDLE ready_event; + HANDLE os_start; + DWORD BytesRead; + void *sb_shared; + apr_status_t rv; + + /* *** We now do this way back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + if (!ReadFile(pipe, &ready_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00384) + "Child: Unable to retrieve the ready event from the parent"); + exit(APEXIT_CHILDINIT); + } + + SetEvent(ready_event); + CloseHandle(ready_event); + + if (!ReadFile(pipe, child_exit_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00385) + "Child: Unable to retrieve the exit event from the parent"); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &os_start, sizeof(os_start), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(os_start))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00386) + "Child: Unable to retrieve the start_mutex from the parent"); + exit(APEXIT_CHILDINIT); + } + *child_start_mutex = NULL; + if ((rv = apr_os_proc_mutex_put(child_start_mutex, &os_start, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00387) + "Child: Unable to access the start_mutex from the parent"); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &hScore, sizeof(hScore), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(hScore))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00388) + "Child: Unable to retrieve the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + *scoreboard_shm = NULL; + if ((rv = apr_os_shm_put(scoreboard_shm, &hScore, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00389) + "Child: Unable to access the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + + rv = ap_reopen_scoreboard(s->process->pool, scoreboard_shm, 1); + if (rv || !(sb_shared = apr_shm_baseaddr_get(*scoreboard_shm))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00390) + "Child: Unable to reopen the scoreboard from the parent"); + exit(APEXIT_CHILDINIT); + } + /* We must 'initialize' the scoreboard to relink all the + * process-local pointer arrays into the shared memory block. + */ + ap_init_scoreboard(sb_shared); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00391) + "Child: Retrieved our scoreboard from the parent."); +} + + +static int send_handles_to_child(apr_pool_t *p, + HANDLE child_ready_event, + HANDLE child_exit_event, + apr_proc_mutex_t *child_start_mutex, + apr_shm_t *scoreboard_shm, + HANDLE hProcess, + apr_file_t *child_in) +{ + apr_status_t rv; + HANDLE hCurrentProcess = GetCurrentProcess(); + HANDLE hDup; + HANDLE os_start; + HANDLE hScore; + apr_size_t BytesWritten; + + if ((rv = apr_file_write_full(child_in, &my_generation, + sizeof(my_generation), NULL)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(02964) + "Parent: Unable to send its generation to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_ready_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00392) + "Parent: Unable to duplicate the ready event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00393) + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_exit_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00394) + "Parent: Unable to duplicate the exit event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00395) + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if ((rv = apr_os_proc_mutex_get(&os_start, child_start_mutex)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00396) + "Parent: Unable to retrieve the start mutex for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, os_start, hProcess, &hDup, + SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00397) + "Parent: Unable to duplicate the start mutex to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00398) + "Parent: Unable to send the start mutex to the child"); + return -1; + } + if ((rv = apr_os_shm_get(&hScore, scoreboard_shm)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00399) + "Parent: Unable to retrieve the scoreboard handle for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, hScore, hProcess, &hDup, + FILE_MAP_READ | FILE_MAP_WRITE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00400) + "Parent: Unable to duplicate the scoreboard handle to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00401) + "Parent: Unable to send the scoreboard handle to the child"); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00402) + "Parent: Sent the scoreboard to the child"); + return 0; +} + + +/* + * get_listeners_from_parent() + * The listen sockets are opened in the parent. This function, which runs + * exclusively in the child process, receives them from the parent and + * makes them availeble in the child. + */ +static void get_listeners_from_parent(server_rec *s) +{ + WSAPROTOCOL_INFO WSAProtocolInfo; + ap_listen_rec *lr; + DWORD BytesRead; + int lcnt = 0; + SOCKET nsd; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr; + lr = apr_palloc(s->process->pool, sizeof(ap_listen_rec)); + lr->sd = NULL; + lr->next = ap_listeners; + ap_listeners = lr; + } + + /* Open the pipe to the parent process to receive the inherited socket + * data. The sockets have been set to listening in the parent process. + * + * *** We now do this way back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00403) + "Child: Waiting for data for listening socket %pI", + lr->bind_addr); + if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO), + &BytesRead, (LPOVERLAPPED) NULL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00404) + "Child: Unable to read socket data from parent"); + exit(APEXIT_CHILDINIT); + } + + nsd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, + &WSAProtocolInfo, 0, 0); + if (nsd == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00405) + "Child: WSASocket failed to open the inherited socket"); + exit(APEXIT_CHILDINIT); + } + + if (!SetHandleInformation((HANDLE)nsd, HANDLE_FLAG_INHERIT, 0)) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00406) + "Child: SetHandleInformation failed"); + } + apr_os_sock_put(&lr->sd, &nsd, s->process->pool); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00407) + "Child: retrieved %d listeners from parent", lcnt); +} + + +static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId, + apr_file_t *child_in) +{ + apr_status_t rv; + int lcnt = 0; + ap_listen_rec *lr; + LPWSAPROTOCOL_INFO lpWSAProtocolInfo; + apr_size_t BytesWritten; + + /* Run the chain of open sockets. For each socket, duplicate it + * for the target process then send the WSAPROTOCOL_INFO + * (returned by dup socket) to the child. + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + apr_os_sock_t nsd; + lpWSAProtocolInfo = apr_pcalloc(p, sizeof(WSAPROTOCOL_INFO)); + apr_os_sock_get(&nsd, lr->sd); + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00408) + "Parent: Duplicating socket %d (%pI) and sending it to child process %lu", + nsd, lr->bind_addr, dwProcessId); + if (WSADuplicateSocket(nsd, dwProcessId, + lpWSAProtocolInfo) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, APLOGNO(00409) + "Parent: WSADuplicateSocket failed for socket %d. Check the FAQ.", nsd); + return -1; + } + + if ((rv = apr_file_write_full(child_in, lpWSAProtocolInfo, + sizeof(WSAPROTOCOL_INFO), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00410) + "Parent: Unable to write duplicated socket %d to the child.", nsd); + return -1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00411) + "Parent: Sent %d listeners to child %lu", lcnt, dwProcessId); + return 0; +} + +enum waitlist_e { + waitlist_ready = 0, + waitlist_term = 1 +}; + +static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_event, + DWORD *child_pid) +{ + /* These NEVER change for the lifetime of this parent + */ + static char **args = NULL; + static char pidbuf[28]; + + apr_status_t rv; + apr_pool_t *ptemp; + apr_procattr_t *attr; + apr_proc_t new_child; + HANDLE hExitEvent; + HANDLE waitlist[2]; /* see waitlist_e */ + char *cmd; + char *cwd; + char **env; + int envc; + + apr_pool_create_ex(&ptemp, p, NULL, NULL); + + /* Build the command line. Should look something like this: + * C:/apache/bin/httpd.exe -f ap_server_confname + * First, get the path to the executable... + */ + apr_procattr_create(&attr, ptemp); + apr_procattr_cmdtype_set(attr, APR_PROGRAM); + apr_procattr_detach_set(attr, 1); + if (((rv = apr_filepath_get(&cwd, 0, ptemp)) != APR_SUCCESS) + || ((rv = apr_procattr_dir_set(attr, cwd)) != APR_SUCCESS)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00412) + "Parent: Failed to get the current path"); + } + + if (!args) { + /* Build the args array, only once since it won't change + * for the lifetime of this parent process. + */ + if ((rv = ap_os_proc_filepath(&cmd, ptemp)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf, APLOGNO(00413) + "Parent: Failed to get full path of %s", + ap_server_conf->process->argv[0]); + apr_pool_destroy(ptemp); + return -1; + } + + args = malloc((ap_server_conf->process->argc + 1) * sizeof (char*)); + memcpy(args + 1, ap_server_conf->process->argv + 1, + (ap_server_conf->process->argc - 1) * sizeof (char*)); + args[0] = malloc(strlen(cmd) + 1); + strcpy(args[0], cmd); + args[ap_server_conf->process->argc] = NULL; + } + else { + cmd = args[0]; + } + + /* Create a pipe to send handles to the child */ + if ((rv = apr_procattr_io_set(attr, APR_FULL_BLOCK, + APR_NO_PIPE, APR_NO_PIPE)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00414) + "Parent: Unable to create child stdin pipe."); + apr_pool_destroy(ptemp); + return -1; + } + + /* Create the child_ready_event */ + waitlist[waitlist_ready] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!waitlist[waitlist_ready]) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00415) + "Parent: Could not create ready event for child process"); + apr_pool_destroy (ptemp); + return -1; + } + + /* Create the child_exit_event */ + hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hExitEvent) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00416) + "Parent: Could not create exit event for child process"); + apr_pool_destroy(ptemp); + CloseHandle(waitlist[waitlist_ready]); + return -1; + } + + /* Build the env array */ + for (envc = 0; _environ[envc]; ++envc) { + ; + } + env = apr_palloc(ptemp, (envc + 2) * sizeof (char*)); + memcpy(env, _environ, envc * sizeof (char*)); + apr_snprintf(pidbuf, sizeof(pidbuf), "AP_PARENT_PID=%lu", parent_pid); + env[envc] = pidbuf; + env[envc + 1] = NULL; + + rv = apr_proc_create(&new_child, cmd, (const char * const *)args, + (const char * const *)env, attr, ptemp); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, APLOGNO(00417) + "Parent: Failed to create the child process."); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00418) + "Parent: Created child process %d", new_child.pid); + + if (send_handles_to_child(ptemp, waitlist[waitlist_ready], hExitEvent, + start_mutex, ap_scoreboard_shm, + new_child.hproc, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + /* Important: + * Give the child process a chance to run before dup'ing the sockets. + * We have already set the listening sockets noninheritable, but if + * WSADuplicateSocket runs before the child process initializes + * the listeners will be inherited anyway. + */ + waitlist[waitlist_term] = new_child.hproc; + rv = WaitForMultipleObjects(2, waitlist, FALSE, INFINITE); + CloseHandle(waitlist[waitlist_ready]); + if (rv != WAIT_OBJECT_0) { + /* + * Outch... that isn't a ready signal. It's dead, Jim! + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + if (send_listeners_to_child(ptemp, new_child.pid, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + apr_file_close(new_child.in); + + *child_exit_event = hExitEvent; + *child_proc = new_child.hproc; + *child_pid = new_child.pid; + + return 0; +} + +/*********************************************************************** + * master_main() + * master_main() runs in the parent process. It creates the child + * process which handles HTTP requests then waits on one of three + * events: + * + * restart_event + * ------------- + * The restart event causes master_main to start a new child process and + * tells the old child process to exit (by setting the child_exit_event). + * The restart event is set as a result of one of the following: + * 1. An apache -k restart command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_RESTART) + * call by code in service.c. + * 3. The child process calling ap_signal_parent(SIGNAL_PARENT_RESTART) + * as a result of hitting MaxConnectionsPerChild. + * + * shutdown_event + * -------------- + * The shutdown event causes master_main to tell the child process to + * exit and that the server is shutting down. The shutdown event is + * set as a result of one of the following: + * 1. An apache -k shutdown command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_SHUTDOWN) + * call by code in service.c. + * + * child process handle + * -------------------- + * The child process handle will be signaled if the child process + * exits for any reason. In a normal running server, the signaling + * of this event means that the child process has exited prematurely + * due to a seg fault or other irrecoverable error. For server + * robustness, master_main will restart the child process under this + * condtion. + * + * master_main uses the child_exit_event to signal the child process + * to exit. + **********************************************************************/ +#define NUM_WAIT_HANDLES 3 +#define CHILD_HANDLE 0 +#define SHUTDOWN_HANDLE 1 +#define RESTART_HANDLE 2 +static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_event) +{ + int rv, cld; + int child_created; + int restart_pending; + int shutdown_pending; + HANDLE child_exit_event; + HANDLE event_handles[NUM_WAIT_HANDLES]; + DWORD child_pid; + + child_created = restart_pending = shutdown_pending = 0; + + event_handles[SHUTDOWN_HANDLE] = shutdown_event; + event_handles[RESTART_HANDLE] = restart_event; + + /* Create a single child process */ + rv = create_process(pconf, &event_handles[CHILD_HANDLE], + &child_exit_event, &child_pid); + if (rv < 0) + { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00419) + "master_main: create child process failed. Exiting."); + shutdown_pending = 1; + goto die_now; + } + + child_created = 1; + + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_started(); + } + + /* Update the scoreboard. Note that there is only a single active + * child at once. + */ + ap_scoreboard_image->parent[0].quiescing = 0; + winnt_note_child_started(/* slot */ 0, child_pid); + + /* Wait for shutdown or restart events or for child death */ + winnt_mpm_state = AP_MPMQ_RUNNING; + rv = WaitForMultipleObjects(NUM_WAIT_HANDLES, (HANDLE *) event_handles, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK,APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00420) + "master_main: WaitForMultipleObjects WAIT_FAILED -- doing server shutdown"); + shutdown_pending = 1; + } + else if (rv == WAIT_TIMEOUT) { + /* Hey, this cannot happen */ + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00421) + "master_main: WaitForMultipleObjects with INFINITE wait exited with WAIT_TIMEOUT"); + shutdown_pending = 1; + } + else if (cld == SHUTDOWN_HANDLE) { + /* shutdown_event signalled */ + shutdown_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, s, APLOGNO(00422) + "Parent: Received shutdown signal -- Shutting down the server."); + if (ResetEvent(shutdown_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00423) + "ResetEvent(shutdown_event)"); + } + } + else if (cld == RESTART_HANDLE) { + /* Received a restart event. Prepare the restart_event to be reused + * then signal the child process to exit. + */ + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(00424) + "Parent: Received restart signal -- Restarting the server."); + if (ResetEvent(restart_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00425) + "Parent: ResetEvent(restart_event) failed."); + } + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, APLOGNO(00426) + "Parent: SetEvent for child process event %pp failed.", + event_handles[CHILD_HANDLE]); + } + /* Don't wait to verify that the child process really exits, + * just move on with the restart. + */ + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + /* The child process exited prematurely due to a fatal error. */ + DWORD exitcode; + if (!GetExitCodeProcess(event_handles[CHILD_HANDLE], &exitcode)) { + /* HUH? We did exit, didn't we? */ + exitcode = APEXIT_CHILDFATAL; + } + if ( exitcode == APEXIT_CHILDFATAL + || exitcode == APEXIT_CHILDINIT + || exitcode == APEXIT_INIT) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(00427) + "Parent: child process %lu exited with status %lu -- Aborting.", + child_pid, exitcode); + shutdown_pending = 1; + } + else { + int i; + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00428) + "Parent: child process %lu exited with status %lu -- Restarting.", + child_pid, exitcode); + for (i = 0; i < ap_threads_per_child; i++) { + ap_update_child_status_from_indexes(0, i, SERVER_DEAD, NULL); + } + } + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + + winnt_note_child_killed(/* slot */ 0); + + if (restart_pending) { + ++my_generation; + ap_scoreboard_image->global->running_generation = my_generation; + } +die_now: + if (shutdown_pending) + { + int timeout = 30000; /* Timeout is milliseconds */ + winnt_mpm_state = AP_MPMQ_STOPPING; + + if (!child_created) { + return 0; /* Tell the caller we do not want to restart */ + } + + /* This shutdown is only marginally graceful. We will give the + * child a bit of time to exit gracefully. If the time expires, + * the child will be wacked. + */ + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_stopping(); + } + /* Signal the child processes to exit */ + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_os_error(), ap_server_conf, APLOGNO(00429) + "Parent: SetEvent for child process event %pp failed", + event_handles[CHILD_HANDLE]); + } + if (event_handles[CHILD_HANDLE]) { + rv = WaitForSingleObject(event_handles[CHILD_HANDLE], timeout); + if (rv == WAIT_OBJECT_0) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00430) + "Parent: Child process %lu exited successfully.", child_pid); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, APLOGNO(00431) + "Parent: Forcing termination of child process %lu", + child_pid); + TerminateProcess(event_handles[CHILD_HANDLE], 1); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + } + CloseHandle(child_exit_event); + return 0; /* Tell the caller we do not want to restart */ + } + winnt_mpm_state = AP_MPMQ_STARTING; + CloseHandle(child_exit_event); + return 1; /* Tell the caller we want a restart */ +} + +/* service_nt_main_fn needs to append the StartService() args + * outside of our call stack and thread as the service starts... + */ +apr_array_header_t *mpm_new_argv; + +/* Remember service_to_start failures to log and fail in pre_config. + * Remember inst_argc and inst_argv for installing or starting the + * service after we preflight the config. + */ + +static int winnt_query(int query_code, int *result, apr_status_t *rv) +{ + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_MAX_DAEMON_USED: + *result = MAXIMUM_WAIT_OBJECTS; + break; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = ap_threads_per_child; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + break; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + break; + case AP_MPMQ_MAX_DAEMONS: + *result = 1; + break; + case AP_MPMQ_MPM_STATE: + *result = winnt_mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return OK; +} + +static const char *winnt_get_name(void) +{ + return "WinNT"; +} + +#define SERVICE_UNSET (-1) +static apr_status_t service_set = SERVICE_UNSET; +static apr_status_t service_to_start_success; +static int inst_argc; +static const char * const *inst_argv; +static const char *service_name = NULL; + +static void winnt_rewrite_args(process_rec *process) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [transition in service context only] + * -k install + * -k config + * -k uninstall + * -k stop + * -k shutdown (same as -k stop). Maintained for backward compatibility. + * + * We can't leave this phase until we know our identity + * and modify the command arguments appropriately. + * + * We do not care if the .conf file exists or is parsable when + * attempting to stop or uninstall a service. + */ + apr_status_t rv; + char *def_server_root; + char *binpath; + char optbuf[3]; + const char *opt_arg; + int fixed_args; + char *pid; + apr_getopt_t *opt; + int running_as_service = 1; + int errout = 0; + apr_file_t *nullfile; + + pconf = process->pconf; + + osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osver); + + /* We wish this was *always* a reservation, but sadly it wasn't so and + * we couldn't break a hard limit prior to NT Kernel 5.1 + */ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT + && ((osver.dwMajorVersion > 5) + || ((osver.dwMajorVersion == 5) && (osver.dwMinorVersion > 0)))) { + stack_res_flag = STACK_SIZE_PARAM_IS_A_RESERVATION; + } + + /* AP_PARENT_PID is only valid in the child */ + pid = getenv("AP_PARENT_PID"); + if (pid) + { + HANDLE filehand; + HANDLE hproc = GetCurrentProcess(); + DWORD BytesRead; + + /* This is the child */ + my_pid = GetCurrentProcessId(); + parent_pid = (DWORD) atol(pid); + + /* Prevent holding open the (nonexistent) console */ + ap_real_exit_code = 0; + + /* The parent gave us stdin, we need to remember this + * handle, and no longer inherit it at our children + * (we can't slurp it up now, we just aren't ready yet). + * The original handle is closed below, at apr_file_dup2() + */ + pipe = GetStdHandle(STD_INPUT_HANDLE); + if (DuplicateHandle(hproc, pipe, + hproc, &filehand, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + pipe = filehand; + } + + /* The parent gave us stdout of the NUL device, + * and expects us to suck up stdin of all of our + * shared handles and data from the parent. + * Don't infect child processes with our stdin + * handle, use another handle to NUL! + */ + { + apr_file_t *infile, *outfile; + if ((apr_file_open_stdout(&outfile, process->pool) == APR_SUCCESS) + && (apr_file_open_stdin(&infile, process->pool) == APR_SUCCESS)) + apr_file_dup2(infile, outfile, process->pool); + } + + /* This child needs the existing stderr opened for logging, + * already + */ + + /* Read this child's generation number as soon as now, + * so that further hooks can query it. + */ + if (!ReadFile(pipe, &my_generation, sizeof(my_generation), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(my_generation))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), NULL, APLOGNO(02965) + "Child: Unable to retrieve my generation from the parent"); + exit(APEXIT_CHILDINIT); + } + + /* The parent is responsible for providing the + * COMPLETE ARGUMENTS REQUIRED to the child. + * + * No further argument parsing is needed, but + * for good measure we will provide a simple + * signal string for later testing. + */ + signal_arg = "runchild"; + return; + } + + /* This is the parent, we have a long way to go :-) */ + parent_pid = my_pid = GetCurrentProcessId(); + + /* This behavior is voided by setting real_exit_code to 0 */ + atexit(hold_console_open_on_error); + + /* Rewrite process->argv[]; + * + * strip out -k signal into signal_arg + * strip out -n servicename and set the names + * add default -d serverroot from the path of this executable + * + * The end result will look like: + * + * The invocation command (%0) + * The -d serverroot default from the running executable + * The requested service's (-n) registry ConfigArgs + * The WinNT SCM's StartService() args + */ + if ((rv = ap_os_proc_filepath(&binpath, process->pconf)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL, APLOGNO(00432) + "Failed to get the full path of %s", process->argv[0]); + exit(APEXIT_INIT); + } + /* WARNING: There is an implict assumption here that the + * executable resides in ServerRoot or ServerRoot\bin + */ + def_server_root = (char *) apr_filepath_name_get(binpath); + if (def_server_root > binpath) { + *(def_server_root - 1) = '\0'; + def_server_root = (char *) apr_filepath_name_get(binpath); + if (!strcasecmp(def_server_root, "bin")) + *(def_server_root - 1) = '\0'; + } + apr_filepath_merge(&def_server_root, NULL, binpath, + APR_FILEPATH_TRUENAME, process->pool); + + /* Use process->pool so that the rewritten argv + * lasts for the lifetime of the server process, + * because pconf will be destroyed after the + * initial pre-flight of the config parser. + */ + mpm_new_argv = apr_array_make(process->pool, process->argc + 2, + sizeof(const char *)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + *(const char **)apr_array_push(mpm_new_argv) = "-d"; + *(const char **)apr_array_push(mpm_new_argv) = def_server_root; + + fixed_args = mpm_new_argv->nelts; + + optbuf[0] = '-'; + optbuf[2] = '\0'; + apr_getopt_init(&opt, process->pool, process->argc, process->argv); + opt->errfn = NULL; + while ((rv = apr_getopt(opt, "wn:k:" AP_SERVER_BASEARGS, + optbuf + 1, &opt_arg)) == APR_SUCCESS) { + switch (optbuf[1]) { + + /* Shortcuts; include the -w option to hold the window open on error. + * This must not be toggled once we reset ap_real_exit_code to 0! + */ + case 'w': + if (ap_real_exit_code) + ap_real_exit_code = 2; + break; + + case 'n': + service_set = mpm_service_set_name(process->pool, &service_name, + opt_arg); + break; + + case 'k': + signal_arg = opt_arg; + break; + + case 'E': + errout = 1; + /* Fall through so the Apache main() handles the 'E' arg */ + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + + if (opt_arg) { + *(const char **)apr_array_push(mpm_new_argv) = opt_arg; + } + break; + } + } + + /* 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++]); + } + + /* Track the number of args actually entered by the user */ + inst_argc = mpm_new_argv->nelts - fixed_args; + + /* Provide a default 'run' -k arg to simplify signal_arg tests */ + if (!signal_arg) + { + signal_arg = "run"; + running_as_service = 0; + } + + if (!strcasecmp(signal_arg, "runservice")) + { + /* Start the NT Service _NOW_ because the WinNT SCM is + * expecting us to rapidly assume control of our own + * process, the SCM will tell us our service name, and + * may have extra StartService() command arguments to + * add for us. + * + * The SCM will generally invoke the executable with + * the c:\win\system32 default directory. This is very + * lethal if folks use ServerRoot /foopath on windows + * without a drive letter. Change to the default root + * (path to apache root, above /bin) for safety. + */ + apr_filepath_set(def_server_root, process->pool); + + /* Any other process has a console, so we don't to begin + * a Win9x service until the configuration is parsed and + * any command line errors are reported. + * + * We hold the return value so that we can die in pre_config + * after logging begins, and the failure can land in the log. + */ + if (!errout) { + mpm_nt_eventlog_stderr_open(service_name, process->pool); + } + service_to_start_success = mpm_service_to_start(&service_name, + process->pool); + if (service_to_start_success == APR_SUCCESS) { + service_set = APR_SUCCESS; + } + + /* Open a null handle to soak stdout in this process. + * Windows service processes are missing any file handle + * usable for stdin/out/err. This was the cause of later + * trouble with invocations of apr_file_open_stdout() + */ + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + process->pool)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, process->pool) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, process->pool); + apr_file_close(nullfile); + } + } + + /* Get the default for any -k option, except run */ + if (service_set == SERVICE_UNSET && strcasecmp(signal_arg, "run")) { + service_set = mpm_service_set_name(process->pool, &service_name, + AP_DEFAULT_SERVICE_NAME); + } + + if (!strcasecmp(signal_arg, "install")) /* -k install */ + { + if (service_set == APR_SUCCESS) + { + ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL, APLOGNO(00433) + "%s: Service is already installed.", service_name); + exit(APEXIT_INIT); + } + } + else if (running_as_service) + { + if (service_set == APR_SUCCESS) + { + /* Attempt to Uninstall, or stop, before + * we can read the arguments or .conf files + */ + if (!strcasecmp(signal_arg, "uninstall")) { + rv = mpm_service_uninstall(); + exit(rv); + } + + if ((!strcasecmp(signal_arg, "stop")) || + (!strcasecmp(signal_arg, "shutdown"))) { + mpm_signal_service(process->pool, 0); + exit(0); + } + + rv = mpm_merge_service_args(process->pool, mpm_new_argv, + fixed_args); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_INFO, 0, NULL, APLOGNO(00434) + "Using ConfigArgs of the installed service " + "\"%s\".", service_name); + } + else { + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL, APLOGNO(00435) + "No installed ConfigArgs for the service " + "\"%s\", using Apache defaults.", service_name); + } + } + else + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00436) + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + } + if (strcasecmp(signal_arg, "install") && service_set && service_set != SERVICE_UNSET) + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, APLOGNO(00437) + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + + /* Track the args actually entered by the user. + * These will be used for the -k install parameters, as well as + * for the -k start service override arguments. + */ + inst_argv = (const char * const *)mpm_new_argv->elts + + mpm_new_argv->nelts - inst_argc; + + /* Now, do service install or reconfigure then proceed to + * post_config to test the installed configuration. + */ + if (!strcasecmp(signal_arg, "config")) { /* -k config */ + /* Reconfigure the service */ + rv = mpm_service_install(process->pool, inst_argc, inst_argv, 1); + if (rv != APR_SUCCESS) { + exit(rv); + } + + fprintf(stderr,"Testing httpd.conf....\n"); + fprintf(stderr,"Errors reported here must be corrected before the " + "service can be started.\n"); + } + else if (!strcasecmp(signal_arg, "install")) { /* -k install */ + /* Install the service */ + rv = mpm_service_install(process->pool, inst_argc, inst_argv, 0); + if (rv != APR_SUCCESS) { + exit(rv); + } + + fprintf(stderr,"Testing httpd.conf....\n"); + fprintf(stderr,"Errors reported here must be corrected before the " + "service can be started.\n"); + } + + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *) mpm_new_argv->elts; +} + + +static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *ptemp) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [WinNT errors logged from rewrite_args] + */ + + /* Initialize shared static objects. + * TODO: Put config related statics into an sconf structure. + */ + pconf = pconf_; + + if (ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG")) + one_process = -1; + + /* XXX: presume proper privilages; one nice thing would be + * a loud emit if running as "LocalSystem"/"SYSTEM" to indicate + * they should change to a user with write access to logs/ alone. + */ + ap_sys_privileges_handlers(1); + + if (!strcasecmp(signal_arg, "runservice") + && (service_to_start_success != APR_SUCCESS)) { + ap_log_error(APLOG_MARK,APLOG_CRIT, service_to_start_success, NULL, APLOGNO(00438) + "%s: Unable to start the service manager.", + service_name); + exit(APEXIT_INIT); + } + else if (ap_state_query(AP_SQ_RUN_MODE) == AP_SQ_RM_NORMAL + && !one_process && !my_generation) { + /* Open a null handle to soak stdout in this process. + * We need to emulate apr_proc_detach, unix performs this + * same check in the pre_config hook (although it is + * arguably premature). Services already fixed this. + */ + apr_file_t *nullfile; + apr_status_t rv; + apr_pool_t *pproc = apr_pool_parent_get(pconf); + + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + pproc)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, pproc) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, pproc); + apr_file_close(nullfile); + } + } + + ap_listen_pre_config(); + thread_limit = DEFAULT_THREAD_LIMIT; + ap_threads_per_child = DEFAULT_THREADS_PER_CHILD; + + return OK; +} + +static int winnt_check_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec* s) +{ + int is_parent; + int startup = 0; + + /* We want this only in the parent and only the first time around */ + is_parent = (parent_pid == my_pid); + if (is_parent && + ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) { + startup = 1; + } + + if (thread_limit > MAX_THREAD_LIMIT) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00439) + "WARNING: ThreadLimit of %d exceeds compile-time " + "limit of %d threads, decreasing to %d.", + thread_limit, MAX_THREAD_LIMIT, MAX_THREAD_LIMIT); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00440) + "ThreadLimit of %d exceeds compile-time limit " + "of %d, decreasing to match", + thread_limit, MAX_THREAD_LIMIT); + } + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00441) + "WARNING: ThreadLimit of %d not allowed, " + "increasing to 1.", thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00442) + "ThreadLimit of %d not allowed, increasing to 1", + thread_limit); + } + thread_limit = 1; + } + + /* You cannot change ThreadLimit across a restart; ignore + * any such attempts. + */ + if (!first_thread_limit) { + first_thread_limit = thread_limit; + } + else if (thread_limit != first_thread_limit) { + /* Don't need a startup console version here */ + if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00443) + "changing ThreadLimit to %d from original value " + "of %d not allowed during restart", + thread_limit, first_thread_limit); + } + thread_limit = first_thread_limit; + } + + if (ap_threads_per_child > thread_limit) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00444) + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "of %d threads, decreasing to %d. To increase, please " + "see the ThreadLimit directive.", + ap_threads_per_child, thread_limit, thread_limit); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00445) + "ThreadsPerChild of %d exceeds ThreadLimit " + "of %d, decreasing to match", + ap_threads_per_child, thread_limit); + } + ap_threads_per_child = thread_limit; + } + else if (ap_threads_per_child < 1) { + if (startup) { + ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00446) + "WARNING: ThreadsPerChild of %d not allowed, " + "increasing to 1.", ap_threads_per_child); + } else if (is_parent) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00447) + "ThreadsPerChild of %d not allowed, increasing to 1", + ap_threads_per_child); + } + ap_threads_per_child = 1; + } + + return OK; +} + +static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec* s) +{ + apr_status_t rv = 0; + + /* Handle the following SCM aspects in this phase: + * + * -k install (catch and exit as install was handled in rewrite_args) + * -k config (catch and exit as config was handled in rewrite_args) + * -k start + * -k restart + * -k runservice [Win95, only once - after we parsed the config] + * + * because all of these signals are useful _only_ if there + * is a valid conf\httpd.conf environment to start. + * + * We reached this phase by avoiding errors that would cause + * these options to fail unexpectedly in another process. + */ + + if (!strcasecmp(signal_arg, "install")) { + /* Service install happens in the rewrite_args hooks. If we + * made it this far, the server configuration is clean and the + * service will successfully start. + */ + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(0); + } + if (!strcasecmp(signal_arg, "config")) { + /* Service reconfiguration happens in the rewrite_args hooks. If we + * made it this far, the server configuration is clean and the + * service will successfully start. + */ + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(0); + } + + if (!strcasecmp(signal_arg, "start")) { + ap_listen_rec *lr; + + /* Close the listening sockets. */ + for (lr = ap_listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + lr->active = 0; + } + rv = mpm_service_start(ptemp, inst_argc, inst_argv); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit (rv); + } + + if (!strcasecmp(signal_arg, "restart")) { + mpm_signal_service(ptemp, 1); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit (rv); + } + + if (parent_pid == my_pid) + { + if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG + && ap_state_query(AP_SQ_CONFIG_GEN) == 1) + { + /* This code should be run once in the parent and not run + * across a restart + */ + PSECURITY_ATTRIBUTES sa = GetNullACL(); /* returns NULL if invalid (Win95?) */ + setup_signal_names(apr_psprintf(pconf, "ap%lu", parent_pid)); + + ap_log_pid(pconf, ap_pid_fname); + + /* Create shutdown event, apPID_shutdown, where PID is the parent + * Apache process ID. Shutdown is signaled by 'apache -k shutdown'. + */ + shutdown_event = CreateEvent(sa, FALSE, FALSE, signal_shutdown_name); + if (!shutdown_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00448) + "Parent: Cannot create shutdown event %s", signal_shutdown_name); + CleanNullACL((void *)sa); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Create restart event, apPID_restart, where PID is the parent + * Apache process ID. Restart is signaled by 'apache -k restart'. + */ + restart_event = CreateEvent(sa, FALSE, FALSE, signal_restart_name); + if (!restart_event) { + CloseHandle(shutdown_event); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00449) + "Parent: Cannot create restart event %s", signal_restart_name); + CleanNullACL((void *)sa); + return HTTP_INTERNAL_SERVER_ERROR; + } + CleanNullACL((void *)sa); + + /* Create the start mutex, as an unnamed object for security. + * Ths start mutex is used during a restart to prevent more than + * one child process from entering the accept loop at once. + */ + rv = apr_proc_mutex_create(&start_mutex, NULL, + APR_LOCK_DEFAULT, + ap_server_conf->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00450) + "%s: Unable to create the start_mutex.", + service_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + /* Always reset our console handler to be the first, even on a restart + * because some modules (e.g. mod_perl) might have set a console + * handler to terminate the process. + */ + if (strcasecmp(signal_arg, "runservice")) + mpm_start_console_handler(); + } + else /* parent_pid != my_pid */ + { + mpm_start_child_console_handler(); + } + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + /* Initialize shared static objects. + */ + if (parent_pid != my_pid) { + return OK; + } + + /* We cannot initialize our listeners if we are restarting + * (the parent process already has glomed on to them) + * nor should we do so for service reconfiguration + * (since the service may already be running.) + */ + if (!strcasecmp(signal_arg, "restart") + || !strcasecmp(signal_arg, "config")) { + return OK; + } + + if (ap_setup_listeners(s) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, + NULL, APLOGNO(00451) "no listening sockets available, shutting down"); + return !OK; + } + + return OK; +} + +static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) +{ + apr_status_t rv; + + setup_signal_names(apr_psprintf(pchild, "ap%lu", parent_pid)); + + /* This is a child process, not in single process mode */ + if (!one_process) { + /* Set up events and the scoreboard */ + get_handles_from_parent(s, &exit_event, &start_mutex, + &ap_scoreboard_shm); + + /* Set up the listeners */ + get_listeners_from_parent(s); + + /* Done reading from the parent, close that channel */ + CloseHandle(pipe); + } + else { + /* Single process mode - this lock doesn't even need to exist */ + rv = apr_proc_mutex_create(&start_mutex, signal_name_prefix, + APR_LOCK_DEFAULT, s->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, APLOGNO(00452) + "%s child: Unable to init the start_mutex.", + service_name); + exit(APEXIT_CHILDINIT); + } + + /* Borrow the shutdown_even as our _child_ loop exit event */ + exit_event = shutdown_event; + } +} + + +static int winnt_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + static int restart = 0; /* Default is "not a restart" */ + + /* ### If non-graceful restarts are ever introduced - we need to rerun + * the pre_mpm hook on subsequent non-graceful restarts. But Win32 + * has only graceful style restarts - and we need this hook to act + * the same on Win32 as on Unix. + */ + if (!restart && ((parent_pid == my_pid) || one_process)) { + /* Set up the scoreboard. */ + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + return !OK; + } + } + + if ((parent_pid != my_pid) || one_process) + { + /* The child process or in one_process (debug) mode + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00453) + "Child process is running"); + + child_main(pconf, parent_pid); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, APLOGNO(00454) + "Child process is exiting"); + return DONE; + } + else + { + /* A real-honest to goodness parent */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00455) + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00456) + "Server built: %s", ap_get_server_built()); + ap_log_command_line(plog, s); + ap_log_mpm_common(s); + + restart = master_main(ap_server_conf, shutdown_event, restart_event); + + if (!restart) + { + /* Shutting down. Clean up... */ + ap_remove_pid(pconf, ap_pid_fname); + apr_proc_mutex_destroy(start_mutex); + + CloseHandle(restart_event); + CloseHandle(shutdown_event); + + return DONE; + } + } + + return OK; /* Restart */ +} + +static void winnt_hooks(apr_pool_t *p) +{ + /* Our open_logs hook function must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + + ap_hook_pre_config(winnt_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_check_config(winnt_check_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(winnt_post_config, NULL, NULL, 0); + ap_hook_child_init(winnt_child_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_open_logs(winnt_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); + ap_hook_mpm(winnt_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(winnt_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(winnt_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(mpm_winnt) = { + MPM20_MODULE_STUFF, + winnt_rewrite_args, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + winnt_cmds, /* command apr_table_t */ + winnt_hooks /* register_hooks */ +}; + +#endif /* def WIN32 */ diff --git a/server/mpm/winnt/mpm_winnt.h b/server/mpm/winnt/mpm_winnt.h new file mode 100644 index 0000000..22ba001 --- /dev/null +++ b/server/mpm/winnt/mpm_winnt.h @@ -0,0 +1,96 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mpm_winnt.h + * @brief WinNT MPM specific + * + * @addtogroup APACHE_MPM_WINNT + * @{ + */ + +#ifndef APACHE_MPM_WINNT_H +#define APACHE_MPM_WINNT_H + +#include "apr_proc_mutex.h" +#include "ap_listen.h" + +/* From service.c: */ + +#define SERVICE_APACHE_RESTART 128 + +#ifndef AP_DEFAULT_SERVICE_NAME +#define AP_DEFAULT_SERVICE_NAME "Apache2.4" +#endif + +#define SERVICECONFIG "System\\CurrentControlSet\\Services\\%s" +#define SERVICEPARAMS "System\\CurrentControlSet\\Services\\%s\\Parameters" + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name); +apr_status_t mpm_merge_service_args(apr_pool_t *p, apr_array_header_t *args, + int fixed_args); + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p); +apr_status_t mpm_service_started(void); +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + char const* const* argv, int reconfig); +apr_status_t mpm_service_uninstall(void); + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + char const* const* argv); + +void mpm_signal_service(apr_pool_t *ptemp, int signal); + +void mpm_service_stopping(void); + +void mpm_start_console_handler(void); +void mpm_start_child_console_handler(void); + +/* From nt_eventlog.c: */ + +void mpm_nt_eventlog_stderr_open(const char *display_name, apr_pool_t *p); +void mpm_nt_eventlog_stderr_flush(void); + +/* From mpm_winnt.c: */ + +extern module AP_MODULE_DECLARE_DATA mpm_winnt_module; +extern int ap_threads_per_child; + +extern DWORD my_pid; +extern apr_proc_mutex_t *start_mutex; +extern HANDLE exit_event; + +extern int winnt_mpm_state; +extern OSVERSIONINFO osver; +extern DWORD stack_res_flag; + +extern void clean_child_exit(int); + +typedef enum { + SIGNAL_PARENT_SHUTDOWN, + SIGNAL_PARENT_RESTART, + SIGNAL_PARENT_RESTART_GRACEFUL +} ap_signal_parent_e; +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type); + +void hold_console_open_on_error(void); + +/* From child.c: */ +void child_main(apr_pool_t *pconf, DWORD parent_pid); + +#endif /* APACHE_MPM_WINNT_H */ +/** @} */ diff --git a/server/mpm/winnt/nt_eventlog.c b/server/mpm/winnt/nt_eventlog.c new file mode 100644 index 0000000..e7e80d0 --- /dev/null +++ b/server/mpm/winnt/nt_eventlog.c @@ -0,0 +1,171 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_portable.h" +#include "ap_regkey.h" + +static const char *display_name = NULL; +static HANDLE stderr_thread = NULL; +static HANDLE stderr_ready; + +static DWORD WINAPI service_stderr_thread(LPVOID hPipe) +{ + HANDLE hPipeRead = (HANDLE) hPipe; + HANDLE hEventSource; + char errbuf[256]; + char *errmsg = errbuf; + const char *errarg[9]; + DWORD errres; + ap_regkey_t *regkey; + apr_status_t rv; + apr_pool_t *p; + + apr_pool_create_ex(&p, NULL, NULL, NULL); + + errarg[0] = "The Apache service named"; + errarg[1] = display_name; + errarg[2] = "reported the following error:\r\n>>>"; + errarg[3] = errbuf; + errarg[4] = NULL; + errarg[5] = NULL; + errarg[6] = NULL; + errarg[7] = NULL; + errarg[8] = NULL; + + /* What are we going to do in here, bail on the user? not. */ + if ((rv = ap_regkey_open(®key, AP_REGKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\" + "EventLog\\Application\\Apache Service", + APR_READ | APR_WRITE | APR_CREATE, p)) + == APR_SUCCESS) + { + DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + + /* The stock message file */ + ap_regkey_value_set(regkey, "EventMessageFile", + "%SystemRoot%\\System32\\netmsg.dll", + AP_REGKEY_EXPAND, p); + + ap_regkey_value_raw_set(regkey, "TypesSupported", &dwData, + sizeof(dwData), REG_DWORD, p); + ap_regkey_close(regkey); + } + + hEventSource = RegisterEventSourceW(NULL, L"Apache Service"); + + SetEvent(stderr_ready); + + while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1)) + { + if ((errmsg > errbuf) || !apr_isspace(*errmsg)) + { + ++errmsg; + if ((*(errmsg - 1) == '\n') + || (errmsg >= errbuf + sizeof(errbuf) - 1)) + { + while ((errmsg > errbuf) && apr_isspace(*(errmsg - 1))) { + --errmsg; + } + *errmsg = '\0'; + + /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9' + * The event code in netmsg.dll is 3299 + */ + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + errmsg = errbuf; + } + } + } + + if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) { + apr_snprintf(errbuf, sizeof(errbuf), + "Win32 error %lu reading stderr pipe stream\r\n", + GetLastError()); + + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + } + + CloseHandle(hPipeRead); + DeregisterEventSource(hEventSource); + CloseHandle(stderr_thread); + stderr_thread = NULL; + apr_pool_destroy(p); + return 0; +} + + +void mpm_nt_eventlog_stderr_flush(void) +{ + HANDLE cleanup_thread = stderr_thread; + + if (cleanup_thread) { + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + fclose(stderr); + CloseHandle(hErr); + WaitForSingleObject(cleanup_thread, 30000); + CloseHandle(cleanup_thread); + } +} + + +void mpm_nt_eventlog_stderr_open(const char *argv0, apr_pool_t *p) +{ + SECURITY_ATTRIBUTES sa; + HANDLE hPipeRead = NULL; + HANDLE hPipeWrite = NULL; + DWORD threadid; + apr_file_t *eventlog_file; + apr_file_t *stderr_file; + + display_name = argv0; + + /* Create a pipe to send stderr messages to the system error log. + * + * _dup2() duplicates the write handle inheritable for us. + */ + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = FALSE; + CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0); + ap_assert(hPipeRead && hPipeWrite); + + stderr_ready = CreateEvent(NULL, FALSE, FALSE, NULL); + stderr_thread = CreateThread(NULL, 65536, service_stderr_thread, + (LPVOID)hPipeRead, stack_res_flag, &threadid); + ap_assert(stderr_ready && stderr_thread); + + WaitForSingleObject(stderr_ready, INFINITE); + + if ((apr_file_open_stderr(&stderr_file, p) + == APR_SUCCESS) + && (apr_os_file_put(&eventlog_file, &hPipeWrite, APR_WRITE, p) + == APR_SUCCESS)) + apr_file_dup2(stderr_file, eventlog_file, p); + + /* The code above _will_ corrupt the StdHandle... + * and we must do so anyways. We set this up only + * after we initialized the posix stderr API. + */ + ap_open_stderr_log(p); +} diff --git a/server/mpm/winnt/service.c b/server/mpm/winnt/service.c new file mode 100644 index 0000000..121aca1 --- /dev/null +++ b/server/mpm/winnt/service.c @@ -0,0 +1,1241 @@ +/* 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. + */ + +/* This module ALONE requires the window message API from user.h + * and the default APR include of windows.h will omit it, so + * preload the API symbols now... + */ + +#define _WINUSER_ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" +#if APR_HAS_UNICODE_FS +#include "arch/win32/apr_arch_utf8.h" +#include "arch/win32/apr_arch_misc.h" +#include <wchar.h> +#endif + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "ap_regkey.h" + +#ifdef NOUSER +#undef NOUSER +#endif +#undef _WINUSER_ +#include <winuser.h> +#include <time.h> + +APLOG_USE_MODULE(mpm_winnt); + +/* Todo; clear up statics */ +static char *mpm_service_name = NULL; +static char *mpm_display_name = NULL; + +#if APR_HAS_UNICODE_FS +static apr_wchar_t *mpm_service_name_w; +#endif + +typedef struct nt_service_ctx_t +{ + HANDLE mpm_thread; /* primary thread handle of the apache server */ + HANDLE service_thread; /* thread service/monitor handle */ + DWORD service_thread_id;/* thread service/monitor ID */ + HANDLE service_init; /* controller thread init mutex */ + HANDLE service_term; /* NT service thread kill signal */ + SERVICE_STATUS ssStatus; + SERVICE_STATUS_HANDLE hServiceStatus; +} nt_service_ctx_t; + +static nt_service_ctx_t globdat; + +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx); + +/* Rather than repeat this logic throughout, create an either-or wide or narrow + * implementation because we don't actually pass strings to OpenSCManager. + * This election is based on build time defines and runtime os version test. + */ +#undef OpenSCManager +typedef SC_HANDLE (WINAPI *fpt_OpenSCManager)(const void *lpMachine, + const void *lpDatabase, + DWORD dwAccess); +static fpt_OpenSCManager pfn_OpenSCManager = NULL; +static APR_INLINE SC_HANDLE OpenSCManager(const void *lpMachine, + const void *lpDatabase, + DWORD dwAccess) +{ + if (!pfn_OpenSCManager) { +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + pfn_OpenSCManager = (fpt_OpenSCManager)OpenSCManagerW; +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + pfn_OpenSCManager = (fpt_OpenSCManager)OpenSCManagerA; +#endif + } + return (*(pfn_OpenSCManager))(lpMachine, lpDatabase, dwAccess); +} + +/* exit() for Win32 is macro mapped (horrible, we agree) that allows us + * to catch the non-zero conditions and inform the console process that + * the application died, and hang on to the console a bit longer. + * + * The macro only maps for http_main.c and other sources that include + * the service.h header, so we best assume it's an error to exit from + * _any_ other module. + * + * If ap_real_exit_code is reset to 0, it will not be set or trigger this + * behavior on exit. All service and child processes are expected to + * reset this flag to zero to avoid undesirable side effects. + */ +AP_DECLARE_DATA int ap_real_exit_code = 1; + +void hold_console_open_on_error(void) +{ + HANDLE hConIn; + HANDLE hConErr; + DWORD result; + time_t start; + time_t remains; + char *msg = "Note the errors or messages above, " + "and press the <ESC> key to exit. "; + CONSOLE_SCREEN_BUFFER_INFO coninfo; + INPUT_RECORD in; + char count[16]; + + if (!ap_real_exit_code) + return; + hConIn = GetStdHandle(STD_INPUT_HANDLE); + hConErr = GetStdHandle(STD_ERROR_HANDLE); + if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE)) + return; + if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) + || !result) + return; + if (!GetConsoleScreenBufferInfo(hConErr, &coninfo)) + return; + if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80)) + return; + + start = time(NULL); + do + { + while (PeekConsoleInput(hConIn, &in, 1, &result) && result) + { + if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result) + return; + if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown + && (in.Event.KeyEvent.uChar.AsciiChar == 27)) + return; + if (in.EventType == MOUSE_EVENT + && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)) + return; + } + remains = ((start + 30) - time(NULL)); + sprintf(count, "%d...", + (int)remains); /* 30 or less, so can't overflow int */ + if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition)) + return; + if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL) + || !result) + return; + } + while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED); +} + +static BOOL CALLBACK console_control_handler(DWORD ctrl_type) +{ + switch (ctrl_type) + { + case CTRL_BREAK_EVENT: + fprintf(stderr, "Apache server restarting...\n"); + ap_signal_parent(SIGNAL_PARENT_RESTART); + return TRUE; + case CTRL_C_EVENT: + fprintf(stderr, "Apache server interrupted...\n"); + /* for Interrupt signals, shut down the server. + * Tell the system we have dealt with the signal + * without waiting for Apache to terminate. + */ + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + return TRUE; + + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + /* for Terminate signals, shut down the server. + * Wait for Apache to terminate, but respond + * after a reasonable time to tell the system + * that we did attempt to shut ourself down. + */ + fprintf(stderr, "Apache server shutdown initiated...\n"); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + Sleep(30000); + return TRUE; + } + + /* We should never get here, but this is (mostly) harmless */ + return FALSE; +} + + +static void stop_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, FALSE); +} + + +void mpm_start_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, TRUE); + atexit(stop_console_handler); +} + + +void mpm_start_child_console_handler(void) +{ + FreeConsole(); +} + + +/********************************** + WinNT service control management + **********************************/ + +static int ReportStatusToSCMgr(int currentState, int waitHint, + nt_service_ctx_t *ctx) +{ + int rv = APR_SUCCESS; + + if (ctx->hServiceStatus) + { + if (currentState == SERVICE_RUNNING) { + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + ctx->ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP + | SERVICE_ACCEPT_SHUTDOWN; + } + else if (currentState == SERVICE_STOPPED) { + ctx->ssStatus.dwWaitHint = 0; + ctx->ssStatus.dwCheckPoint = 0; + /* An unexpected exit? Better to error! */ + if (ctx->ssStatus.dwCurrentState != SERVICE_STOP_PENDING + && !ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwServiceSpecificExitCode = 1; + if (ctx->ssStatus.dwServiceSpecificExitCode) + ctx->ssStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + } + else { + ++ctx->ssStatus.dwCheckPoint; + ctx->ssStatus.dwControlsAccepted = 0; + if(waitHint) + ctx->ssStatus.dwWaitHint = waitHint; + } + + ctx->ssStatus.dwCurrentState = currentState; + + rv = SetServiceStatus(ctx->hServiceStatus, &ctx->ssStatus); + } + return(rv); +} + +/* Note this works on Win2000 and later due to ChangeServiceConfig2 + * Continue to test its existence, but at least drop the feature + * of revising service description tags prior to Win2000. + */ + +/* borrowed from mpm_winnt.c */ +extern apr_pool_t *pconf; + +static void set_service_description(void) +{ + const char *full_description; + SC_HANDLE schSCManager; + + /* Nothing to do if we are a console + */ + if (!mpm_service_name) + return; + + /* Time to fix up the description, upon each successful restart + */ + full_description = ap_get_server_description(); + + if ((ChangeServiceConfig2) && + (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT))) + { + SC_HANDLE schService; + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, + (LPCWSTR)mpm_service_name_w, + SERVICE_CHANGE_CONFIG); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + } +#endif + if (schService) { + /* Cast is necessary, ChangeServiceConfig2 handles multiple + * object types, some volatile, some not. + */ +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(full_description) + 1; + apr_size_t wslen = slen; + apr_wchar_t *full_description_w = + (apr_wchar_t*)apr_palloc(pconf, + wslen * sizeof(apr_wchar_t)); + apr_status_t rv = apr_conv_utf8_to_ucs2(full_description, &slen, + full_description_w, + &wslen); + if ((rv != APR_SUCCESS) || slen + || ChangeServiceConfig2W(schService, 1 + /*SERVICE_CONFIG_DESCRIPTION*/, + (LPVOID) &full_description_w)) + full_description = NULL; + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + if (ChangeServiceConfig2(schService, + 1 /* SERVICE_CONFIG_DESCRIPTION */, + (LPVOID) &full_description)) + full_description = NULL; + } +#endif + CloseServiceHandle(schService); + } + CloseServiceHandle(schSCManager); + } +} + +/* handle the SCM's ControlService() callbacks to our service */ + +static DWORD WINAPI service_nt_ctrl(DWORD dwCtrlCode, DWORD dwEventType, + LPVOID lpEventData, LPVOID lpContext) +{ + nt_service_ctx_t *ctx = lpContext; + + /* SHUTDOWN is offered before STOP, accept the first opportunity */ + if ((dwCtrlCode == SERVICE_CONTROL_STOP) + || (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN)) + { + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_APACHE_RESTART) + { + ap_signal_parent(SIGNAL_PARENT_RESTART); + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + return (NO_ERROR); + } + if (dwCtrlCode == SERVICE_CONTROL_INTERROGATE) { + ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, 0, ctx); + return (NO_ERROR); + } + + return (ERROR_CALL_NOT_IMPLEMENTED); +} + + +/* service_nt_main_fn is outside of the call stack and outside of the + * primary server thread... so now we _really_ need a placeholder! + * The winnt_rewrite_args has created and shared mpm_new_argv with us. + */ +extern apr_array_header_t *mpm_new_argv; + +#if APR_HAS_UNICODE_FS +static void __stdcall service_nt_main_fn_w(DWORD argc, LPWSTR *argv) +{ + const char *ignored; + nt_service_ctx_t *ctx = &globdat; + char *service_name; + apr_size_t wslen = wcslen(argv[0]) + 1; + apr_size_t slen = wslen * 3 - 2; + + service_name = malloc(slen); + (void)apr_conv_ucs2_to_utf8(argv[0], &wslen, service_name, &slen); + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, service_name); + + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; + if (!(ctx->hServiceStatus = + RegisterServiceCtrlHandlerExW(argv[0], service_nt_ctrl, ctx))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(00365) "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we have no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data, **cmb; + DWORD i; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + cmb = cmb_data + mpm_new_argv->nelts; + + for (i = 1; i < argc; ++i) + { + wslen = wcslen(argv[i]) + 1; + slen = wslen * 3 - 2; + service_name = malloc(slen); + (void)apr_conv_ucs2_to_utf8(argv[i], &wslen, *(cmb++), &slen); + } + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(ctx->service_init); + + WaitForSingleObject(ctx->service_term, INFINITE); +} +#endif /* APR_HAS_UNICODE_FS */ + + +#if APR_HAS_ANSI_FS +static void __stdcall service_nt_main_fn(DWORD argc, LPSTR *argv) +{ + const char *ignored; + nt_service_ctx_t *ctx = &globdat; + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]); + + memset(&ctx->ssStatus, 0, sizeof(ctx->ssStatus)); + ctx->ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ctx->ssStatus.dwCurrentState = SERVICE_START_PENDING; + ctx->ssStatus.dwCheckPoint = 1; + + if (!(ctx->hServiceStatus = + RegisterServiceCtrlHandlerExA(argv[0], service_nt_ctrl, ctx))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10008) "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, 30000, ctx); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we have no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(ctx->service_init); + + WaitForSingleObject(ctx->service_term, INFINITE); +} +#endif + + + static DWORD WINAPI service_nt_dispatch_thread(LPVOID nada) + { +#if APR_HAS_UNICODE_FS + SERVICE_TABLE_ENTRYW dispatchTable_w[] = + { + { L"", service_nt_main_fn_w }, + { NULL, NULL } + }; +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + SERVICE_TABLE_ENTRYA dispatchTable[] = + { + { "", service_nt_main_fn }, + { NULL, NULL } + }; +#endif + apr_status_t rv; + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + rv = StartServiceCtrlDispatcherW(dispatchTable_w); +#endif +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + rv = StartServiceCtrlDispatcherA(dispatchTable); +#endif + if (rv) { + rv = APR_SUCCESS; + } + else { + /* This is a genuine failure of the SCM. */ + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00366) "Error starting Windows service control " + "dispatcher"); + } + return (rv); +} + + +/* The service configuration's is stored under the following trees: + * + * HKLM\System\CurrentControlSet\Services\[service name] + * + * \DisplayName + * \ImagePath + * \Parameters\ConfigArgs + */ + + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name) +{ + char key_name[MAX_PATH]; + ap_regkey_t *key; + apr_status_t rv; + + /* ### Needs improvement, on Win2K the user can _easily_ + * change the display name to a string that doesn't reflect + * the internal service name + whitespace! + */ + mpm_service_name = apr_palloc(p, strlen(set_name) + 1); + apr_collapse_spaces((char*) mpm_service_name, set_name); +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(mpm_service_name) + 1; + apr_size_t wslen = slen; + mpm_service_name_w = apr_palloc(p, wslen * sizeof(apr_wchar_t)); + rv = apr_conv_utf8_to_ucs2(mpm_service_name, &slen, + mpm_service_name_w, &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + } +#endif /* APR_HAS_UNICODE_FS */ + + apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + /* Take the given literal name if there is no service entry */ + mpm_display_name = apr_pstrdup(p, set_name); + } + *display_name = mpm_display_name; + + return rv; +} + + +apr_status_t mpm_merge_service_args(apr_pool_t *p, + apr_array_header_t *args, + int fixed_args) +{ + apr_array_header_t *svc_args = NULL; + char conf_key[MAX_PATH]; + char **cmb_data; + apr_status_t rv; + ap_regkey_t *key; + + apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + if (rv == ERROR_FILE_NOT_FOUND) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(00367) + "No ConfigArgs registered for the '%s' service, " + "perhaps this service is not installed?", + mpm_service_name); + return APR_SUCCESS; + } + else + return (rv); + } + + if (!svc_args || svc_args->nelts == 0) { + return (APR_SUCCESS); + } + + /* Now we have the mpm_service_name arg, and the mpm_runservice_nt() + * call appended the arguments passed by StartService(), so it's + * time to _prepend_ the default arguments for the server from + * the service's default arguments (all others override them)... + */ + args->nalloc = args->nelts + svc_args->nelts; + cmb_data = malloc(args->nalloc * sizeof(const char *)); + + /* First three args (argv[0], -f, path) remain first */ + memcpy(cmb_data, args->elts, args->elt_size * fixed_args); + + /* Service args follow from service registry array */ + memcpy(cmb_data + fixed_args, svc_args->elts, + svc_args->elt_size * svc_args->nelts); + + /* Remaining new args follow */ + memcpy(cmb_data + fixed_args + svc_args->nelts, + (const char **)args->elts + fixed_args, + args->elt_size * (args->nelts - fixed_args)); + + args->elts = (char *)cmb_data; + args->nelts = args->nalloc; + + return APR_SUCCESS; +} + + +static void service_stopped(void) +{ + /* Still have a thread & window to clean up, so signal now */ + if (globdat.service_thread) + { + /* Stop logging to the event log */ + mpm_nt_eventlog_stderr_flush(); + + /* Cause the service_nt_main_fn to complete */ + ReleaseMutex(globdat.service_term); + + ReportStatusToSCMgr(SERVICE_STOPPED, 0, &globdat); + + WaitForSingleObject(globdat.service_thread, 5000); + CloseHandle(globdat.service_thread); + } +} + + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p) +{ + HANDLE hProc = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + HANDLE waitfor[2]; + + /* Prevent holding open the (hidden) console */ + ap_real_exit_code = 0; + + /* GetCurrentThread returns a psuedo-handle, we need + * a real handle for another thread to wait upon. + */ + if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread), + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return APR_ENOTHREAD; + } + + globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL); + globdat.service_term = CreateMutex(NULL, TRUE, NULL); + if (!globdat.service_init || !globdat.service_term) { + return APR_EGENERAL; + } + + globdat.service_thread = CreateThread(NULL, 65536, + service_nt_dispatch_thread, + NULL, stack_res_flag, + &globdat.service_thread_id); + + if (!globdat.service_thread) { + return APR_ENOTHREAD; + } + + waitfor[0] = globdat.service_init; + waitfor[1] = globdat.service_thread; + + /* Wait for controlling thread init or termination */ + if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) { + return APR_ENOTHREAD; + } + + atexit(service_stopped); + *display_name = mpm_display_name; + return APR_SUCCESS; +} + + +apr_status_t mpm_service_started(void) +{ + set_service_description(); + ReportStatusToSCMgr(SERVICE_RUNNING, 0, &globdat); + return APR_SUCCESS; +} + + +void mpm_service_stopping(void) +{ + ReportStatusToSCMgr(SERVICE_STOP_PENDING, 30000, &globdat); +} + + +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + const char * const * argv, int reconfig) +{ + char key_name[MAX_PATH]; + char *launch_cmd; + ap_regkey_t *key; + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + DWORD rc; +#if APR_HAS_UNICODE_FS + apr_wchar_t *display_name_w; + apr_wchar_t *launch_cmd_w; +#endif + + fprintf(stderr, reconfig ? "Reconfiguring the '%s' service\n" + : "Installing the '%s' service\n", + mpm_display_name); + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + apr_size_t slen = strlen(mpm_display_name) + 1; + apr_size_t wslen = slen; + display_name_w = apr_palloc(ptemp, wslen * sizeof(apr_wchar_t)); + rv = apr_conv_utf8_to_ucs2(mpm_display_name, &slen, + display_name_w, &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + + launch_cmd_w = apr_palloc(ptemp, (MAX_PATH + 17) * sizeof(apr_wchar_t)); + launch_cmd_w[0] = L'"'; + rc = GetModuleFileNameW(NULL, launch_cmd_w + 1, MAX_PATH); + wcscpy(launch_cmd_w + rc + 1, L"\" -k runservice"); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + launch_cmd = apr_palloc(ptemp, MAX_PATH + 17); + launch_cmd[0] = '"'; + rc = GetModuleFileName(NULL, launch_cmd + 1, MAX_PATH); + strcpy(launch_cmd + rc + 1, "\" -k runservice"); + } +#endif + if (rc == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00368) "GetModuleFileName failed"); + return rv; + } + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CREATE_SERVICE); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00369) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return (rv); + } + + if (reconfig) { +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_CHANGE_CONFIG); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + CloseServiceHandle(schSCManager); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00373) "Failed to open the '%s' service", + mpm_display_name); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + rc = ChangeServiceConfigW(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd_w, NULL, NULL, + L"Tcpip\0Afd\0", NULL, NULL, + display_name_w); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + rc = ChangeServiceConfig(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd, NULL, NULL, + "Tcpip\0Afd\0", NULL, NULL, + mpm_display_name); + } +#endif + if (!rc) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(02652) "ChangeServiceConfig failed"); + + /* !schService aborts configuration below */ + CloseServiceHandle(schService); + schService = NULL; + } + } + else { + /* RPCSS is the Remote Procedure Call (RPC) Locator required + * for DCOM communication pipes. I am far from convinced we + * should add this to the default service dependencies, but + * be warned that future apache modules or ISAPI dll's may + * depend on it. + */ +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = CreateServiceW(schSCManager, // SCManager database + mpm_service_name_w, // name of service + display_name_w, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd_w, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + L"Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = CreateService(schSCManager, // SCManager database + mpm_service_name, // name of service + mpm_display_name, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + "Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + } +#endif + if (!schService) + { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00370) "Failed to create the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + set_service_description(); + + /* Store the service ConfigArgs in the registry... + */ + apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00371) "Failed to store ConfigArgs for the " + "'%s' service in the registry.", mpm_display_name); + return (rv); + } + fprintf(stderr, "The '%s' service is successfully installed.\n", + mpm_display_name); + return APR_SUCCESS; +} + + +apr_status_t mpm_service_uninstall(void) +{ + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr, "Removing the '%s' service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10009) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, DELETE); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, DELETE); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10010) "Failed to open the '%s' service", + mpm_display_name); + return (rv); + } + + /* assure the service is stopped before continuing + * + * This may be out of order... we might not be able to be + * granted all access if the service is running anyway. + * + * And do we want to make it *this easy* for them + * to uninstall their service unintentionally? + */ + /* ap_stop_service(schService); + */ + + if (DeleteService(schService) == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(00374) "Failed to delete the '%s' service", + mpm_display_name); + return (rv); + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + fprintf(stderr, "The '%s' service has been removed successfully.\n", + mpm_display_name); + return APR_SUCCESS; +} + + +/* signal_service_transition is a simple thunk to signal the service + * and monitor its successful transition. If the signal passed is 0, + * then the caller is assumed to already have performed some service + * operation to be monitored (such as StartService), and no actual + * ControlService signal is sent. + */ + +static int signal_service_transition(SC_HANDLE schService, DWORD signal, + DWORD pending, DWORD complete) +{ + if (signal && !ControlService(schService, signal, &globdat.ssStatus)) + return FALSE; + + do { + Sleep(1000); + if (!QueryServiceStatus(schService, &globdat.ssStatus)) + return FALSE; + } while (globdat.ssStatus.dwCurrentState == pending); + + return (globdat.ssStatus.dwCurrentState == complete); +} + + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + const char * const * argv) +{ + apr_status_t rv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr, "Starting the '%s' service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10011) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return (rv); + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_START | SERVICE_QUERY_STATUS); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_START | SERVICE_QUERY_STATUS); + } +#endif + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + APLOGNO(10012) "Failed to open the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + + if (QueryServiceStatus(schService, &globdat.ssStatus) + && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, + APLOGNO(00377) "The '%s' service is already started!", + mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 0; + } + + rv = APR_EINIT; +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + LPWSTR *start_argv_w = malloc((argc + 1) * sizeof(LPCWSTR)); + int i; + + for (i = 0; i < argc; ++i) + { + apr_size_t slen = strlen(argv[i]) + 1; + apr_size_t wslen = slen; + start_argv_w[i] = malloc(wslen * sizeof(WCHAR)); + rv = apr_conv_utf8_to_ucs2(argv[i], &slen, start_argv_w[i], &wslen); + if (rv != APR_SUCCESS) + return rv; + else if (slen) + return APR_ENAMETOOLONG; + } + start_argv_w[argc] = NULL; + + if (StartServiceW(schService, argc, start_argv_w) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + char **start_argv = malloc((argc + 1) * sizeof(const char *)); + memcpy(start_argv, argv, argc * sizeof(const char *)); + start_argv[argc] = NULL; + + if (StartService(schService, argc, start_argv) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + } +#endif + if (rv != APR_SUCCESS) + rv = apr_get_os_error(); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + if (rv == APR_SUCCESS) + fprintf(stderr, "The '%s' service is running.\n", mpm_display_name); + else + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00378) + "Failed to start the '%s' service", + mpm_display_name); + + return rv; +} + + +/* signal is zero to stop, non-zero for restart */ + +void mpm_signal_service(apr_pool_t *ptemp, int signal) +{ + int success = FALSE; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager(NULL, NULL, /* default machine & database */ + SC_MANAGER_CONNECT); + + if (!schSCManager) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10013) "Failed to open the Windows service " + "manager, perhaps you forgot to log in as Adminstrator?"); + return; + } + +#if APR_HAS_UNICODE_FS + IF_WIN_OS_IS_UNICODE + { + schService = OpenServiceW(schSCManager, mpm_service_name_w, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + } +#endif /* APR_HAS_UNICODE_FS */ +#if APR_HAS_ANSI_FS + ELSE_WIN_OS_IS_ANSI + { + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + } +#endif + if (schService == NULL) { + /* Could not open the service */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(10014) "Failed to open the '%s' service", + mpm_display_name); + CloseServiceHandle(schSCManager); + return; + } + + if (!QueryServiceStatus(schService, &globdat.ssStatus)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, + apr_get_os_error(), NULL, + APLOGNO(00381) "Query of the '%s' service failed", + mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) { + fprintf(stderr, "The '%s' service is not started.\n", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + fprintf(stderr, signal ? "The '%s' service is restarting.\n" + : "The '%s' service is stopping.\n", + mpm_display_name); + + if (!signal) + success = signal_service_transition(schService, + SERVICE_CONTROL_STOP, + SERVICE_STOP_PENDING, + SERVICE_STOPPED); + else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) { + mpm_service_start(ptemp, 0, NULL); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + else + success = signal_service_transition(schService, + SERVICE_APACHE_RESTART, + SERVICE_START_PENDING, + SERVICE_RUNNING); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + if (success) + fprintf(stderr, signal ? "The '%s' service has restarted.\n" + : "The '%s' service has stopped.\n", + mpm_display_name); + else + fprintf(stderr, signal ? "Failed to restart the '%s' service.\n" + : "Failed to stop the '%s' service.\n", + mpm_display_name); +} |