1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
59 /* This module ALONE requires the window message API from user.h
60 * and the default APR include of windows.h will omit it, so
61 * preload the API symbols now...
69 #include "mpm_winnt.h"
70 #include "apr_strings.h"
79 static char *mpm_service_name = NULL;
80 static char *mpm_display_name = NULL;
84 HANDLE mpm_thread; /* primary thread handle of the apache server */
85 HANDLE service_thread; /* thread service/monitor handle */
86 DWORD service_thread_id;/* thread service/monitor ID */
87 HANDLE service_init; /* controller thread init mutex */
88 HANDLE service_term; /* NT service thread kill signal */
89 SERVICE_STATUS ssStatus;
90 SERVICE_STATUS_HANDLE hServiceStatus;
93 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
95 /* The service configuration's is stored under the following trees:
97 * HKLM\System\CurrentControlSet\Services\[service name]
101 * \Parameters\ConfigArgs
103 * For Win9x, the launch service command is stored under:
105 * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name]
109 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us
110 * to catch the non-zero conditions and inform the console process that
111 * the application died, and hang on to the console a bit longer.
113 * The macro only maps for http_main.c and other sources that include
114 * the service.h header, so we best assume it's an error to exit from
115 * _any_ other module.
117 * If real_exit_code is reset to 0, it will not be set or trigger this
118 * behavior on exit. All service and child processes are expected to
119 * reset this flag to zero to avoid undesireable side effects.
121 int real_exit_code = 1;
123 void hold_console_open_on_error(void)
130 char *msg = "Note the errors or messages above, "
131 "and press the <ESC> key to exit. ";
132 CONSOLE_SCREEN_BUFFER_INFO coninfo;
138 hConIn = GetStdHandle(STD_INPUT_HANDLE);
139 hConErr = GetStdHandle(STD_ERROR_HANDLE);
140 if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
142 if (!WriteConsole(hConErr, msg, strlen(msg), &result, NULL) || !result)
144 if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
146 if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
152 while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
154 if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
156 if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
157 && (in.Event.KeyEvent.uChar.AsciiChar == 27))
159 if (in.EventType == MOUSE_EVENT
160 && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
163 remains = ((start + 30) - time(NULL));
164 sprintf (count, "%d...", remains);
165 if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
167 if (!WriteConsole(hConErr, count, strlen(count), &result, NULL)
171 while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
174 static BOOL die_on_logoff = FALSE;
176 static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg,
177 WPARAM wParam, LPARAM lParam)
179 /* This is the WndProc procedure for our invisible window.
180 * When the user shuts down the system, this window is sent
181 * a signal WM_ENDSESSION. We clean up by signaling Apache
182 * to shut down, and idle until Apache's primary thread quits.
184 if ((msg == WM_ENDSESSION)
185 && (die_on_logoff || (lParam != ENDSESSION_LOGOFF)))
187 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
189 /* Don't leave this message until we are dead! */
190 WaitForSingleObject(globdat.mpm_thread, 30000);
193 return (DefWindowProc(hWnd, msg, wParam, lParam));
196 static DWORD WINAPI monitor_service_9x_thread(void *service_name)
198 /* When running as a service under Windows 9x, there is no console
199 * window present, and no ConsoleCtrlHandler to call when the system
200 * is shutdown. If the WatchWindow thread is created with a NULL
201 * service_name argument, then the ...SystemMonitor window class is
202 * used to create the "Apache" window to watch for logoff and shutdown.
203 * If the service_name is provided, the ...ServiceMonitor window class
204 * is used to create the window named by the service_name argument,
205 * and the logoff message is ignored.
211 wc.style = CS_GLOBALCLASS;
212 wc.lpfnWndProc = monitor_service_9x_proc;
218 wc.hbrBackground = NULL;
219 wc.lpszMenuName = NULL;
221 wc.lpszClassName = "ApacheWin95ServiceMonitor";
223 wc.lpszClassName = "ApacheWin95SystemMonitor";
225 die_on_logoff = service_name ? FALSE : TRUE;
227 if (!RegisterClass(&wc))
229 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
230 NULL, "Could not register window class for WatchWindow");
231 globdat.service_thread_id = 0;
235 /* Create an invisible window */
236 hwndMain = CreateWindow(wc.lpszClassName,
237 service_name ? (char *) service_name : "Apache",
238 WS_OVERLAPPEDWINDOW & ~WS_VISIBLE,
239 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
240 CW_USEDEFAULT, NULL, NULL, NULL, NULL);
244 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
245 NULL, "Could not create WatchWindow");
246 globdat.service_thread_id = 0;
250 /* If we succeed, eliminate the console window.
251 * Signal the parent we are all set up, and
252 * watch the message queue while the window lives.
255 SetEvent(globdat.service_init);
257 while (GetMessage(&msg, NULL, 0, 0))
259 if (msg.message == WM_CLOSE)
260 DestroyWindow(hwndMain);
262 TranslateMessage(&msg);
263 DispatchMessage(&msg);
266 globdat.service_thread_id = 0;
271 static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
275 case CTRL_BREAK_EVENT:
276 fprintf(stderr, "Apache server restarting...\n");
277 ap_signal_parent(SIGNAL_PARENT_RESTART);
280 fprintf(stderr, "Apache server interrupted...\n");
281 /* for Interrupt signals, shut down the server.
282 * Tell the system we have dealt with the signal
283 * without waiting for Apache to terminate.
285 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
288 case CTRL_CLOSE_EVENT:
289 case CTRL_LOGOFF_EVENT:
290 case CTRL_SHUTDOWN_EVENT:
291 /* for Terminate signals, shut down the server.
292 * Wait for Apache to terminate, but respond
293 * after a reasonable time to tell the system
294 * that we did attempt to shut ourself down.
295 * THESE EVENTS WILL NOT OCCUR UNDER WIN9x!
297 fprintf(stderr, "Apache server shutdown initiated...\n");
298 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
303 /* We should never get here, but this is (mostly) harmless */
308 static void stop_console_handler(void)
310 SetConsoleCtrlHandler(console_control_handler, FALSE);
314 void mpm_start_console_handler(void)
316 SetConsoleCtrlHandler(console_control_handler, TRUE);
317 atexit(stop_console_handler);
321 /* Special situation - children of services need to mind their
322 * P's & Q's and wait quietly, ignoring the mean OS signaling
323 * shutdown and other horrors, to kill them gracefully...
326 static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
331 case CTRL_BREAK_EVENT:
332 /* for Interrupt signals, ignore them.
333 * The system will also signal the parent process,
334 * which will terminate Apache.
338 case CTRL_CLOSE_EVENT:
339 case CTRL_LOGOFF_EVENT:
340 case CTRL_SHUTDOWN_EVENT:
341 /* for Shutdown signals, ignore them, but... .
342 * The system will also signal the parent process,
343 * which will terminate Apache, so we need to wait.
349 /* We should never get here, but this is (mostly) harmless */
354 static void stop_child_console_handler(void)
356 SetConsoleCtrlHandler(child_control_handler, FALSE);
360 void mpm_start_child_console_handler(void)
362 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
367 SetConsoleCtrlHandler(child_control_handler, TRUE);
368 atexit(stop_child_console_handler);
373 /**********************************
374 WinNT service control management
375 **********************************/
377 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
379 static int checkPoint = 1;
380 int rv = APR_SUCCESS;
382 if (globdat.hServiceStatus)
384 if (currentState == SERVICE_RUNNING)
385 globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
387 globdat.ssStatus.dwControlsAccepted = 0;
389 globdat.ssStatus.dwCurrentState = currentState;
390 globdat.ssStatus.dwWin32ExitCode = exitCode;
392 if ( ( currentState == SERVICE_RUNNING ) ||
393 ( currentState == SERVICE_STOPPED ) )
395 globdat.ssStatus.dwWaitHint = 0;
396 globdat.ssStatus.dwCheckPoint = 0;
401 globdat.ssStatus.dwWaitHint = waitHint;
402 globdat.ssStatus.dwCheckPoint = ++checkPoint;
404 rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
409 /* Set the service description regardless of platform.
410 * We revert to set_service_description on NT/9x, the
411 * very long way so any Apache management program can grab the
412 * description. This would be bad on Win2000, since it wouldn't
413 * notify the service control manager of the name change.
416 /* Windows 2000 alone supports ChangeServiceConfig2 in order to
417 * register our server_version string... so we need some fixups
418 * to avoid binding to that function if we are on WinNT/9x.
420 static void set_service_description(void)
422 const char *full_description;
423 SC_HANDLE schSCManager;
426 /* Nothing to do if we are a console
428 if (!mpm_service_name)
431 /* Time to fix up the description, upon each successful restart
433 full_description = ap_get_server_version();
435 if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
436 && (osver.dwMajorVersion > 4)
437 && (ChangeServiceConfig2)
438 && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)))
440 SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
443 /* Cast is necessary, ChangeServiceConfig2 handles multiple
444 * object types, some volatile, some not.
446 if (ChangeServiceConfig2(schService,
447 1 /* SERVICE_CONFIG_DESCRIPTION */,
448 (LPVOID) &full_description)) {
449 full_description = NULL;
451 CloseServiceHandle(schService);
453 CloseServiceHandle(schSCManager);
456 if (full_description)
458 char szPath[MAX_PATH];
461 /* Create/Find the Service key that Monitor Applications iterate */
462 apr_snprintf(szPath, sizeof(szPath),
463 "SYSTEM\\CurrentControlSet\\Services\\%s",
465 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szPath, 0, KEY_SET_VALUE, &hkey)
470 /* Attempt to set the Description value for our service */
471 RegSetValueEx(hkey, "Description", 0, REG_SZ,
472 (unsigned char *) full_description,
473 strlen(full_description) + 1);
478 /* handle the SCM's ControlService() callbacks to our service */
480 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
482 if (dwCtrlCode == SERVICE_CONTROL_STOP)
484 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
485 globdat.ssStatus.dwCurrentState = SERVICE_STOP_PENDING;
486 ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 3000);
489 if (dwCtrlCode == SERVICE_APACHE_RESTART)
491 ap_signal_parent(SIGNAL_PARENT_RESTART);
492 globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
493 ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000);
497 ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
501 long __stdcall service_stderr_thread(LPVOID hPipe)
503 HANDLE hPipeRead = (HANDLE) hPipe;
505 char errbuf[256], *errread = errbuf;
506 const char *errarg[9];
510 errarg[0] = "The Apache service named";
511 errarg[1] = mpm_display_name;
512 errarg[2] = "reported the following error:\r\n>>>";
520 /* What are we going to do in here, bail on the user? not. */
521 if (!RegCreateKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services"
522 "\\EventLog\\Application\\Apache Service", &hk))
524 /* The stock message file */
525 char *netmsgkey = "%SystemRoot%\\System32\\netmsg.dll";
526 DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
527 EVENTLOG_INFORMATION_TYPE;
529 RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ,
530 (LPBYTE) netmsgkey, strlen(netmsgkey) + 1);
532 RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD,
533 (LPBYTE) &dwData, sizeof(dwData));
537 hEventSource = RegisterEventSource(NULL, "Apache Service");
539 while (ReadFile(hPipeRead, errread,
540 sizeof(errbuf) - (errread - errbuf) - 1, &errres, NULL))
545 errread[errres] = '\0';
547 /* Process complete lines */
553 /* Trim leading whitespace */
555 while (apr_isspace(*errread)) {
563 /* Find eol, but only re-Read if the buffer is unfilled */
564 erreol = strchr(errread, '\n');
565 if (!erreol && (errread > errbuf)) {
566 errlen = strlen(errbuf);
567 memmove(errbuf, errread, errlen + 1);
568 errread = errbuf + errlen;
573 /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9'
574 * The event code in netmsg.dll is 3299
576 ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0,
577 3299, NULL, 9, 0, errarg, NULL);
584 errread = erreol + 1;
589 if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) {
590 apr_snprintf(errbuf, sizeof(errbuf),
591 "Win32 error %d reading stderr pipe stream\r\n",
594 ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0,
595 3299, NULL, 9, 0, errarg, NULL);
597 CloseHandle(hPipeRead);
602 /* service_nt_main_fn is outside of the call stack and outside of the
603 * primary server thread... so now we _really_ need a placeholder!
604 * The winnt_rewrite_args has created and shared mpm_new_argv with us.
606 extern apr_array_header_t *mpm_new_argv;
608 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
611 HANDLE hPipeRead = NULL;
612 HANDLE hPipeWrite = NULL;
618 /* args and service names live in the same pool */
619 mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
621 globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
622 globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
623 globdat.ssStatus.dwServiceSpecificExitCode = 0;
624 globdat.ssStatus.dwCheckPoint = 1;
626 if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
628 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
629 NULL, "Failure registering service handler");
632 /* Report status, no errors, and buy 3 more seconds */
633 ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 3000);
635 /* Create a pipe to send stderr messages to the system error log
637 if (CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0))
639 HANDLE hProc = GetCurrentProcess();
640 if (DuplicateHandle(hProc, hPipeWrite, hProc, &hDup,
641 0, TRUE, GENERIC_WRITE))
643 CloseHandle(hPipeWrite);
645 thread = (HANDLE) _beginthreadex(NULL, 0, service_stderr_thread,
646 (LPVOID) hPipeRead, 0, &threadid);
654 /* Flush, commit and close stderr. This is typically a noop
655 * in Win2K/XP since services start with NULL std handles,
656 * but is required for NT 4.0 and a decent saftey anyways.
659 _commit(2 /* stderr */);
662 /* The fdopen mode "wcb" is write, binary, so that simple
663 * strings are not buffered for \n -> crlf munging, and
664 * commit-on-write. Used setvbuf to assure no buffering.
666 if (((fd = _open_osfhandle((long) hPipeWrite,
667 _O_WRONLY | _O_BINARY) != -1)
668 && (dup2(fd, 2 /* stderr */) == 0)
669 && ((fl = _fdopen(2 /* stderr */, "wcb")) != NULL))) {
674 setvbuf(stderr, NULL, _IONBF, 0);
677 /* The code above _will_ corrupt the StdHandle...
678 * and we must do so anyways. We set this up only
679 * after we initialized the posix stderr API.
681 SetStdHandle(STD_ERROR_HANDLE, hPipeWrite);
685 CloseHandle(hPipeRead);
686 CloseHandle(hPipeWrite);
692 CloseHandle(hPipeRead);
693 CloseHandle(hPipeWrite);
698 /* We need to append all the command arguments passed via StartService()
699 * to our running service... which just got here via the SCM...
700 * but we hvae no interest in argv[0] for the mpm_new_argv list.
706 mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
707 cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
709 /* mpm_new_argv remains first (of lower significance) */
710 memcpy (cmb_data, mpm_new_argv->elts,
711 mpm_new_argv->elt_size * mpm_new_argv->nelts);
713 /* Service args follow from StartService() invocation */
714 memcpy (cmb_data + mpm_new_argv->nelts, argv + 1,
715 mpm_new_argv->elt_size * (argc - 1));
717 /* The replacement arg list is complete */
718 mpm_new_argv->elts = (char *)cmb_data;
719 mpm_new_argv->nelts = mpm_new_argv->nalloc;
722 /* Let the main thread continue now... but hang on to the
723 * signal_monitor event so we can take further action
725 SetEvent(globdat.service_init);
727 waitfor[0] = globdat.service_term;
728 waitfor[1] = globdat.mpm_thread;
729 WaitForMultipleObjects(2, waitfor, FALSE, INFINITE);
731 /* The process is ready to terminate, or already has */
732 CloseHandle(hPipeWrite);
736 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
738 apr_status_t rv = APR_SUCCESS;
740 SERVICE_TABLE_ENTRY dispatchTable[] =
742 { "", service_nt_main_fn },
746 if (!StartServiceCtrlDispatcher(dispatchTable))
748 /* This is a genuine failure of the SCM. */
749 rv = apr_get_os_error();
750 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
751 "Error starting service control dispatcher");
754 globdat.service_thread_id = 0;
759 apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
760 const char *set_name)
762 char key_name[MAX_PATH];
765 /* ### Needs improvement, on Win2K the user can _easily_
766 * change the display name to a string that doesn't reflect
767 * the internal service name + whitespace!
769 mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
770 apr_collapse_spaces((char*) mpm_service_name, set_name);
771 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
772 rv = ap_registry_get_value(p, key_name, "DisplayName", &mpm_display_name);
773 if (rv != APR_SUCCESS) {
774 /* Take the given literal name if there is no service entry */
775 mpm_display_name = apr_pstrdup(p, set_name);
777 *display_name = mpm_display_name;
782 apr_status_t mpm_merge_service_args(apr_pool_t *p,
783 apr_array_header_t *args,
786 apr_array_header_t *svc_args = NULL;
787 char conf_key[MAX_PATH];
791 apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
792 rv = ap_registry_get_array(p, conf_key, "ConfigArgs", &svc_args);
793 if (rv != APR_SUCCESS) {
794 if (rv == ERROR_FILE_NOT_FOUND) {
795 ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, NULL,
796 "No ConfigArgs registered for %s, perhaps "
797 "this service is not installed?",
805 if (!svc_args || svc_args->nelts == 0) {
806 return (APR_SUCCESS);
809 /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
810 * call appended the arguments passed by StartService(), so it's
811 * time to _prepend_ the default arguments for the server from
812 * the service's default arguments (all others override them)...
814 args->nalloc = args->nelts + svc_args->nelts;
815 cmb_data = malloc(args->nalloc * sizeof(const char *));
817 /* First three args (argv[0], -f, path) remain first */
818 memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
820 /* Service args follow from service registry array */
821 memcpy(cmb_data + fixed_args, svc_args->elts,
822 svc_args->elt_size * svc_args->nelts);
824 /* Remaining new args follow */
825 memcpy(cmb_data + fixed_args + svc_args->nelts,
826 (const char **)args->elts + fixed_args,
827 args->elt_size * (args->nelts - fixed_args));
829 args->elts = (char *)cmb_data;
830 args->nelts = args->nalloc;
836 void service_stopped(void)
838 /* Still have a thread & window to clean up, so signal now */
839 if (globdat.service_thread_id)
841 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
843 ReportStatusToSCMgr(SERVICE_STOPPED, // service state
844 NO_ERROR, // exit code
847 /* Cause the service_nt_main_fn to complete */
848 SetEvent(globdat.service_term);
850 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
852 RegisterServiceProcess(0, 0);
853 PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0);
856 WaitForSingleObject(globdat.service_thread, 5000);
857 CloseHandle(globdat.service_thread);
862 apr_status_t mpm_service_to_start(const char **display_name)
864 HANDLE hProc = GetCurrentProcess();
865 HANDLE hThread = GetCurrentThread();
868 if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
869 0, FALSE, DUPLICATE_SAME_ACCESS)) {
870 return APR_ENOTHREAD;
873 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
875 globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
876 globdat.service_term = CreateEvent(NULL, FALSE, FALSE, NULL);
877 if (globdat.service_init)
878 globdat.service_thread = (HANDLE) _beginthreadex(NULL, 0,
879 service_nt_dispatch_thread,
881 &globdat.service_thread_id);
883 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
885 if (!RegisterServiceProcess(0, 1))
886 return GetLastError();
888 globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
889 if (globdat.service_init)
890 globdat.service_thread = (HANDLE) _beginthreadex(NULL, 0,
891 monitor_service_9x_thread,
892 (LPVOID) mpm_service_name, 0,
893 &globdat.service_thread_id);
896 if (globdat.service_init && globdat.service_thread)
898 waitfor[0] = globdat.service_init;
899 waitfor[1] = globdat.service_thread;
901 /* Wait for controlling thread init or termination */
902 if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
903 CloseHandle(globdat.service_thread);
904 CloseHandle(globdat.mpm_thread);
905 return APR_ENOTHREAD;
909 if (globdat.service_thread_id)
910 atexit(service_stopped);
911 else if (globdat.service_thread)
912 CloseHandle(globdat.service_thread);
914 *display_name = mpm_display_name;
919 apr_status_t mpm_service_started(void)
921 set_service_description();
922 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
924 ReportStatusToSCMgr(SERVICE_RUNNING, // service state
925 NO_ERROR, // exit code
932 void mpm_service_stopping(void)
934 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
935 ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state
936 NO_ERROR, // exit code
941 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
942 const char * const * argv)
944 char key_name[MAX_PATH];
945 char exe_path[MAX_PATH];
949 printf("Installing the %s service\n", mpm_display_name);
951 if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
953 apr_status_t rv = apr_get_os_error();
954 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
955 "GetModuleFileName failed");
959 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
961 SC_HANDLE schService;
962 SC_HANDLE schSCManager;
964 // TODO: Determine the minimum permissions required for security
965 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
966 SC_MANAGER_ALL_ACCESS);
968 rv = apr_get_os_error();
969 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
970 "Failed to open the WinNT service manager");
974 launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
976 /* RPCSS is the Remote Procedure Call (RPC) Locator required for DCOM
977 * communication pipes. I am far from convinced we should add this to
978 * the default service dependencies, but be warned that future apache
979 * modules or ISAPI dll's may depend on it.
981 schService = CreateService(schSCManager, // SCManager database
982 mpm_service_name, // name of service
983 mpm_display_name, // name to display
984 SERVICE_ALL_ACCESS, // access required
985 SERVICE_WIN32_OWN_PROCESS, // service type
986 SERVICE_AUTO_START, // start type
987 SERVICE_ERROR_NORMAL, // error control type
988 launch_cmd, // service's binary
989 NULL, // no load svc group
990 NULL, // no tag identifier
991 "Tcpip\0Afd\0", // dependencies
992 NULL, // use SYSTEM account
993 NULL); // no password
997 rv = apr_get_os_error();
998 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
999 "Failed to create WinNT Service Profile");
1000 CloseServiceHandle(schSCManager);
1004 CloseServiceHandle(schService);
1005 CloseServiceHandle(schSCManager);
1007 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1009 /* Store the launch command in the registry */
1010 launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice",
1011 exe_path, mpm_service_name);
1012 rv = ap_registry_store_value(SERVICECONFIG9X, mpm_service_name, launch_cmd);
1013 if (rv != APR_SUCCESS) {
1014 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1015 "%s: Failed to add the RunServices registry entry.",
1020 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
1021 rv = ap_registry_store_value(key_name, "ImagePath", launch_cmd);
1022 if (rv != APR_SUCCESS) {
1023 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1024 "%s: Failed to store ImagePath in the registry.",
1028 rv = ap_registry_store_value(key_name, "DisplayName", mpm_display_name);
1029 if (rv != APR_SUCCESS) {
1030 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1031 "%s: Failed to store DisplayName in the registry.",
1037 set_service_description();
1039 /* For both WinNT & Win9x store the service ConfigArgs in the registry...
1041 apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
1042 rv = ap_registry_store_array(ptemp, key_name, "ConfigArgs", argc, argv);
1043 if (rv != APR_SUCCESS) {
1044 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1045 "%s: Failed to store the ConfigArgs in the registry.",
1049 printf("The %s service is successfully installed.\n", mpm_display_name);
1054 apr_status_t mpm_service_uninstall(void)
1056 char key_name[MAX_PATH];
1059 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1061 SC_HANDLE schService;
1062 SC_HANDLE schSCManager;
1064 printf("Removing the %s service\n", mpm_display_name);
1066 // TODO: Determine the minimum permissions required for security
1067 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1068 SC_MANAGER_ALL_ACCESS);
1069 if (!schSCManager) {
1070 rv = apr_get_os_error();
1071 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1072 "Failed to open the WinNT service manager.");
1076 schService = OpenService(schSCManager, mpm_service_name, SERVICE_ALL_ACCESS);
1079 rv = apr_get_os_error();
1080 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1081 "%s: OpenService failed", mpm_display_name);
1085 /* assure the service is stopped before continuing
1087 * This may be out of order... we might not be able to be
1088 * granted all access if the service is running anyway.
1090 * And do we want to make it *this easy* for them
1091 * to uninstall their service unintentionally?
1093 // ap_stop_service(schService);
1095 if (DeleteService(schService) == 0) {
1096 rv = apr_get_os_error();
1097 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1098 "%s: Failed to delete the service.", mpm_display_name);
1102 CloseServiceHandle(schService);
1103 CloseServiceHandle(schSCManager);
1105 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1107 printf("Removing the %s service\n", mpm_display_name);
1109 /* TODO: assure the service is stopped before continuing */
1111 if (ap_registry_delete_value(SERVICECONFIG9X, mpm_service_name)) {
1112 rv = apr_get_os_error();
1113 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1114 "%s: Failed to remove the RunServices registry "
1115 "entry.", mpm_display_name);
1119 /* we blast Services/us, not just the Services/us/Parameters branch */
1120 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
1121 if (ap_registry_delete_key(key_name))
1123 rv = apr_get_os_error();
1124 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1125 "%s: Failed to remove the service config from the "
1126 "registry.", mpm_display_name);
1130 printf("The %s service has been removed successfully.\n", mpm_display_name);
1135 /* signal_service_transition is a simple thunk to signal the service
1136 * and monitor its successful transition. If the signal passed is 0,
1137 * then the caller is assumed to already have performed some service
1138 * operation to be monitored (such as StartService), and no actual
1139 * ControlService signal is sent.
1142 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
1144 if (signal && !ControlService(schService, signal, &globdat.ssStatus))
1149 if (!QueryServiceStatus(schService, &globdat.ssStatus))
1151 } while (globdat.ssStatus.dwCurrentState == pending);
1153 return (globdat.ssStatus.dwCurrentState == complete);
1157 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
1158 const char * const * argv)
1162 printf("Starting the %s service\n", mpm_display_name);
1164 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1167 SC_HANDLE schService;
1168 SC_HANDLE schSCManager;
1170 // TODO: Determine the minimum permissions required for security
1171 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1172 SC_MANAGER_ALL_ACCESS);
1173 if (!schSCManager) {
1174 rv = apr_get_os_error();
1175 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1176 "Failed to open the WinNT service manager");
1180 schService = OpenService(schSCManager, mpm_service_name,
1181 SERVICE_START | SERVICE_QUERY_STATUS);
1183 rv = apr_get_os_error();
1184 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1185 "%s: Failed to open the service.", mpm_display_name);
1186 CloseServiceHandle(schSCManager);
1190 if (QueryServiceStatus(schService, &globdat.ssStatus)
1191 && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
1192 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1193 "Service %s is already started!", mpm_display_name);
1194 CloseServiceHandle(schService);
1195 CloseServiceHandle(schSCManager);
1200 start_argv = malloc(argc * sizeof(const char **));
1201 start_argv[0] = mpm_service_name;
1203 memcpy(start_argv + 1, argv, (argc - 1) * sizeof(const char **));
1206 if (StartService(schService, argc, start_argv)
1207 && signal_service_transition(schService, 0, /* test only */
1208 SERVICE_START_PENDING,
1212 if (rv != APR_SUCCESS)
1213 rv = apr_get_os_error();
1215 CloseServiceHandle(schService);
1216 CloseServiceHandle(schSCManager);
1218 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1220 STARTUPINFO si; /* Filled in prior to call to CreateProcess */
1221 PROCESS_INFORMATION pi; /* filled in on call to CreateProcess */
1222 char exe_path[MAX_PATH];
1223 char exe_cmd[MAX_PATH * 4];
1227 /* Locate the active top level window named service_name
1228 * provided the class is ApacheWin95ServiceMonitor
1230 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1231 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1232 "Service %s is already started!", mpm_display_name);
1236 /* This may not appear intuitive, but Win9x will not allow a process
1237 * to detach from the console without releasing the entire console.
1238 * Ergo, we must spawn a new process for the service to get back our
1240 * The config is pre-flighted, so there should be no danger of failure.
1243 if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
1245 apr_status_t rv = apr_get_os_error();
1246 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1247 "GetModuleFileName failed");
1251 apr_snprintf(exe_cmd, sizeof(exe_cmd),
1252 "\"%s\" -n %s -k runservice",
1253 exe_path, mpm_service_name);
1254 next_arg = strchr(exe_cmd, '\0');
1255 for (i = 0; i < argc; ++i) {
1256 apr_snprintf(next_arg, sizeof(exe_cmd) - (next_arg - exe_cmd),
1257 " \"%s\"", argv[i]);
1258 next_arg = strchr(exe_cmd, '\0');
1261 memset(&si, 0, sizeof(si));
1262 memset(&pi, 0, sizeof(pi));
1264 si.dwFlags = STARTF_USESHOWWINDOW;
1265 si.wShowWindow = SW_HIDE; /* This might be redundant */
1268 if (CreateProcess(NULL, exe_cmd, NULL, NULL, FALSE,
1269 DETACHED_PROCESS, /* Creation flags */
1270 NULL, NULL, &si, &pi))
1273 while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) {
1274 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1282 if (rv != APR_SUCCESS)
1283 rv = apr_get_os_error();
1285 CloseHandle(pi.hProcess);
1286 CloseHandle(pi.hThread);
1289 if (rv == APR_SUCCESS)
1290 printf("The %s service is running.\n", mpm_display_name);
1292 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
1293 "%s: Failed to start the service process.",
1300 /* signal is zero to stop, non-zero for restart */
1302 void mpm_signal_service(apr_pool_t *ptemp, int signal)
1304 int success = FALSE;
1306 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1308 SC_HANDLE schService;
1309 SC_HANDLE schSCManager;
1311 schSCManager = OpenSCManager(NULL, NULL, // default machine & database
1312 SC_MANAGER_ALL_ACCESS);
1314 if (!schSCManager) {
1315 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1316 "Failed to open the NT Service Manager");
1320 schService = OpenService(schSCManager, mpm_service_name,
1321 SERVICE_ALL_ACCESS);
1323 if (schService == NULL) {
1324 /* Could not open the service */
1325 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1326 "Failed to open the %s Service", mpm_display_name);
1327 CloseServiceHandle(schSCManager);
1331 if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
1332 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1333 "Query of Service %s failed", mpm_display_name);
1334 CloseServiceHandle(schService);
1335 CloseServiceHandle(schSCManager);
1339 if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
1340 printf("The %s service is not started.\n", mpm_display_name);
1341 CloseServiceHandle(schService);
1342 CloseServiceHandle(schSCManager);
1346 printf("The %s service is %s.\n", mpm_display_name,
1347 signal ? "restarting" : "stopping");
1350 success = signal_service_transition(schService,
1351 SERVICE_CONTROL_STOP,
1352 SERVICE_STOP_PENDING,
1354 else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1355 mpm_service_start(ptemp, 0, NULL);
1356 CloseServiceHandle(schService);
1357 CloseServiceHandle(schSCManager);
1361 success = signal_service_transition(schService,
1362 SERVICE_APACHE_RESTART,
1363 SERVICE_START_PENDING,
1366 CloseServiceHandle(schService);
1367 CloseServiceHandle(schSCManager);
1369 else /* !isWindowsNT() */
1374 /* Locate the active top level window named service_name
1375 * provided the class is ApacheWin95ServiceMonitor
1377 hwnd = FindWindow("ApacheWin95ServiceMonitor", mpm_service_name);
1378 if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
1379 globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
1382 globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
1384 printf("The %s service is not started.\n", mpm_display_name);
1389 printf("The %s service is %s.\n", mpm_display_name,
1390 signal ? "restarting" : "stopping");
1392 apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
1393 setup_signal_names(prefix);
1398 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
1401 if (!IsWindow(hwnd)) {
1410 /* TODO: Aught to add a little test to the restart logic, and
1411 * store the restart counter in the window's user dword.
1412 * Then we can hang on and report a successful restart. But
1413 * that's a project for another day.
1415 if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1416 mpm_service_start(ptemp, 0, NULL);
1421 ap_signal_parent(SIGNAL_PARENT_RESTART);
1427 printf("The %s service has %s.\n", mpm_display_name,
1428 signal ? "restarted" : "stopped");
1430 printf("Failed to %s the %s service.\n",
1431 signal ? "restart" : "stop", mpm_display_name);