1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* This module ALONE requires the window message API from user.h
18 * and the default APR include of windows.h will omit it, so
19 * preload the API symbols now...
27 #include "mpm_winnt.h"
28 #include "apr_strings.h"
30 #include "ap_regkey.h"
38 static char *mpm_service_name = NULL;
39 static char *mpm_display_name = NULL;
43 HANDLE mpm_thread; /* primary thread handle of the apache server */
44 HANDLE service_thread; /* thread service/monitor handle */
45 DWORD service_thread_id;/* thread service/monitor ID */
46 HANDLE service_init; /* controller thread init mutex */
47 HANDLE service_term; /* NT service thread kill signal */
48 SERVICE_STATUS ssStatus;
49 SERVICE_STATUS_HANDLE hServiceStatus;
52 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
55 #define PRODREGKEY "SOFTWARE\\" AP_SERVER_BASEVENDOR "\\" \
56 AP_SERVER_BASEPRODUCT "\\" AP_SERVER_BASEREVISION
59 * Get the server root from the registry into 'dir' which is
60 * size bytes long. Returns 0 if the server root was found
61 * or if the serverroot key does not exist (in which case
62 * dir will contain an empty string), or -1 if there was
63 * an error getting the key.
65 apr_status_t ap_registry_get_server_root(apr_pool_t *p, char **buf)
70 if ((rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, PRODREGKEY,
71 APR_READ, p)) == APR_SUCCESS) {
72 rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
74 if (rv == APR_SUCCESS)
78 if ((rv = ap_regkey_open(&key, AP_REGKEY_CURRENT_USER, PRODREGKEY,
79 APR_READ, p)) == APR_SUCCESS) {
80 rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
82 if (rv == APR_SUCCESS)
91 /* The service configuration's is stored under the following trees:
93 * HKLM\System\CurrentControlSet\Services\[service name]
97 * \Parameters\ConfigArgs
99 * For Win9x, the launch service command is stored under:
101 * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name]
105 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us
106 * to catch the non-zero conditions and inform the console process that
107 * the application died, and hang on to the console a bit longer.
109 * The macro only maps for http_main.c and other sources that include
110 * the service.h header, so we best assume it's an error to exit from
111 * _any_ other module.
113 * If ap_real_exit_code is reset to 0, it will not be set or trigger this
114 * behavior on exit. All service and child processes are expected to
115 * reset this flag to zero to avoid undesireable side effects.
117 AP_DECLARE_DATA int ap_real_exit_code = 1;
119 void hold_console_open_on_error(void)
126 char *msg = "Note the errors or messages above, "
127 "and press the <ESC> key to exit. ";
128 CONSOLE_SCREEN_BUFFER_INFO coninfo;
132 if (!ap_real_exit_code)
134 hConIn = GetStdHandle(STD_INPUT_HANDLE);
135 hConErr = GetStdHandle(STD_ERROR_HANDLE);
136 if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
138 if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) || !result)
140 if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
142 if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
148 while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
150 if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
152 if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
153 && (in.Event.KeyEvent.uChar.AsciiChar == 27))
155 if (in.EventType == MOUSE_EVENT
156 && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
159 remains = ((start + 30) - time(NULL));
160 sprintf (count, "%d...", remains);
161 if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
163 if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL)
167 while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
170 static BOOL die_on_logoff = FALSE;
172 static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg,
173 WPARAM wParam, LPARAM lParam)
175 /* This is the WndProc procedure for our invisible window.
176 * When the user shuts down the system, this window is sent
177 * a signal WM_ENDSESSION. We clean up by signaling Apache
178 * to shut down, and idle until Apache's primary thread quits.
180 if ((msg == WM_ENDSESSION)
181 && (die_on_logoff || (lParam != ENDSESSION_LOGOFF)))
183 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
185 /* Don't leave this message until we are dead! */
186 WaitForSingleObject(globdat.mpm_thread, 30000);
189 return (DefWindowProc(hWnd, msg, wParam, lParam));
192 static DWORD WINAPI monitor_service_9x_thread(void *service_name)
194 /* When running as a service under Windows 9x, there is no console
195 * window present, and no ConsoleCtrlHandler to call when the system
196 * is shutdown. If the WatchWindow thread is created with a NULL
197 * service_name argument, then the ...SystemMonitor window class is
198 * used to create the "Apache" window to watch for logoff and shutdown.
199 * If the service_name is provided, the ...ServiceMonitor window class
200 * is used to create the window named by the service_name argument,
201 * and the logoff message is ignored.
207 wc.style = CS_GLOBALCLASS;
208 wc.lpfnWndProc = monitor_service_9x_proc;
214 wc.hbrBackground = NULL;
215 wc.lpszMenuName = NULL;
217 wc.lpszClassName = "ApacheWin95ServiceMonitor";
219 wc.lpszClassName = "ApacheWin95SystemMonitor";
221 die_on_logoff = service_name ? FALSE : TRUE;
223 if (!RegisterClass(&wc))
225 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
226 NULL, "Could not register window class for WatchWindow");
227 globdat.service_thread_id = 0;
231 /* Create an invisible window */
232 hwndMain = CreateWindow(wc.lpszClassName,
233 service_name ? (char *) service_name : "Apache",
234 WS_OVERLAPPEDWINDOW & ~WS_VISIBLE,
235 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
236 CW_USEDEFAULT, NULL, NULL, NULL, NULL);
240 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
241 NULL, "Could not create WatchWindow");
242 globdat.service_thread_id = 0;
246 /* If we succeed, eliminate the console window.
247 * Signal the parent we are all set up, and
248 * watch the message queue while the window lives.
251 SetEvent(globdat.service_init);
253 while (GetMessage(&msg, NULL, 0, 0))
255 if (msg.message == WM_CLOSE)
256 DestroyWindow(hwndMain);
258 TranslateMessage(&msg);
259 DispatchMessage(&msg);
262 globdat.service_thread_id = 0;
267 static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
271 case CTRL_BREAK_EVENT:
272 fprintf(stderr, "Apache server restarting...\n");
273 ap_signal_parent(SIGNAL_PARENT_RESTART);
276 fprintf(stderr, "Apache server interrupted...\n");
277 /* for Interrupt signals, shut down the server.
278 * Tell the system we have dealt with the signal
279 * without waiting for Apache to terminate.
281 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
284 case CTRL_CLOSE_EVENT:
285 case CTRL_LOGOFF_EVENT:
286 case CTRL_SHUTDOWN_EVENT:
287 /* for Terminate signals, shut down the server.
288 * Wait for Apache to terminate, but respond
289 * after a reasonable time to tell the system
290 * that we did attempt to shut ourself down.
291 * THESE EVENTS WILL NOT OCCUR UNDER WIN9x!
293 fprintf(stderr, "Apache server shutdown initiated...\n");
294 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
299 /* We should never get here, but this is (mostly) harmless */
304 static void stop_console_handler(void)
306 SetConsoleCtrlHandler(console_control_handler, FALSE);
310 void mpm_start_console_handler(void)
312 SetConsoleCtrlHandler(console_control_handler, TRUE);
313 atexit(stop_console_handler);
317 /* Special situation - children of services need to mind their
318 * P's & Q's and wait quietly, ignoring the mean OS signaling
319 * shutdown and other horrors, to kill them gracefully...
322 static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
327 case CTRL_BREAK_EVENT:
328 /* for Interrupt signals, ignore them.
329 * The system will also signal the parent process,
330 * which will terminate Apache.
334 case CTRL_CLOSE_EVENT:
335 case CTRL_LOGOFF_EVENT:
336 case CTRL_SHUTDOWN_EVENT:
337 /* for Shutdown signals, ignore them, but... .
338 * The system will also signal the parent process,
339 * which will terminate Apache, so we need to wait.
345 /* We should never get here, but this is (mostly) harmless */
350 static void stop_child_console_handler(void)
352 SetConsoleCtrlHandler(child_control_handler, FALSE);
356 void mpm_start_child_console_handler(void)
358 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
363 SetConsoleCtrlHandler(child_control_handler, TRUE);
364 atexit(stop_child_console_handler);
369 /**********************************
370 WinNT service control management
371 **********************************/
373 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
375 static int checkPoint = 1;
376 int rv = APR_SUCCESS;
378 if (globdat.hServiceStatus)
380 if (currentState == SERVICE_RUNNING) {
381 globdat.ssStatus.dwWaitHint = 0;
382 globdat.ssStatus.dwCheckPoint = 0;
383 globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
385 else if (currentState == SERVICE_STOPPED) {
386 globdat.ssStatus.dwWaitHint = 0;
387 globdat.ssStatus.dwCheckPoint = 0;
388 if (!exitCode && globdat.ssStatus.dwCurrentState
389 != SERVICE_STOP_PENDING) {
390 /* An unexpected exit? Better to error! */
394 globdat.ssStatus.dwWin32ExitCode =ERROR_SERVICE_SPECIFIC_ERROR;
395 globdat.ssStatus.dwServiceSpecificExitCode = exitCode;
399 globdat.ssStatus.dwCheckPoint = ++checkPoint;
400 globdat.ssStatus.dwControlsAccepted = 0;
402 globdat.ssStatus.dwWaitHint = waitHint;
405 globdat.ssStatus.dwCurrentState = currentState;
407 rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
412 /* Set the service description regardless of platform.
413 * We revert to set_service_description on NT/9x, the
414 * very long way so any Apache management program can grab the
415 * description. This would be bad on Win2000, since it wouldn't
416 * notify the service control manager of the name change.
419 /* borrowed from mpm_winnt.c */
420 extern apr_pool_t *pconf;
422 /* Windows 2000 alone supports ChangeServiceConfig2 in order to
423 * register our server_version string... so we need some fixups
424 * to avoid binding to that function if we are on WinNT/9x.
426 static void set_service_description(void)
428 const char *full_description;
429 SC_HANDLE schSCManager;
432 /* Nothing to do if we are a console
434 if (!mpm_service_name)
437 /* Time to fix up the description, upon each successful restart
439 full_description = ap_get_server_version();
441 if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
442 && (osver.dwMajorVersion > 4)
443 && (ChangeServiceConfig2)
444 && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)))
446 SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
447 SERVICE_CHANGE_CONFIG);
449 /* Cast is necessary, ChangeServiceConfig2 handles multiple
450 * object types, some volatile, some not.
453 if (ChangeServiceConfig2(schService,
454 1 /* SERVICE_CONFIG_DESCRIPTION */,
455 (LPVOID) &full_description)) {
456 full_description = NULL;
458 CloseServiceHandle(schService);
460 CloseServiceHandle(schSCManager);
463 if (full_description)
465 char szPath[MAX_PATH];
469 /* Find the Service key that Monitor Applications iterate */
470 apr_snprintf(szPath, sizeof(szPath),
471 "SYSTEM\\CurrentControlSet\\Services\\%s",
473 rv = ap_regkey_open(&svckey, AP_REGKEY_LOCAL_MACHINE, szPath,
474 APR_READ | APR_WRITE, pconf);
475 if (rv != APR_SUCCESS) {
478 /* Attempt to set the Description value for our service */
479 ap_regkey_value_set(svckey, "Description", full_description, 0, pconf);
480 ap_regkey_close(svckey);
484 /* handle the SCM's ControlService() callbacks to our service */
486 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
488 if (dwCtrlCode == SERVICE_CONTROL_STOP)
490 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
491 ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
494 if (dwCtrlCode == SERVICE_APACHE_RESTART)
496 ap_signal_parent(SIGNAL_PARENT_RESTART);
497 ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
501 ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
505 /* service_nt_main_fn is outside of the call stack and outside of the
506 * primary server thread... so now we _really_ need a placeholder!
507 * The winnt_rewrite_args has created and shared mpm_new_argv with us.
509 extern apr_array_header_t *mpm_new_argv;
512 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
516 /* args and service names live in the same pool */
517 mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
519 memset(&globdat.ssStatus, 0, sizeof(globdat.ssStatus));
520 globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
521 globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
522 globdat.ssStatus.dwCheckPoint = 1;
525 if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
527 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
528 NULL, "Failure registering service handler");
532 /* Report status, no errors, and buy 3 more seconds */
533 ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
535 /* We need to append all the command arguments passed via StartService()
536 * to our running service... which just got here via the SCM...
537 * but we hvae no interest in argv[0] for the mpm_new_argv list.
543 mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
544 cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
546 /* mpm_new_argv remains first (of lower significance) */
547 memcpy (cmb_data, mpm_new_argv->elts,
548 mpm_new_argv->elt_size * mpm_new_argv->nelts);
550 /* Service args follow from StartService() invocation */
551 memcpy (cmb_data + mpm_new_argv->nelts, argv + 1,
552 mpm_new_argv->elt_size * (argc - 1));
554 /* The replacement arg list is complete */
555 mpm_new_argv->elts = (char *)cmb_data;
556 mpm_new_argv->nelts = mpm_new_argv->nalloc;
559 /* Let the main thread continue now... but hang on to the
560 * signal_monitor event so we can take further action
562 SetEvent(globdat.service_init);
564 WaitForSingleObject(globdat.service_term, INFINITE);
568 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
570 apr_status_t rv = APR_SUCCESS;
572 SERVICE_TABLE_ENTRY dispatchTable[] =
574 { "", service_nt_main_fn },
579 if (!StartServiceCtrlDispatcher(dispatchTable))
581 /* This is a genuine failure of the SCM. */
582 rv = apr_get_os_error();
583 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
584 "Error starting service control dispatcher");
591 apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
592 const char *set_name)
594 char key_name[MAX_PATH];
598 /* ### Needs improvement, on Win2K the user can _easily_
599 * change the display name to a string that doesn't reflect
600 * the internal service name + whitespace!
602 mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
603 apr_collapse_spaces((char*) mpm_service_name, set_name);
604 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
605 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, APR_READ, pconf);
606 if (rv == APR_SUCCESS) {
607 rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf);
608 ap_regkey_close(key);
610 if (rv != APR_SUCCESS) {
611 /* Take the given literal name if there is no service entry */
612 mpm_display_name = apr_pstrdup(p, set_name);
614 *display_name = mpm_display_name;
619 apr_status_t mpm_merge_service_args(apr_pool_t *p,
620 apr_array_header_t *args,
623 apr_array_header_t *svc_args = NULL;
624 char conf_key[MAX_PATH];
629 apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
630 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p);
631 if (rv == APR_SUCCESS) {
632 rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p);
633 ap_regkey_close(key);
635 if (rv != APR_SUCCESS) {
636 if (rv == ERROR_FILE_NOT_FOUND) {
637 ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
638 "No ConfigArgs registered for %s, perhaps "
639 "this service is not installed?",
647 if (!svc_args || svc_args->nelts == 0) {
648 return (APR_SUCCESS);
651 /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
652 * call appended the arguments passed by StartService(), so it's
653 * time to _prepend_ the default arguments for the server from
654 * the service's default arguments (all others override them)...
656 args->nalloc = args->nelts + svc_args->nelts;
657 cmb_data = malloc(args->nalloc * sizeof(const char *));
659 /* First three args (argv[0], -f, path) remain first */
660 memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
662 /* Service args follow from service registry array */
663 memcpy(cmb_data + fixed_args, svc_args->elts,
664 svc_args->elt_size * svc_args->nelts);
666 /* Remaining new args follow */
667 memcpy(cmb_data + fixed_args + svc_args->nelts,
668 (const char **)args->elts + fixed_args,
669 args->elt_size * (args->nelts - fixed_args));
671 args->elts = (char *)cmb_data;
672 args->nelts = args->nalloc;
678 void service_stopped(void)
680 /* Still have a thread & window to clean up, so signal now */
681 if (globdat.service_thread)
683 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
685 /* Stop logging to the event log */
686 mpm_nt_eventlog_stderr_flush();
688 /* Cause the service_nt_main_fn to complete */
689 ReleaseMutex(globdat.service_term);
691 ReportStatusToSCMgr(SERVICE_STOPPED, // service state
692 NO_ERROR, // exit code
695 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
697 RegisterServiceProcess(0, 0);
698 PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0);
701 WaitForSingleObject(globdat.service_thread, 5000);
702 CloseHandle(globdat.service_thread);
707 apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p)
709 HANDLE hProc = GetCurrentProcess();
710 HANDLE hThread = GetCurrentThread();
713 /* Prevent holding open the (hidden) console */
714 ap_real_exit_code = 0;
716 /* GetCurrentThread returns a psuedo-handle, we need
717 * a real handle for another thread to wait upon.
719 if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
720 0, FALSE, DUPLICATE_SAME_ACCESS)) {
721 return APR_ENOTHREAD;
724 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
726 globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
727 globdat.service_term = CreateMutex(NULL, TRUE, NULL);
728 if (!globdat.service_init || !globdat.service_term) {
732 globdat.service_thread = CreateThread(NULL, 0, service_nt_dispatch_thread,
733 NULL, 0, &globdat.service_thread_id);
735 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
737 if (!RegisterServiceProcess(0, 1))
738 return GetLastError();
740 globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
741 if (!globdat.service_init) {
745 globdat.service_thread = CreateThread(NULL, 0, monitor_service_9x_thread,
746 (LPVOID) mpm_service_name, 0,
747 &globdat.service_thread_id);
750 if (!globdat.service_thread) {
751 return APR_ENOTHREAD;
754 waitfor[0] = globdat.service_init;
755 waitfor[1] = globdat.service_thread;
757 /* Wait for controlling thread init or termination */
758 if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
759 return APR_ENOTHREAD;
762 atexit(service_stopped);
763 *display_name = mpm_display_name;
768 apr_status_t mpm_service_started(void)
770 set_service_description();
771 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
773 ReportStatusToSCMgr(SERVICE_RUNNING, // service state
774 NO_ERROR, // exit code
781 void mpm_service_stopping(void)
783 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
784 ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state
785 NO_ERROR, // exit code
790 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
791 const char * const * argv, int reconfig)
793 char key_name[MAX_PATH];
794 char exe_path[MAX_PATH];
799 fprintf(stderr,reconfig ? "Reconfiguring the %s service\n"
800 : "Installing the %s service\n", mpm_display_name);
803 if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
805 apr_status_t rv = apr_get_os_error();
806 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
807 "GetModuleFileName failed");
811 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
813 SC_HANDLE schService;
814 SC_HANDLE schSCManager;
816 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
817 SC_MANAGER_CREATE_SERVICE);
819 rv = apr_get_os_error();
820 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
821 "Failed to open the WinNT service manager");
825 launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
829 schService = OpenService(schSCManager, mpm_service_name,
830 SERVICE_CHANGE_CONFIG);
832 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
833 apr_get_os_error(), NULL,
834 "OpenService failed");
837 else if (!ChangeServiceConfig(schService,
838 SERVICE_WIN32_OWN_PROCESS,
840 SERVICE_ERROR_NORMAL,
841 launch_cmd, NULL, NULL,
842 "Tcpip\0Afd\0", NULL, NULL,
844 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
845 apr_get_os_error(), NULL,
846 "ChangeServiceConfig failed");
847 /* !schService aborts configuration below */
848 CloseServiceHandle(schService);
853 /* RPCSS is the Remote Procedure Call (RPC) Locator required
854 * for DCOM communication pipes. I am far from convinced we
855 * should add this to the default service dependencies, but
856 * be warned that future apache modules or ISAPI dll's may
860 schService = CreateService(schSCManager, // SCManager database
861 mpm_service_name, // name of service
862 mpm_display_name, // name to display
863 SERVICE_ALL_ACCESS, // access required
864 SERVICE_WIN32_OWN_PROCESS, // service type
865 SERVICE_AUTO_START, // start type
866 SERVICE_ERROR_NORMAL, // error control type
867 launch_cmd, // service's binary
868 NULL, // no load svc group
869 NULL, // no tag identifier
870 "Tcpip\0Afd\0", // dependencies
871 NULL, // use SYSTEM account
872 NULL); // no password
876 rv = apr_get_os_error();
877 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
878 "Failed to create WinNT Service Profile");
879 CloseServiceHandle(schSCManager);
884 CloseServiceHandle(schService);
885 CloseServiceHandle(schSCManager);
887 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
889 /* Store the launch command in the registry */
890 launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice",
891 exe_path, mpm_service_name);
892 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X,
893 APR_READ | APR_WRITE | APR_CREATE, pconf);
894 if (rv == APR_SUCCESS) {
895 rv = ap_regkey_value_set(key, mpm_service_name,
896 launch_cmd, 0, pconf);
897 ap_regkey_close(key);
899 if (rv != APR_SUCCESS) {
900 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
901 "%s: Failed to add the RunServices registry entry.",
906 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
907 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
908 APR_READ | APR_WRITE | APR_CREATE, pconf);
909 if (rv != APR_SUCCESS) {
910 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
911 "%s: Failed to create the registry service key.",
915 rv = ap_regkey_value_set(key, "ImagePath", launch_cmd, 0, pconf);
916 if (rv != APR_SUCCESS) {
917 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
918 "%s: Failed to store ImagePath in the registry.",
920 ap_regkey_close(key);
923 rv = ap_regkey_value_set(key, "DisplayName",
924 mpm_display_name, 0, pconf);
925 ap_regkey_close(key);
926 if (rv != APR_SUCCESS) {
927 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
928 "%s: Failed to store DisplayName in the registry.",
934 set_service_description();
936 /* For both WinNT & Win9x store the service ConfigArgs in the registry...
938 apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
939 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
940 APR_READ | APR_WRITE | APR_CREATE, pconf);
941 if (rv == APR_SUCCESS) {
942 rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf);
943 ap_regkey_close(key);
945 if (rv != APR_SUCCESS) {
946 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
947 "%s: Failed to store the ConfigArgs in the registry.",
951 fprintf(stderr,"The %s service is successfully installed.\n", mpm_display_name);
956 apr_status_t mpm_service_uninstall(void)
958 char key_name[MAX_PATH];
961 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
963 SC_HANDLE schService;
964 SC_HANDLE schSCManager;
966 fprintf(stderr,"Removing the %s service\n", mpm_display_name);
968 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
971 rv = apr_get_os_error();
972 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
973 "Failed to open the WinNT service manager.");
978 schService = OpenService(schSCManager, mpm_service_name, DELETE);
981 rv = apr_get_os_error();
982 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
983 "%s: OpenService failed", mpm_display_name);
987 /* assure the service is stopped before continuing
989 * This may be out of order... we might not be able to be
990 * granted all access if the service is running anyway.
992 * And do we want to make it *this easy* for them
993 * to uninstall their service unintentionally?
995 // ap_stop_service(schService);
997 if (DeleteService(schService) == 0) {
998 rv = apr_get_os_error();
999 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1000 "%s: Failed to delete the service.", mpm_display_name);
1004 CloseServiceHandle(schService);
1005 CloseServiceHandle(schSCManager);
1007 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1009 apr_status_t rv2, rv3;
1011 fprintf(stderr,"Removing the %s service\n", mpm_display_name);
1013 /* TODO: assure the service is stopped before continuing */
1015 rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X,
1016 APR_READ | APR_WRITE | APR_CREATE, pconf);
1017 if (rv == APR_SUCCESS) {
1018 rv = ap_regkey_value_remove(key, mpm_service_name, pconf);
1019 ap_regkey_close(key);
1021 if (rv != APR_SUCCESS) {
1022 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1023 "%s: Failed to remove the RunServices registry "
1024 "entry.", mpm_display_name);
1027 /* we blast Services/us, not just the Services/us/Parameters branch */
1028 apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
1029 rv2 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf);
1030 apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
1031 rv3 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf);
1032 rv2 = (rv2 != APR_SUCCESS) ? rv2 : rv3;
1033 if (rv2 != APR_SUCCESS) {
1034 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv2, NULL,
1035 "%s: Failed to remove the service config from the "
1036 "registry.", mpm_display_name);
1038 rv = (rv != APR_SUCCESS) ? rv : rv2;
1039 if (rv != APR_SUCCESS)
1042 fprintf(stderr,"The %s service has been removed successfully.\n", mpm_display_name);
1047 /* signal_service_transition is a simple thunk to signal the service
1048 * and monitor its successful transition. If the signal passed is 0,
1049 * then the caller is assumed to already have performed some service
1050 * operation to be monitored (such as StartService), and no actual
1051 * ControlService signal is sent.
1054 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
1056 if (signal && !ControlService(schService, signal, &globdat.ssStatus))
1061 if (!QueryServiceStatus(schService, &globdat.ssStatus))
1063 } while (globdat.ssStatus.dwCurrentState == pending);
1065 return (globdat.ssStatus.dwCurrentState == complete);
1069 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
1070 const char * const * argv)
1074 fprintf(stderr,"Starting the %s service\n", mpm_display_name);
1076 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1079 SC_HANDLE schService;
1080 SC_HANDLE schSCManager;
1082 schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1083 SC_MANAGER_CONNECT);
1084 if (!schSCManager) {
1085 rv = apr_get_os_error();
1086 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1087 "Failed to open the WinNT service manager");
1092 schService = OpenService(schSCManager, mpm_service_name,
1093 SERVICE_START | SERVICE_QUERY_STATUS);
1095 rv = apr_get_os_error();
1096 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1097 "%s: Failed to open the service.", mpm_display_name);
1098 CloseServiceHandle(schSCManager);
1102 if (QueryServiceStatus(schService, &globdat.ssStatus)
1103 && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
1104 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1105 "Service %s is already started!", mpm_display_name);
1106 CloseServiceHandle(schService);
1107 CloseServiceHandle(schSCManager);
1111 start_argv = malloc((argc + 1) * sizeof(const char **));
1112 memcpy(start_argv, argv, argc * sizeof(const char **));
1113 start_argv[argc] = NULL;
1117 if (StartService(schService, argc, start_argv)
1118 && signal_service_transition(schService, 0, /* test only */
1119 SERVICE_START_PENDING,
1123 if (rv != APR_SUCCESS)
1124 rv = apr_get_os_error();
1126 CloseServiceHandle(schService);
1127 CloseServiceHandle(schSCManager);
1129 else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1131 STARTUPINFO si; /* Filled in prior to call to CreateProcess */
1132 PROCESS_INFORMATION pi; /* filled in on call to CreateProcess */
1133 char exe_path[MAX_PATH];
1134 char exe_cmd[MAX_PATH * 4];
1138 /* Locate the active top level window named service_name
1139 * provided the class is ApacheWin95ServiceMonitor
1141 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1142 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1143 "Service %s is already started!", mpm_display_name);
1147 /* This may not appear intuitive, but Win9x will not allow a process
1148 * to detach from the console without releasing the entire console.
1149 * Ergo, we must spawn a new process for the service to get back our
1151 * The config is pre-flighted, so there should be no danger of failure.
1154 if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
1156 apr_status_t rv = apr_get_os_error();
1157 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1158 "GetModuleFileName failed");
1162 apr_snprintf(exe_cmd, sizeof(exe_cmd),
1163 "\"%s\" -n %s -k runservice",
1164 exe_path, mpm_service_name);
1165 next_arg = strchr(exe_cmd, '\0');
1166 for (i = 0; i < argc; ++i) {
1167 apr_snprintf(next_arg, sizeof(exe_cmd) - (next_arg - exe_cmd),
1168 " \"%s\"", argv[i]);
1169 next_arg = strchr(exe_cmd, '\0');
1172 memset(&si, 0, sizeof(si));
1173 memset(&pi, 0, sizeof(pi));
1175 si.dwFlags = STARTF_USESHOWWINDOW;
1176 si.wShowWindow = SW_HIDE; /* This might be redundant */
1179 if (CreateProcess(NULL, exe_cmd, NULL, NULL, FALSE,
1180 DETACHED_PROCESS, /* Creation flags */
1181 NULL, NULL, &si, &pi))
1184 while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) {
1185 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1193 if (rv != APR_SUCCESS)
1194 rv = apr_get_os_error();
1196 CloseHandle(pi.hProcess);
1197 CloseHandle(pi.hThread);
1200 if (rv == APR_SUCCESS)
1201 fprintf(stderr,"The %s service is running.\n", mpm_display_name);
1203 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
1204 "%s: Failed to start the service process.",
1211 /* signal is zero to stop, non-zero for restart */
1213 void mpm_signal_service(apr_pool_t *ptemp, int signal)
1215 int success = FALSE;
1217 if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1219 SC_HANDLE schService;
1220 SC_HANDLE schSCManager;
1222 schSCManager = OpenSCManager(NULL, NULL, // default machine & database
1223 SC_MANAGER_CONNECT);
1225 if (!schSCManager) {
1226 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1227 "Failed to open the NT Service Manager");
1232 schService = OpenService(schSCManager, mpm_service_name,
1233 SERVICE_INTERROGATE | SERVICE_QUERY_STATUS |
1234 SERVICE_USER_DEFINED_CONTROL |
1235 SERVICE_START | SERVICE_STOP);
1237 if (schService == NULL) {
1238 /* Could not open the service */
1239 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1240 "Failed to open the %s Service", mpm_display_name);
1241 CloseServiceHandle(schSCManager);
1245 if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
1246 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1247 "Query of Service %s failed", mpm_display_name);
1248 CloseServiceHandle(schService);
1249 CloseServiceHandle(schSCManager);
1253 if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
1254 fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
1255 CloseServiceHandle(schService);
1256 CloseServiceHandle(schSCManager);
1260 fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
1261 signal ? "restarting" : "stopping");
1264 success = signal_service_transition(schService,
1265 SERVICE_CONTROL_STOP,
1266 SERVICE_STOP_PENDING,
1268 else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1269 mpm_service_start(ptemp, 0, NULL);
1270 CloseServiceHandle(schService);
1271 CloseServiceHandle(schSCManager);
1275 success = signal_service_transition(schService,
1276 SERVICE_APACHE_RESTART,
1277 SERVICE_START_PENDING,
1280 CloseServiceHandle(schService);
1281 CloseServiceHandle(schSCManager);
1283 else /* !isWindowsNT() */
1288 /* Locate the active top level window named service_name
1289 * provided the class is ApacheWin95ServiceMonitor
1291 hwnd = FindWindow("ApacheWin95ServiceMonitor", mpm_service_name);
1292 if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
1293 globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
1296 globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
1298 fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
1303 fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
1304 signal ? "restarting" : "stopping");
1306 apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
1307 setup_signal_names(prefix);
1312 ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
1315 if (!IsWindow(hwnd)) {
1324 /* TODO: Aught to add a little test to the restart logic, and
1325 * store the restart counter in the window's user dword.
1326 * Then we can hang on and report a successful restart. But
1327 * that's a project for another day.
1329 if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1330 mpm_service_start(ptemp, 0, NULL);
1335 ap_signal_parent(SIGNAL_PARENT_RESTART);
1341 fprintf(stderr,"The %s service has %s.\n", mpm_display_name,
1342 signal ? "restarted" : "stopped");
1344 fprintf(stderr,"Failed to %s the %s service.\n",
1345 signal ? "restart" : "stop", mpm_display_name);