1 /* Copyright 1999-2004 The Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 /* This module ALONE requires the window message API from user.h
17 * and the default APR include of windows.h will omit it, so
18 * preload the API symbols now...
26 #include "mpm_winnt.h"
27 #include "apr_strings.h"
29 #include "ap_regkey.h"
37 static char *mpm_service_name = NULL;
38 static char *mpm_display_name = NULL;
42 HANDLE mpm_thread; /* primary thread handle of the apache server */
43 HANDLE service_thread; /* thread service/monitor handle */
44 DWORD service_thread_id;/* thread service/monitor ID */
45 HANDLE service_init; /* controller thread init mutex */
46 HANDLE service_term; /* NT service thread kill signal */
47 SERVICE_STATUS ssStatus;
48 SERVICE_STATUS_HANDLE hServiceStatus;
51 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
54 #define PRODREGKEY "SOFTWARE\\" AP_SERVER_BASEVENDOR "\\" \
55 AP_SERVER_BASEPRODUCT "\\" AP_SERVER_BASEREVISION
58 * Get the server root from the registry into 'dir' which is
59 * size bytes long. Returns 0 if the server root was found
60 * or if the serverroot key does not exist (in which case
61 * dir will contain an empty string), or -1 if there was
62 * an error getting the key.
64 apr_status_t ap_registry_get_server_root(apr_pool_t *p, char **buf)
69 if ((rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, PRODREGKEY,
70 APR_READ, p)) == APR_SUCCESS) {
71 rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
73 if (rv == APR_SUCCESS)
77 if ((rv = ap_regkey_open(&key, AP_REGKEY_CURRENT_USER, PRODREGKEY,
78 APR_READ, p)) == APR_SUCCESS) {
79 rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
81 if (rv == APR_SUCCESS)
90 /* The service configuration's is stored under the following trees:
92 * HKLM\System\CurrentControlSet\Services\[service name]
96 * \Parameters\ConfigArgs
98 * For Win9x, the launch service command is stored under:
100 * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name]
104 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us
105 * to catch the non-zero conditions and inform the console process that
106 * the application died, and hang on to the console a bit longer.
108 * The macro only maps for http_main.c and other sources that include
109 * the service.h header, so we best assume it's an error to exit from
110 * _any_ other module.
112 * If real_exit_code is reset to 0, it will not be set or trigger this
113 * behavior on exit. All service and child processes are expected to
114 * reset this flag to zero to avoid undesireable side effects.
116 AP_DECLARE_DATA int real_exit_code = 1;
118 void hold_console_open_on_error(void)
125 char *msg = "Note the errors or messages above, "
126 "and press the <ESC> key to exit. ";
127 CONSOLE_SCREEN_BUFFER_INFO coninfo;
133 hConIn = GetStdHandle(STD_INPUT_HANDLE);
134 hConErr = GetStdHandle(STD_ERROR_HANDLE);
135 if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
137 if (!WriteConsole(hConErr, msg, (int)strlen(msg), &result, NULL) || !result)
139 if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
141 if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
147 while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
149 if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
151 if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
152 && (in.Event.KeyEvent.uChar.AsciiChar == 27))
154 if (in.EventType == MOUSE_EVENT
155 && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
158 remains = ((start + 30) - time(NULL));
159 sprintf (count, "%d...", remains);
160 if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
162 if (!WriteConsole(hConErr, count, (int)strlen(count), &result, NULL)
166 while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
169 static BOOL die_on_logoff = FALSE;
171 static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg,
172 WPARAM wParam, LPARAM lParam)
174 /* This is the WndProc procedure for our invisible window.
175 * When the user shuts down the system, this window is sent
176 * a signal WM_ENDSESSION. We clean up by signaling Apache
177 * to shut down, and idle until Apache's primary thread quits.
179 if ((msg == WM_ENDSESSION)
180 && (die_on_logoff || (lParam != ENDSESSION_LOGOFF)))
182 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
184 /* Don't leave this message until we are dead! */
185 WaitForSingleObject(globdat.mpm_thread, 30000);
188 return (DefWindowProc(hWnd, msg, wParam, lParam));
191 static DWORD WINAPI monitor_service_9x_thread(void *service_name)
193 /* When running as a service under Windows 9x, there is no console
194 * window present, and no ConsoleCtrlHandler to call when the system
195 * is shutdown. If the WatchWindow thread is created with a NULL
196 * service_name argument, then the ...SystemMonitor window class is
197 * used to create the "Apache" window to watch for logoff and shutdown.
198 * If the service_name is provided, the ...ServiceMonitor window class
199 * is used to create the window named by the service_name argument,
200 * and the logoff message is ignored.
206 wc.style = CS_GLOBALCLASS;
207 wc.lpfnWndProc = monitor_service_9x_proc;
213 wc.hbrBackground = NULL;
214 wc.lpszMenuName = NULL;
216 wc.lpszClassName = "ApacheWin95ServiceMonitor";
218 wc.lpszClassName = "ApacheWin95SystemMonitor";
220 die_on_logoff = service_name ? FALSE : TRUE;
222 if (!RegisterClass(&wc))
224 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
225 NULL, "Could not register window class for WatchWindow");
226 globdat.service_thread_id = 0;
230 /* Create an invisible window */
231 hwndMain = CreateWindow(wc.lpszClassName,
232 service_name ? (char *) service_name : "Apache",
233 WS_OVERLAPPEDWINDOW & ~WS_VISIBLE,
234 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
235 CW_USEDEFAULT, NULL, NULL, NULL, NULL);
239 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
240 NULL, "Could not create WatchWindow");
241 globdat.service_thread_id = 0;
245 /* If we succeed, eliminate the console window.
246 * Signal the parent we are all set up, and
247 * watch the message queue while the window lives.
250 SetEvent(globdat.service_init);
252 while (GetMessage(&msg, NULL, 0, 0))
254 if (msg.message == WM_CLOSE)
255 DestroyWindow(hwndMain);
257 TranslateMessage(&msg);
258 DispatchMessage(&msg);
261 globdat.service_thread_id = 0;
266 static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
270 case CTRL_BREAK_EVENT:
271 fprintf(stderr, "Apache server restarting...\n");
272 ap_signal_parent(SIGNAL_PARENT_RESTART);
275 fprintf(stderr, "Apache server interrupted...\n");
276 /* for Interrupt signals, shut down the server.
277 * Tell the system we have dealt with the signal
278 * without waiting for Apache to terminate.
280 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
283 case CTRL_CLOSE_EVENT:
284 case CTRL_LOGOFF_EVENT:
285 case CTRL_SHUTDOWN_EVENT:
286 /* for Terminate signals, shut down the server.
287 * Wait for Apache to terminate, but respond
288 * after a reasonable time to tell the system
289 * that we did attempt to shut ourself down.
290 * THESE EVENTS WILL NOT OCCUR UNDER WIN9x!
292 fprintf(stderr, "Apache server shutdown initiated...\n");
293 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
298 /* We should never get here, but this is (mostly) harmless */
303 static void stop_console_handler(void)
305 SetConsoleCtrlHandler(console_control_handler, FALSE);
309 void mpm_start_console_handler(void)
311 SetConsoleCtrlHandler(console_control_handler, TRUE);
312 atexit(stop_console_handler);
316 /* Special situation - children of services need to mind their
317 * P's & Q's and wait quietly, ignoring the mean OS signaling
318 * shutdown and other horrors, to kill them gracefully...
321 static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
326 case CTRL_BREAK_EVENT:
327 /* for Interrupt signals, ignore them.
328 * The system will also signal the parent process,
329 * which will terminate Apache.
333 case CTRL_CLOSE_EVENT:
334 case CTRL_LOGOFF_EVENT:
335 case CTRL_SHUTDOWN_EVENT:
336 /* for Shutdown signals, ignore them, but... .
337 * The system will also signal the parent process,
338 * which will terminate Apache, so we need to wait.
344 /* We should never get here, but this is (mostly) harmless */
349 static void stop_child_console_handler(void)
351 SetConsoleCtrlHandler(child_control_handler, FALSE);
355 void mpm_start_child_console_handler(void)
357 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
362 SetConsoleCtrlHandler(child_control_handler, TRUE);
363 atexit(stop_child_console_handler);
368 /**********************************
369 WinNT service control management
370 **********************************/
372 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
374 static int checkPoint = 1;
375 int rv = APR_SUCCESS;
377 if (globdat.hServiceStatus)
379 if (currentState == SERVICE_RUNNING) {
380 globdat.ssStatus.dwWaitHint = 0;
381 globdat.ssStatus.dwCheckPoint = 0;
382 globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
384 else if (currentState == SERVICE_STOPPED) {
385 globdat.ssStatus.dwWaitHint = 0;
386 globdat.ssStatus.dwCheckPoint = 0;
387 if (!exitCode && globdat.ssStatus.dwCurrentState
388 != SERVICE_STOP_PENDING) {
389 /* An unexpected exit? Better to error! */
393 globdat.ssStatus.dwWin32ExitCode =ERROR_SERVICE_SPECIFIC_ERROR;
394 globdat.ssStatus.dwServiceSpecificExitCode = exitCode;
398 globdat.ssStatus.dwCheckPoint = ++checkPoint;
399 globdat.ssStatus.dwControlsAccepted = 0;
401 globdat.ssStatus.dwWaitHint = waitHint;
404 globdat.ssStatus.dwCurrentState = currentState;
406 rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
411 /* Set the service description regardless of platform.
412 * We revert to set_service_description on NT/9x, the
413 * very long way so any Apache management program can grab the
414 * description. This would be bad on Win2000, since it wouldn't
415 * notify the service control manager of the name change.
418 /* borrowed from mpm_winnt.c */
419 extern apr_pool_t *pconf;
421 /* Windows 2000 alone supports ChangeServiceConfig2 in order to
422 * register our server_version string... so we need some fixups
423 * to avoid binding to that function if we are on WinNT/9x.
425 static void set_service_description(void)
427 const char *full_description;
428 SC_HANDLE schSCManager;
431 /* Nothing to do if we are a console
433 if (!mpm_service_name)
436 /* Time to fix up the description, upon each successful restart
438 full_description = ap_get_server_version();
440 if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
441 && (osver.dwMajorVersion > 4)
442 && (ChangeServiceConfig2)
443 && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)))
445 SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
446 SERVICE_CHANGE_CONFIG);
448 /* Cast is necessary, ChangeServiceConfig2 handles multiple
449 * object types, some volatile, some not.
452 if (ChangeServiceConfig2(schService,
453 1 /* SERVICE_CONFIG_DESCRIPTION */,
454 (LPVOID) &full_description)) {
455 full_description = NULL;
457 CloseServiceHandle(schService);
459 CloseServiceHandle(schSCManager);
462 if (full_description)
464 char szPath[MAX_PATH];
468 /* Find the Service key that Monitor Applications iterate */
469 apr_snprintf(szPath, sizeof(szPath),
470 "SYSTEM\\CurrentControlSet\\Services\\%s",
472 rv = ap_regkey_open(&svckey, AP_REGKEY_LOCAL_MACHINE, szPath,
473 APR_READ | APR_WRITE, pconf);
474 if (rv != APR_SUCCESS) {
477 /* Attempt to set the Description value for our service */
478 ap_regkey_value_set(svckey, "Description", full_description, 0, pconf);
479 ap_regkey_close(svckey);
483 /* handle the SCM's ControlService() callbacks to our service */
485 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
487 if (dwCtrlCode == SERVICE_CONTROL_STOP)
489 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
490 ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
493 if (dwCtrlCode == SERVICE_APACHE_RESTART)
495 ap_signal_parent(SIGNAL_PARENT_RESTART);
496 ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
500 ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
504 /* service_nt_main_fn is outside of the call stack and outside of the
505 * primary server thread... so now we _really_ need a placeholder!
506 * The winnt_rewrite_args has created and shared mpm_new_argv with us.
508 extern apr_array_header_t *mpm_new_argv;
511 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
515 /* args and service names live in the same pool */
516 mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
518 memset(&globdat.ssStatus, 0, sizeof(globdat.ssStatus));
519 globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
520 globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
521 globdat.ssStatus.dwCheckPoint = 1;
524 if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
526 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
527 NULL, "Failure registering service handler");
531 /* Report status, no errors, and buy 3 more seconds */
532 ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
534 /* We need to append all the command arguments passed via StartService()
535 * to our running service... which just got here via the SCM...
536 * but we hvae no interest in argv[0] for the mpm_new_argv list.
542 mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
543 cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
545 /* mpm_new_argv remains first (of lower significance) */
546 memcpy (cmb_data, mpm_new_argv->elts,
547 mpm_new_argv->elt_size * mpm_new_argv->nelts);
549 /* Service args follow from StartService() invocation */
550 memcpy (cmb_data + mpm_new_argv->nelts, argv + 1,
551 mpm_new_argv->elt_size * (argc - 1));
553 /* The replacement arg list is complete */
554 mpm_new_argv->elts = (char *)cmb_data;
555 mpm_new_argv->nelts = mpm_new_argv->nalloc;
558 /* Let the main thread continue now... but hang on to the
559 * signal_monitor event so we can take further action
561 SetEvent(globdat.service_init);
563 WaitForSingleObject(globdat.service_term, INFINITE);
567 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
569 apr_status_t rv = APR_SUCCESS;
571 SERVICE_TABLE_ENTRY dispatchTable[] =
573 { "", service_nt_main_fn },
578 if (!StartServiceCtrlDispatcher(dispatchTable))
580 /* This is a genuine failure of the SCM. */
581 rv = apr_get_os_error();
582 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
583 "Error starting service control dispatcher");
590 apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
591 const char *set_name)
593 char key_name[MAX_PATH];
597 /* ### Needs improvement, on Win2K the user can _easily_
598 * change the display name to a string that doesn't reflect
599 * the internal service name + whitespace!
601 mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
602 apr_collapse_spaces((char*) mpm_service_name, set_name);
603 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
604 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, APR_READ, pconf);
605 if (rv == APR_SUCCESS) {
606 rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf);
607 ap_regkey_close(key);
609 if (rv != APR_SUCCESS) {
610 /* Take the given literal name if there is no service entry */
611 mpm_display_name = apr_pstrdup(p, set_name);
613 *display_name = mpm_display_name;
618 apr_status_t mpm_merge_service_args(apr_pool_t *p,
619 apr_array_header_t *args,
622 apr_array_header_t *svc_args = NULL;
623 char conf_key[MAX_PATH];
628 apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
629 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p);
630 if (rv == APR_SUCCESS) {
631 rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p);
632 ap_regkey_close(key);
634 if (rv != APR_SUCCESS) {
635 if (rv == ERROR_FILE_NOT_FOUND) {
636 ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
637 "No ConfigArgs registered for %s, perhaps "
638 "this service is not installed?",
646 if (!svc_args || svc_args->nelts == 0) {
647 return (APR_SUCCESS);
650 /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
651 * call appended the arguments passed by StartService(), so it's
652 * time to _prepend_ the default arguments for the server from
653 * the service's default arguments (all others override them)...
655 args->nalloc = args->nelts + svc_args->nelts;
656 cmb_data = malloc(args->nalloc * sizeof(const char *));
658 /* First three args (argv[0], -f, path) remain first */
659 memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
661 /* Service args follow from service registry array */
662 memcpy(cmb_data + fixed_args, svc_args->elts,
663 svc_args->elt_size * svc_args->nelts);
665 /* Remaining new args follow */
666 memcpy(cmb_data + fixed_args + svc_args->nelts,
667 (const char **)args->elts + fixed_args,
668 args->elt_size * (args->nelts - fixed_args));
670 args->elts = (char *)cmb_data;
671 args->nelts = args->nalloc;
677 void service_stopped(void)
679 /* Still have a thread & window to clean up, so signal now */
680 if (globdat.service_thread)
682 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
684 /* Stop logging to the event log */
685 mpm_nt_eventlog_stderr_flush();
687 /* Cause the service_nt_main_fn to complete */
688 ReleaseMutex(globdat.service_term);
690 ReportStatusToSCMgr(SERVICE_STOPPED, // service state
691 NO_ERROR, // exit code
694 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
696 RegisterServiceProcess(0, 0);
697 PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0);
700 WaitForSingleObject(globdat.service_thread, 5000);
701 CloseHandle(globdat.service_thread);
706 apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p)
708 HANDLE hProc = GetCurrentProcess();
709 HANDLE hThread = GetCurrentThread();
712 /* Prevent holding open the (hidden) console */
715 /* GetCurrentThread returns a psuedo-handle, we need
716 * a real handle for another thread to wait upon.
718 if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
719 0, FALSE, DUPLICATE_SAME_ACCESS)) {
720 return APR_ENOTHREAD;
723 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
725 globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
726 globdat.service_term = CreateMutex(NULL, TRUE, NULL);
727 if (!globdat.service_init || !globdat.service_term) {
731 globdat.service_thread = CreateThread(NULL, 0, service_nt_dispatch_thread,
732 NULL, 0, &globdat.service_thread_id);
734 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
736 if (!RegisterServiceProcess(0, 1))
737 return GetLastError();
739 globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
740 if (!globdat.service_init) {
744 globdat.service_thread = CreateThread(NULL, 0, monitor_service_9x_thread,
745 (LPVOID) mpm_service_name, 0,
746 &globdat.service_thread_id);
749 if (!globdat.service_thread) {
750 return APR_ENOTHREAD;
753 waitfor[0] = globdat.service_init;
754 waitfor[1] = globdat.service_thread;
756 /* Wait for controlling thread init or termination */
757 if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
758 return APR_ENOTHREAD;
761 atexit(service_stopped);
762 *display_name = mpm_display_name;
767 apr_status_t mpm_service_started(void)
769 set_service_description();
770 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
772 ReportStatusToSCMgr(SERVICE_RUNNING, // service state
773 NO_ERROR, // exit code
780 void mpm_service_stopping(void)
782 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
783 ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state
784 NO_ERROR, // exit code
789 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
790 const char * const * argv, int reconfig)
792 char key_name[MAX_PATH];
793 char exe_path[MAX_PATH];
798 fprintf(stderr,reconfig ? "Reconfiguring the %s service\n"
799 : "Installing the %s service\n", mpm_display_name);
802 if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
804 apr_status_t rv = apr_get_os_error();
805 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
806 "GetModuleFileName failed");
810 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
812 SC_HANDLE schService;
813 SC_HANDLE schSCManager;
815 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
816 SC_MANAGER_CREATE_SERVICE);
818 rv = apr_get_os_error();
819 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
820 "Failed to open the WinNT service manager");
824 launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
828 schService = OpenService(schSCManager, mpm_service_name,
829 SERVICE_CHANGE_CONFIG);
831 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
832 apr_get_os_error(), NULL,
833 "OpenService failed");
836 else if (!ChangeServiceConfig(schService,
837 SERVICE_WIN32_OWN_PROCESS,
839 SERVICE_ERROR_NORMAL,
840 launch_cmd, NULL, NULL,
841 "Tcpip\0Afd\0", NULL, NULL,
843 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
844 apr_get_os_error(), NULL,
845 "ChangeServiceConfig failed");
846 /* !schService aborts configuration below */
847 CloseServiceHandle(schService);
852 /* RPCSS is the Remote Procedure Call (RPC) Locator required
853 * for DCOM communication pipes. I am far from convinced we
854 * should add this to the default service dependencies, but
855 * be warned that future apache modules or ISAPI dll's may
859 schService = CreateService(schSCManager, // SCManager database
860 mpm_service_name, // name of service
861 mpm_display_name, // name to display
862 SERVICE_ALL_ACCESS, // access required
863 SERVICE_WIN32_OWN_PROCESS, // service type
864 SERVICE_AUTO_START, // start type
865 SERVICE_ERROR_NORMAL, // error control type
866 launch_cmd, // service's binary
867 NULL, // no load svc group
868 NULL, // no tag identifier
869 "Tcpip\0Afd\0", // dependencies
870 NULL, // use SYSTEM account
871 NULL); // no password
875 rv = apr_get_os_error();
876 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
877 "Failed to create WinNT Service Profile");
878 CloseServiceHandle(schSCManager);
883 CloseServiceHandle(schService);
884 CloseServiceHandle(schSCManager);
886 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
888 /* Store the launch command in the registry */
889 launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice",
890 exe_path, mpm_service_name);
891 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X,
892 APR_READ | APR_WRITE | APR_CREATE, pconf);
893 if (rv == APR_SUCCESS) {
894 rv = ap_regkey_value_set(key, mpm_service_name,
895 launch_cmd, 0, pconf);
896 ap_regkey_close(key);
898 if (rv != APR_SUCCESS) {
899 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
900 "%s: Failed to add the RunServices registry entry.",
905 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
906 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
907 APR_READ | APR_WRITE | APR_CREATE, pconf);
908 if (rv != APR_SUCCESS) {
909 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
910 "%s: Failed to create the registry service key.",
914 rv = ap_regkey_value_set(key, "ImagePath", launch_cmd, 0, pconf);
915 if (rv != APR_SUCCESS) {
916 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
917 "%s: Failed to store ImagePath in the registry.",
919 ap_regkey_close(key);
922 rv = ap_regkey_value_set(key, "DisplayName",
923 mpm_display_name, 0, pconf);
924 ap_regkey_close(key);
925 if (rv != APR_SUCCESS) {
926 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
927 "%s: Failed to store DisplayName in the registry.",
933 set_service_description();
935 /* For both WinNT & Win9x store the service ConfigArgs in the registry...
937 apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
938 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
939 APR_READ | APR_WRITE | APR_CREATE, pconf);
940 if (rv == APR_SUCCESS) {
941 rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf);
942 ap_regkey_close(key);
944 if (rv != APR_SUCCESS) {
945 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
946 "%s: Failed to store the ConfigArgs in the registry.",
950 fprintf(stderr,"The %s service is successfully installed.\n", mpm_display_name);
955 apr_status_t mpm_service_uninstall(void)
957 char key_name[MAX_PATH];
960 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
962 SC_HANDLE schService;
963 SC_HANDLE schSCManager;
965 fprintf(stderr,"Removing the %s service\n", mpm_display_name);
967 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
970 rv = apr_get_os_error();
971 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
972 "Failed to open the WinNT service manager.");
977 schService = OpenService(schSCManager, mpm_service_name, DELETE);
980 rv = apr_get_os_error();
981 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
982 "%s: OpenService failed", mpm_display_name);
986 /* assure the service is stopped before continuing
988 * This may be out of order... we might not be able to be
989 * granted all access if the service is running anyway.
991 * And do we want to make it *this easy* for them
992 * to uninstall their service unintentionally?
994 // ap_stop_service(schService);
996 if (DeleteService(schService) == 0) {
997 rv = apr_get_os_error();
998 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
999 "%s: Failed to delete the service.", mpm_display_name);
1003 CloseServiceHandle(schService);
1004 CloseServiceHandle(schSCManager);
1006 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1008 apr_status_t rv2, rv3;
1010 fprintf(stderr,"Removing the %s service\n", mpm_display_name);
1012 /* TODO: assure the service is stopped before continuing */
1014 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X,
1015 APR_READ | APR_WRITE | APR_CREATE, pconf);
1016 if (rv == APR_SUCCESS) {
1017 rv = ap_regkey_value_remove(key, mpm_service_name, pconf);
1018 ap_regkey_close(key);
1020 if (rv != APR_SUCCESS) {
1021 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1022 "%s: Failed to remove the RunServices registry "
1023 "entry.", mpm_display_name);
1026 /* we blast Services/us, not just the Services/us/Parameters branch */
1027 apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
1028 rv2 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf);
1029 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
1030 rv3 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf);
1031 rv2 = (rv2 != APR_SUCCESS) ? rv2 : rv3;
1032 if (rv2 != APR_SUCCESS) {
1033 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv2, NULL,
1034 "%s: Failed to remove the service config from the "
1035 "registry.", mpm_display_name);
1037 rv = (rv != APR_SUCCESS) ? rv : rv2;
1038 if (rv != APR_SUCCESS)
1041 fprintf(stderr,"The %s service has been removed successfully.\n", mpm_display_name);
1046 /* signal_service_transition is a simple thunk to signal the service
1047 * and monitor its successful transition. If the signal passed is 0,
1048 * then the caller is assumed to already have performed some service
1049 * operation to be monitored (such as StartService), and no actual
1050 * ControlService signal is sent.
1053 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
1055 if (signal && !ControlService(schService, signal, &globdat.ssStatus))
1060 if (!QueryServiceStatus(schService, &globdat.ssStatus))
1062 } while (globdat.ssStatus.dwCurrentState == pending);
1064 return (globdat.ssStatus.dwCurrentState == complete);
1068 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
1069 const char * const * argv)
1073 fprintf(stderr,"Starting the %s service\n", mpm_display_name);
1075 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1078 SC_HANDLE schService;
1079 SC_HANDLE schSCManager;
1081 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1082 SC_MANAGER_CONNECT);
1083 if (!schSCManager) {
1084 rv = apr_get_os_error();
1085 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1086 "Failed to open the WinNT service manager");
1091 schService = OpenService(schSCManager, mpm_service_name,
1092 SERVICE_START | SERVICE_QUERY_STATUS);
1094 rv = apr_get_os_error();
1095 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1096 "%s: Failed to open the service.", mpm_display_name);
1097 CloseServiceHandle(schSCManager);
1101 if (QueryServiceStatus(schService, &globdat.ssStatus)
1102 && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
1103 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1104 "Service %s is already started!", mpm_display_name);
1105 CloseServiceHandle(schService);
1106 CloseServiceHandle(schSCManager);
1110 start_argv = malloc((argc + 1) * sizeof(const char **));
1111 memcpy(start_argv, argv, argc * sizeof(const char **));
1112 start_argv[argc] = NULL;
1116 if (StartService(schService, argc, start_argv)
1117 && signal_service_transition(schService, 0, /* test only */
1118 SERVICE_START_PENDING,
1122 if (rv != APR_SUCCESS)
1123 rv = apr_get_os_error();
1125 CloseServiceHandle(schService);
1126 CloseServiceHandle(schSCManager);
1128 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1130 STARTUPINFO si; /* Filled in prior to call to CreateProcess */
1131 PROCESS_INFORMATION pi; /* filled in on call to CreateProcess */
1132 char exe_path[MAX_PATH];
1133 char exe_cmd[MAX_PATH * 4];
1137 /* Locate the active top level window named service_name
1138 * provided the class is ApacheWin95ServiceMonitor
1140 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1141 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1142 "Service %s is already started!", mpm_display_name);
1146 /* This may not appear intuitive, but Win9x will not allow a process
1147 * to detach from the console without releasing the entire console.
1148 * Ergo, we must spawn a new process for the service to get back our
1150 * The config is pre-flighted, so there should be no danger of failure.
1153 if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
1155 apr_status_t rv = apr_get_os_error();
1156 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1157 "GetModuleFileName failed");
1161 apr_snprintf(exe_cmd, sizeof(exe_cmd),
1162 "\"%s\" -n %s -k runservice",
1163 exe_path, mpm_service_name);
1164 next_arg = strchr(exe_cmd, '\0');
1165 for (i = 0; i < argc; ++i) {
1166 apr_snprintf(next_arg, sizeof(exe_cmd) - (next_arg - exe_cmd),
1167 " \"%s\"", argv[i]);
1168 next_arg = strchr(exe_cmd, '\0');
1171 memset(&si, 0, sizeof(si));
1172 memset(&pi, 0, sizeof(pi));
1174 si.dwFlags = STARTF_USESHOWWINDOW;
1175 si.wShowWindow = SW_HIDE; /* This might be redundant */
1178 if (CreateProcess(NULL, exe_cmd, NULL, NULL, FALSE,
1179 DETACHED_PROCESS, /* Creation flags */
1180 NULL, NULL, &si, &pi))
1183 while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) {
1184 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1192 if (rv != APR_SUCCESS)
1193 rv = apr_get_os_error();
1195 CloseHandle(pi.hProcess);
1196 CloseHandle(pi.hThread);
1199 if (rv == APR_SUCCESS)
1200 fprintf(stderr,"The %s service is running.\n", mpm_display_name);
1202 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
1203 "%s: Failed to start the service process.",
1210 /* signal is zero to stop, non-zero for restart */
1212 void mpm_signal_service(apr_pool_t *ptemp, int signal)
1214 int success = FALSE;
1216 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1218 SC_HANDLE schService;
1219 SC_HANDLE schSCManager;
1221 schSCManager = OpenSCManager(NULL, NULL, // default machine & database
1222 SC_MANAGER_CONNECT);
1224 if (!schSCManager) {
1225 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1226 "Failed to open the NT Service Manager");
1231 schService = OpenService(schSCManager, mpm_service_name,
1232 SERVICE_INTERROGATE | SERVICE_QUERY_STATUS |
1233 SERVICE_USER_DEFINED_CONTROL |
1234 SERVICE_START | SERVICE_STOP);
1236 if (schService == NULL) {
1237 /* Could not open the service */
1238 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1239 "Failed to open the %s Service", mpm_display_name);
1240 CloseServiceHandle(schSCManager);
1244 if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
1245 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1246 "Query of Service %s failed", mpm_display_name);
1247 CloseServiceHandle(schService);
1248 CloseServiceHandle(schSCManager);
1252 if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
1253 fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
1254 CloseServiceHandle(schService);
1255 CloseServiceHandle(schSCManager);
1259 fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
1260 signal ? "restarting" : "stopping");
1263 success = signal_service_transition(schService,
1264 SERVICE_CONTROL_STOP,
1265 SERVICE_STOP_PENDING,
1267 else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1268 mpm_service_start(ptemp, 0, NULL);
1269 CloseServiceHandle(schService);
1270 CloseServiceHandle(schSCManager);
1274 success = signal_service_transition(schService,
1275 SERVICE_APACHE_RESTART,
1276 SERVICE_START_PENDING,
1279 CloseServiceHandle(schService);
1280 CloseServiceHandle(schSCManager);
1282 else /* !isWindowsNT() */
1287 /* Locate the active top level window named service_name
1288 * provided the class is ApacheWin95ServiceMonitor
1290 hwnd = FindWindow("ApacheWin95ServiceMonitor", mpm_service_name);
1291 if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
1292 globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
1295 globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
1297 fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
1302 fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
1303 signal ? "restarting" : "stopping");
1305 apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
1306 setup_signal_names(prefix);
1311 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
1314 if (!IsWindow(hwnd)) {
1323 /* TODO: Aught to add a little test to the restart logic, and
1324 * store the restart counter in the window's user dword.
1325 * Then we can hang on and report a successful restart. But
1326 * that's a project for another day.
1328 if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1329 mpm_service_start(ptemp, 0, NULL);
1334 ap_signal_parent(SIGNAL_PARENT_RESTART);
1340 fprintf(stderr,"The %s service has %s.\n", mpm_display_name,
1341 signal ? "restarted" : "stopped");
1343 fprintf(stderr,"Failed to %s the %s service.\n",
1344 signal ? "restart" : "stop", mpm_display_name);