]> granicus.if.org Git - apache/blob - server/mpm/winnt/service.c
Use "const char * const *" for process->argv (which is the correct
[apache] / server / mpm / winnt / service.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
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
17  *    distribution.
18  *
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.
25  *
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.
30  *
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.
34  *
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
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
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/>.
53  *
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.
57  */
58
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...
62  */
63
64 #ifndef _WIN32_WINNT
65 #define _WIN32_WINNT 0x0400
66 #endif
67 #ifndef NOGDI
68 #define NOGDI
69 #endif
70 #ifndef NONLS
71 #define NONLS
72 #endif
73 #ifndef NOMCX
74 #define NOMCX
75 #endif
76 #ifndef NOIME
77 #define NOIME
78 #endif
79 #include <windows.h>
80 #include <winsock2.h>
81 #include <mswsock.h>
82
83 #define  CORE_PRIVATE 
84
85 #include "httpd.h"
86 #include "http_conf_globals.h"
87 #include "http_log.h"
88 #include "mpm_winnt.h"
89 #include "apr_strings.h"
90
91 static const char * service_name = NULL;
92
93 /* ### should be namespace-protected */
94 const char * display_name = NULL;
95
96 static struct
97 {
98     HANDLE mpm_thread;       /* primary thread handle of the apache server */
99     HANDLE service_thread;   /* thread service/monitor handle */
100     DWORD  service_thread_id;/* thread service/monitor ID */
101     HANDLE signal_monitor;   /* service monitor thread signal event */
102     SERVICE_STATUS ssStatus;
103     SERVICE_STATUS_HANDLE hServiceStatus;
104 } globdat;
105
106 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
107
108 /* The service configuration's is stored under the following trees:
109  *
110  * HKLM\System\CurrentControlSet\Services\[service name]
111  *
112  *     \DisplayName
113  *     \ImagePath            (NT Only)
114  *     \Parameters\ConfigArgs
115  *
116  * For Win9x, the launch service command is stored under:
117  *
118  * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name]
119  */
120
121
122 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us 
123  * to catch the non-zero conditions and inform the console process that
124  * the application died, and hang on to the console a bit longer.
125  *
126  * The macro only maps for http_main.c and other sources that include
127  * the service.h header, so we best assume it's an error to exit from
128  * _any_ other module.
129  *
130  * If real_exit_code is reset to 0, it will not be set or trigger this
131  * behavior on exit.  All service and child processes are expected to
132  * reset this flag to zero to avoid undesireable side effects.
133  */
134 int real_exit_code = 1;
135
136 void hold_console_open_on_error(void)
137 {
138     HANDLE hConIn;
139     HANDLE hConErr;
140     DWORD result;
141     time_t start;
142     time_t remains;
143     char *msg = "Note the errors or messages above, "
144                 "and press the <ESC> key to exit.  ";
145     CONSOLE_SCREEN_BUFFER_INFO coninfo;
146     INPUT_RECORD in;
147     char count[16];
148     
149     if (!real_exit_code)
150         return;
151     hConIn = GetStdHandle(STD_INPUT_HANDLE);
152     hConErr = GetStdHandle(STD_ERROR_HANDLE);
153     if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
154         return;
155     if (!WriteConsole(hConErr, msg, strlen(msg), &result, NULL) || !result)
156         return;
157     if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
158         return;
159     if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
160         return;
161         
162     start = time(NULL);
163     do
164     {
165         while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
166         {
167             if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
168                 return;
169             if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown 
170                     && (in.Event.KeyEvent.uChar.AsciiChar == 27))
171                 return;
172             if (in.EventType == MOUSE_EVENT 
173                     && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
174                 return;
175         }
176         remains = ((start + 30) - time(NULL)); 
177         sprintf (count, "%d...", remains);
178         if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
179             return;
180         if (!WriteConsole(hConErr, count, strlen(count), &result, NULL) 
181                 || !result)
182             return;
183     }
184     while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
185 }
186
187 static BOOL  die_on_logoff = FALSE;
188
189 static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg, 
190                                                 WPARAM wParam, LPARAM lParam)
191 {
192 /* This is the WndProc procedure for our invisible window.
193  * When the user shuts down the system, this window is sent
194  * a signal WM_ENDSESSION. We clean up by signaling Apache
195  * to shut down, and idle until Apache's primary thread quits.
196  */
197     if ((msg == WM_ENDSESSION) 
198             && (die_on_logoff || (lParam != ENDSESSION_LOGOFF)))
199     {
200         signal_parent(0);
201         if (wParam)
202             /* Don't leave this message until we are dead! */
203             WaitForSingleObject(globdat.mpm_thread, 30000);
204         return 0;
205     }
206     return (DefWindowProc(hWnd, msg, wParam, lParam));
207 }
208
209 static DWORD WINAPI monitor_service_9x_thread(void *service_name)
210 {
211     /* When running as a service under Windows 9x, there is no console
212      * window present, and no ConsoleCtrlHandler to call when the system 
213      * is shutdown.  If the WatchWindow thread is created with a NULL
214      * service_name argument, then the ...SystemMonitor window class is
215      * used to create the "Apache" window to watch for logoff and shutdown.
216      * If the service_name is provided, the ...ServiceMonitor window class
217      * is used to create the window named by the service_name argument,
218      * and the logoff message is ignored.
219      */
220     WNDCLASS wc;
221     HWND hwndMain;
222     MSG msg;
223     
224     wc.style         = CS_GLOBALCLASS;
225     wc.lpfnWndProc   = monitor_service_9x_proc; 
226     wc.cbClsExtra    = 0;
227     wc.cbWndExtra    = 0; 
228     wc.hInstance     = NULL;
229     wc.hIcon         = NULL;
230     wc.hCursor       = NULL;
231     wc.hbrBackground = NULL;
232     wc.lpszMenuName  = NULL;
233     if (service_name)
234         wc.lpszClassName = "ApacheWin95ServiceMonitor";
235     else
236         wc.lpszClassName = "ApacheWin95SystemMonitor";
237  
238     die_on_logoff = service_name ? FALSE : TRUE;
239
240     if (!RegisterClass(&wc)) 
241     {
242         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), 
243                      NULL, "Could not register window class for WatchWindow");
244         SetEvent(globdat.signal_monitor);
245         globdat.service_thread_id = 0;
246         return 0;
247     }
248     
249     /* Create an invisible window */
250     hwndMain = CreateWindow(wc.lpszClassName, 
251                             service_name ? (char *) service_name : "Apache",
252                             WS_OVERLAPPEDWINDOW & ~WS_VISIBLE, 
253                             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
254                             CW_USEDEFAULT, NULL, NULL, NULL, NULL);
255                             
256     if (!hwndMain)
257     {
258         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), 
259                      NULL, "Could not create WatchWindow");
260         SetEvent(globdat.signal_monitor);
261         globdat.service_thread_id = 0;
262         return 0;
263     }
264
265     /* If we succeed, eliminate the console window.
266      * Signal the parent we are all set up, and
267      * watch the message queue while the window lives.
268      */
269     FreeConsole();
270     SetEvent((HANDLE) globdat.signal_monitor);
271     while (GetMessage(&msg, NULL, 0, 0)) 
272     {
273         if (msg.message == WM_CLOSE)
274             DestroyWindow(hwndMain); 
275         else {
276             TranslateMessage(&msg);
277             DispatchMessage(&msg);
278         }
279     }
280     globdat.service_thread_id = 0;
281     return 0;
282 }
283
284
285 static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
286 {
287     switch (ctrl_type)
288     {
289         case CTRL_BREAK_EVENT:
290             fprintf(stderr, "Apache server restarting...\n");
291             signal_parent(1);
292             return TRUE;
293         case CTRL_C_EVENT:
294             fprintf(stderr, "Apache server interrupted...\n");
295             /* for Interrupt signals, shut down the server.
296              * Tell the system we have dealt with the signal
297              * without waiting for Apache to terminate.
298              */
299             signal_parent(0);
300             return TRUE;
301
302         case CTRL_CLOSE_EVENT:
303         case CTRL_LOGOFF_EVENT:
304         case CTRL_SHUTDOWN_EVENT:
305             /* for Terminate signals, shut down the server.
306              * Wait for Apache to terminate, but respond
307              * after a reasonable time to tell the system
308              * that we did attempt to shut ourself down.
309              * THESE EVENTS WILL NOT OCCUR UNDER WIN9x!
310              */
311             fprintf(stderr, "Apache server shutdown initiated...\n");
312             signal_parent(0);
313             Sleep(30000);
314             return TRUE;
315     }
316  
317     /* We should never get here, but this is (mostly) harmless */
318     return FALSE;
319 }
320
321
322 static void stop_console_handler(void)
323 {
324     SetConsoleCtrlHandler(console_control_handler, FALSE);
325 }
326
327
328 void mpm_start_console_handler(void)
329 {
330     SetConsoleCtrlHandler(console_control_handler, TRUE);
331     atexit(stop_console_handler);
332 }
333
334
335 /* Special situation - children of services need to mind their
336  * P's & Q's and wait quietly, ignoring the mean OS signaling
337  * shutdown and other horrors, to kill them gracefully...
338  */
339
340 static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
341 {
342     switch (ctrl_type)
343     {
344         case CTRL_C_EVENT:
345         case CTRL_BREAK_EVENT:
346             /* for Interrupt signals, ignore them.
347              * The system will also signal the parent process,
348              * which will terminate Apache.
349              */
350             return TRUE;
351
352         case CTRL_CLOSE_EVENT:
353         case CTRL_LOGOFF_EVENT:
354         case CTRL_SHUTDOWN_EVENT:
355             /* for Shutdown signals, ignore them, but...             .
356              * The system will also signal the parent process,
357              * which will terminate Apache, so we need to wait.
358              */
359             Sleep(30000);
360             return TRUE;
361     }
362  
363     /* We should never get here, but this is (mostly) harmless */
364     return FALSE;
365 }
366
367
368 static void stop_child_console_handler(void)
369 {
370     SetConsoleCtrlHandler(child_control_handler, FALSE);
371 }
372
373
374 void mpm_start_child_console_handler(void)
375 {
376     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
377         FreeConsole();
378     }
379     else
380     {
381         SetConsoleCtrlHandler(child_control_handler, TRUE);
382         atexit(stop_child_console_handler);
383     }
384 }
385
386
387 /**********************************
388   WinNT service control management
389  **********************************/
390
391 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
392 {
393     static int checkPoint = 1;
394     int rv = APR_SUCCESS;
395     
396     if (globdat.hServiceStatus)
397     {
398         if (currentState == SERVICE_RUNNING)
399             globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
400         else
401             globdat.ssStatus.dwControlsAccepted = 0;
402         
403         globdat.ssStatus.dwCurrentState = currentState;
404         globdat.ssStatus.dwWin32ExitCode = exitCode;
405         
406         if ( ( currentState == SERVICE_RUNNING ) ||
407              ( currentState == SERVICE_STOPPED ) )
408         {
409             globdat.ssStatus.dwWaitHint = 0;
410             globdat.ssStatus.dwCheckPoint = 0;
411         }
412         else
413         {
414             if(waitHint)
415                 globdat.ssStatus.dwWaitHint = waitHint;
416             globdat.ssStatus.dwCheckPoint = ++checkPoint;
417         }
418         rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
419     }
420     return(rv);
421 }
422
423
424 /* handle the SCM's ControlService() callbacks to our service */
425
426 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
427 {
428     if (dwCtrlCode == SERVICE_CONTROL_STOP)
429     {
430         ap_start_shutdown();
431         globdat.ssStatus.dwCurrentState = SERVICE_STOP_PENDING;
432         ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 3000);
433         return;
434     }
435     if (dwCtrlCode == SERVICE_APACHE_RESTART)
436     {
437         ap_start_restart(1);
438         globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
439         ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000);
440         return;
441     }
442     
443     ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);            
444 }
445
446
447 long __stdcall service_stderr_thread(LPVOID hPipe)
448 {
449     HANDLE hPipeRead = (HANDLE) hPipe;
450     HANDLE hEventSource;
451     char errbuf[256];
452     char *errmsg = errbuf;
453     const char *errarg[9];
454     DWORD errlen = 0;
455     DWORD errres;
456     HKEY hk;
457     
458     errarg[0] = "The Apache service named";
459     errarg[1] = display_name;
460     errarg[2] = "reported the following error:\r\n>>>";
461     errarg[3] = errmsg;
462     errarg[4] = "<<<\r\n before the error.log file could be opened.\r\n";
463     errarg[5] = "More information may be available in the error.log file.";
464     errarg[6] = NULL;
465     errarg[7] = NULL;
466     errarg[8] = NULL;
467     
468     /* What are we going to do in here, bail on the user?  not. */
469     if (!RegCreateKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services"
470                       "\\EventLog\\Application\\Apache Service", &hk)) 
471     {
472         /* The stock message file */
473         char *netmsgkey = "%SystemRoot%\\System32\\netmsg.dll";
474         DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | 
475                        EVENTLOG_INFORMATION_TYPE; 
476  
477         RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ,
478                           (LPBYTE) netmsgkey, strlen(netmsgkey) + 1);
479         
480         RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD,
481                           (LPBYTE) &dwData, sizeof(dwData));
482         RegCloseKey(hk);
483     }
484
485     hEventSource = RegisterEventSource(NULL, "Apache Service");
486
487     while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1))
488     {
489         if ((errmsg > errbuf) || !isspace(*errmsg))
490         {
491             ++errlen;
492             ++errmsg;
493             if ((*(errmsg - 1) == '\n') || (errlen == sizeof(errbuf) - 1))
494             {
495                 while (errlen && isspace(errbuf[errlen - 1]))
496                     --errlen;
497                 errbuf[errlen] = '\0';
498
499                 /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9'
500                  * The event code in netmsg.dll is 3299
501                  */
502                 ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, 
503                             3299, NULL, 9, 0, errarg, NULL);
504                 errmsg = errbuf;
505                 errlen = 0;
506             }
507         }
508     }
509
510     CloseHandle(hPipeRead);
511     return 0;
512 }
513
514
515 /* service_nt_main_fn is outside of the call stack and outside of the
516  * primary server thread... so now we _really_ need a placeholder!
517  * The winnt_rewrite_args has created and shared mpm_new_argv with us.
518  */
519 extern apr_array_header_t *mpm_new_argv;
520
521 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
522 {
523     HANDLE waitfor[2];
524     HANDLE hCurrentProcess;
525     HANDLE hPipeRead = NULL;
526     HANDLE hPipeWrite = NULL;
527     HANDLE hPipeReadDup;
528     HANDLE thread;
529     DWORD  threadid;
530     SECURITY_ATTRIBUTES sa = {0};  
531     
532     /* args and service names live in the same pool */
533     mpm_service_set_name(mpm_new_argv->cont, argv[0]);
534
535     globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
536     globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
537     globdat.ssStatus.dwServiceSpecificExitCode = 0;
538     globdat.ssStatus.dwCheckPoint = 1;
539
540     if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
541     {
542         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), 
543                      NULL, "Failure registering service handler");
544         PulseEvent(globdat.signal_monitor);
545         return;
546     }
547
548     ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, // service state
549                         NO_ERROR,              // exit code
550                         3000);                 // wait hint, 3 seconds more
551     
552     /* Create a pipe to send stderr messages to the system error log */
553     hCurrentProcess = GetCurrentProcess();
554     if (CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0)) 
555     {
556         if (DuplicateHandle(hCurrentProcess, hPipeRead, hCurrentProcess,
557                             &hPipeReadDup, 0, FALSE, DUPLICATE_SAME_ACCESS))
558         {
559             CloseHandle(hPipeRead);
560             hPipeRead = hPipeReadDup;
561             thread = CreateThread(NULL, 0, service_stderr_thread, 
562                                   (LPVOID) hPipeRead, 0, &threadid);
563             if (thread)
564             {
565                 int fh;
566                 FILE *fl;
567                 CloseHandle(thread);
568                 fflush(stderr);
569                 SetStdHandle(STD_ERROR_HANDLE, hPipeWrite);
570                                 
571                 fh = _open_osfhandle((long) STD_ERROR_HANDLE, 
572                                      _O_WRONLY | _O_BINARY);
573                 dup2(fh, STDERR_FILENO);
574                 fl = _fdopen(STDERR_FILENO, "wcb");
575                 memcpy(stderr, fl, sizeof(FILE));
576             }
577             else
578             {
579                 CloseHandle(hPipeRead);
580                 CloseHandle(hPipeWrite);
581                 hPipeWrite = NULL;
582             }            
583         }
584         else
585         {
586             CloseHandle(hPipeRead);
587             CloseHandle(hPipeWrite);
588             hPipeWrite = NULL;
589         }            
590     }
591
592     /* We need to append all the command arguments passed via StartService() 
593      * to our running service... which just got here via the SCM...
594      * but we hvae no interest in argv[0] for the mpm_new_argv list.
595      */
596     if (argc > 1) 
597     {
598         const char **cmb_data;
599
600         mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
601         cmb_data = apr_palloc(mpm_new_argv->cont,
602                               mpm_new_argv->nalloc * sizeof(const char *));
603
604         /* mpm_new_argv remains first (of lower significance) */
605         memcpy (cmb_data, mpm_new_argv->elts, 
606                 mpm_new_argv->elt_size * mpm_new_argv->nelts);
607         
608         /* Service args follow from StartService() invocation */
609         memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, 
610                 mpm_new_argv->elt_size * (argc - 1));
611         
612         /* The replacement arg list is complete */
613         mpm_new_argv->elts = (char *)cmb_data;
614         mpm_new_argv->nelts = mpm_new_argv->nalloc;
615     }
616         
617     /* Let the main thread continue now... but hang on to the
618      * signal_monitor event so we can take further action
619      */
620     SetEvent(globdat.signal_monitor);
621
622     waitfor[0] = globdat.signal_monitor;
623     waitfor[1] = globdat.mpm_thread;
624     WaitForMultipleObjects(2, waitfor, FALSE, INFINITE);
625     /* The process is ready to terminate, or already has */
626
627     CloseHandle(hPipeWrite);
628 }
629
630
631 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
632 {
633     apr_status_t rv = APR_SUCCESS;
634
635     SERVICE_TABLE_ENTRY dispatchTable[] =
636     {
637         { "", service_nt_main_fn },
638         { NULL, NULL }
639     };
640
641     if (!StartServiceCtrlDispatcher(dispatchTable))
642     {
643         /* This is a genuine failure of the SCM. */
644         rv = apr_get_os_error();
645         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
646                      "Error starting service control dispatcher");
647     }
648
649     globdat.service_thread_id = 0;
650     return (rv);
651 }
652
653
654 apr_status_t mpm_service_set_name(apr_pool_t *p, const char *name)
655 {
656     char *key_name;
657     
658     service_name = apr_palloc(p, strlen(name) + 1);
659     apr_collapse_spaces((char*) service_name, name);
660     key_name = apr_psprintf(p, SERVICECONFIG, service_name);
661     if (ap_registry_get_value(p, key_name, "DisplayName", (char**)&display_name) == APR_SUCCESS)
662         return APR_SUCCESS;
663
664     /* Take the given literal name if there is no service entry */
665     display_name = apr_pstrdup(p, name);
666     return APR_ENOFILE;
667 }
668
669
670 apr_status_t mpm_merge_service_args(apr_pool_t *p, 
671                                    apr_array_header_t *args, 
672                                    int fixed_args)
673 {
674     apr_array_header_t *svc_args = NULL;
675     char conf_key[MAX_PATH];
676     const char **cmb_data;
677     apr_status_t rv;
678
679     apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, service_name);
680     rv = ap_registry_get_array(p, conf_key, "ConfigArgs", &svc_args);
681     if (rv != APR_SUCCESS) {
682         if (rv == ERROR_FILE_NOT_FOUND) {
683             ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, NULL,
684                          "No ConfigArgs registered for %s, perhaps "
685                          "this service is not installed?", 
686                          service_name);
687             return APR_SUCCESS;
688         }
689         else
690             return (rv);        
691     }
692
693     if (!svc_args || svc_args->nelts == 0) {
694         return (APR_SUCCESS);
695     }
696
697     /* Now we have the service_name arg, and the mpm_runservice_nt()
698      * call appended the arguments passed by StartService(), so it's  
699      * time to _prepend_ the default arguments for the server from 
700      * the service's default arguments (all others override them)...
701      */
702     args->nalloc = args->nelts + svc_args->nelts;
703     cmb_data = apr_palloc(p, args->nalloc * sizeof(const char *));
704
705     /* First three args (argv[0], -f, path) remain first */
706     memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
707     
708     /* Service args follow from service registry array */
709     memcpy(cmb_data + fixed_args, svc_args->elts, 
710            svc_args->elt_size * svc_args->nelts);
711     
712     /* Remaining new args follow  */
713     memcpy(cmb_data + fixed_args + svc_args->nelts,
714            (const char **)args->elts + fixed_args, 
715            args->elt_size * (args->nelts - fixed_args));
716     
717     args->elts = (char *)cmb_data;
718     args->nelts = args->nalloc;
719
720     return APR_SUCCESS;
721 }
722
723
724 void service_stopped(void)
725 {
726     /* Still have a thread & window to clean up, so signal now */
727     if (globdat.service_thread_id)
728     {
729         if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
730         {
731             ReportStatusToSCMgr(SERVICE_STOPPED,    // service state
732                                 NO_ERROR,           // exit code
733                                 0);                 // wait hint
734
735             /* Cause the service_nt_main_fn to complete */
736             SetEvent(globdat.signal_monitor);
737         }
738         else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
739         {
740             RegisterServiceProcess(0, 0);
741             PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0);
742         }
743
744         WaitForSingleObject(globdat.service_thread, 5000);
745         CloseHandle(globdat.service_thread);
746     }
747 }
748
749
750 apr_status_t mpm_service_to_start(void)
751 {
752     HANDLE waitfor[2];
753
754     globdat.mpm_thread = GetCurrentThread();
755     
756     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
757     {
758         globdat.signal_monitor = CreateEvent(NULL, FALSE, FALSE, NULL);
759         if (globdat.signal_monitor)
760             globdat.service_thread = CreateThread(NULL, 0, 
761                                                   service_nt_dispatch_thread, 
762                                                   NULL, 0, 
763                                                   &globdat.service_thread_id);
764     }
765     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
766     {
767         if (!RegisterServiceProcess(0, 1)) 
768             return GetLastError();
769
770         globdat.signal_monitor = CreateEvent(NULL, FALSE, FALSE, NULL);
771         if (globdat.signal_monitor)
772             globdat.service_thread = CreateThread(NULL, 0,
773                                                   monitor_service_9x_thread, 
774                                                   (LPVOID) service_name, 0, 
775                                                   &globdat.service_thread_id);
776     }
777
778     if (globdat.signal_monitor && globdat.service_thread) 
779     {
780         waitfor[0] = globdat.signal_monitor;
781         waitfor[1] = globdat.service_thread;
782     
783         /* SetEvent(globdat.signal_monitor) to clean up the SCM thread */
784         if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
785             CloseHandle(globdat.service_thread);
786             return APR_ENOTHREAD;
787         }
788     }
789
790     if (globdat.service_thread_id)
791         atexit(service_stopped);
792     else if (globdat.service_thread)
793         CloseHandle(globdat.service_thread);
794
795     return APR_SUCCESS;
796 }
797
798
799 apr_status_t mpm_service_started(void)
800 {
801     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
802     {
803         ReportStatusToSCMgr(SERVICE_RUNNING,    // service state
804                             NO_ERROR,           // exit code
805                             0);                 // wait hint
806     }
807     return APR_SUCCESS;
808 }
809
810
811 void mpm_service_stopping(void)
812 {
813     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
814         ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state
815                             NO_ERROR,             // exit code
816                             3000);                // wait hint
817 }
818
819
820 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, 
821                                  const char * const * argv)
822 {
823     char key_name[MAX_PATH];
824     char exe_path[MAX_PATH];
825     char *launch_cmd;
826     apr_status_t(rv);
827     
828     printf("Installing the %s service\n", display_name);
829
830     if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
831     {
832         apr_status_t rv = apr_get_os_error();
833         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
834                      "GetModuleFileName failed");
835         return rv;
836     }
837
838     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
839     {
840         SC_HANDLE   schService;
841         SC_HANDLE   schSCManager;
842     
843         // TODO: Determine the minimum permissions required for security
844         schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
845                                      SC_MANAGER_ALL_ACCESS);
846         if (!schSCManager) {
847             rv = apr_get_os_error();
848             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
849                          "Failed to open the WinNT service manager");
850             return (rv);
851         }
852
853         launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
854
855         /* RPCSS is the Remote Procedure Call (RPC) Locator required for DCOM 
856          * communication pipes.  I am far from convinced we should add this to
857          * the default service dependencies, but be warned that future apache 
858          * modules or ISAPI dll's may depend on it.
859          */
860         schService = CreateService(schSCManager,         // SCManager database
861                                    service_name,         // name of service
862                                    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
873
874         if (!schService) 
875         {
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);
880             return (rv);
881         }
882
883         CloseServiceHandle(schService);
884         CloseServiceHandle(schSCManager);
885     }
886     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
887     {
888         /* Store the launch command in the registry */
889         launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice", 
890                                  exe_path, service_name);
891         rv = ap_registry_store_value(SERVICECONFIG9X, service_name, launch_cmd);
892         if (rv != APR_SUCCESS) {
893             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
894                          "%s: Failed to add the RunServices registry entry.", 
895                          display_name);
896             return (rv);
897         }
898
899         apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, service_name);
900         rv = ap_registry_store_value(key_name, "DisplayName", display_name);
901         if (rv != APR_SUCCESS) {
902             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
903                          "%s: Failed to store DisplayName in the registry.", 
904                          display_name);
905             return (rv);
906         }
907     }
908
909     /* For both WinNT & Win9x store the service ConfigArgs in the registry...
910      */
911     apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, service_name);
912     rv = ap_registry_store_array(ptemp, key_name, "ConfigArgs", argc, argv);
913     if (rv != APR_SUCCESS) {
914         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
915                      "%s: Failed to store the ConfigArgs in the registry.", 
916                      display_name);
917         return (rv);
918     }
919     printf("The %s service is successfully installed.\n", display_name);
920 }
921
922
923 apr_status_t mpm_service_uninstall(void)
924 {
925     char key_name[MAX_PATH];
926     apr_status_t rv;
927
928     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
929     {
930         SC_HANDLE schService;
931         SC_HANDLE schSCManager;
932
933         printf("Removing the %s service\n", display_name);
934
935         // TODO: Determine the minimum permissions required for security
936         schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
937                                      SC_MANAGER_ALL_ACCESS);
938         if (!schSCManager) {
939             rv = apr_get_os_error();
940             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
941                          "Failed to open the WinNT service manager.");
942             return (rv);
943         }
944         
945         schService = OpenService(schSCManager, service_name, SERVICE_ALL_ACCESS);
946
947         if (!schService) {
948            rv = apr_get_os_error();
949            ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
950                         "%s: OpenService failed", display_name);
951            return (rv);
952         }
953         
954         /* assure the service is stopped before continuing
955          *
956          * This may be out of order... we might not be able to be
957          * granted all access if the service is running anyway.
958          *
959          * And do we want to make it *this easy* for them
960          * to uninstall their service unintentionally?
961          */
962         // ap_stop_service(schService);
963
964         if (DeleteService(schService) == 0) {
965             rv = apr_get_os_error();
966             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
967                          "%s: Failed to delete the service.", display_name);
968             return (rv);
969         }
970         
971         CloseServiceHandle(schService);        
972         CloseServiceHandle(schSCManager);
973     }
974     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
975     {
976         printf("Removing the %s service\n", display_name);
977
978         /* TODO: assure the service is stopped before continuing */
979
980         if (ap_registry_delete_value(SERVICECONFIG9X, service_name)) {
981             rv = apr_get_os_error();
982             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
983                          "%s: Failed to remove the RunServices registry "
984                          "entry.", display_name);
985             return (rv);
986         }
987         
988         /* we blast Services/us, not just the Services/us/Parameters branch */
989         apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, service_name);
990         if (ap_registry_delete_key(key_name)) 
991         {
992             rv = apr_get_os_error();
993             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
994                          "%s: Failed to remove the service config from the "
995                          "registry.", display_name);
996             return (rv);
997         }
998     }
999     printf("The %s service has been removed successfully.\n", display_name);
1000     return APR_SUCCESS;
1001 }
1002
1003
1004 /* signal_service_transition is a simple thunk to signal the service
1005  * and monitor it's successful transition.  If the signal passed is 0,
1006  * then the caller is assumed to already have performed some service 
1007  * operation to be monitored (such as StartService), and no actual
1008  * ControlService signal is sent.
1009  */
1010
1011 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
1012 {
1013     if (signal && !ControlService(schService, signal, &globdat.ssStatus)) 
1014         return FALSE;
1015     
1016     do {
1017         Sleep(1000);    
1018         if (!QueryServiceStatus(schService, &globdat.ssStatus))
1019             return FALSE;
1020     } while (globdat.ssStatus.dwCurrentState == pending);
1021         
1022     return (globdat.ssStatus.dwCurrentState == complete);
1023 }
1024
1025
1026 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, 
1027                                const char * const * argv)
1028 {
1029     apr_status_t rv;
1030     
1031     printf("Starting the %s service\n", display_name);
1032
1033     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1034     {
1035         const char **start_argv;
1036         SC_HANDLE   schService;
1037         SC_HANDLE   schSCManager;
1038
1039         // TODO: Determine the minimum permissions required for security
1040         schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1041                                      SC_MANAGER_ALL_ACCESS);
1042         if (!schSCManager) {
1043             rv = apr_get_os_error();
1044             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1045                          "Failed to open the WinNT service manager");
1046             return (rv);
1047         }
1048
1049         schService = OpenService(schSCManager, service_name, 
1050                                  SERVICE_START | SERVICE_QUERY_STATUS);
1051         if (!schService) {
1052             rv = apr_get_os_error();
1053             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1054                          "%s: Failed to open the service.", display_name);
1055             CloseServiceHandle(schSCManager);
1056             return (rv);
1057         }
1058
1059         if (QueryServiceStatus(schService, &globdat.ssStatus)
1060             && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
1061             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1062                          "Service %s is already started!", display_name);
1063             CloseServiceHandle(schService);
1064             CloseServiceHandle(schSCManager);
1065             return 0;
1066         }
1067         
1068         argc += 1;
1069         start_argv = apr_palloc(ptemp, argc * sizeof(const char **));
1070         start_argv[0] = service_name;
1071         if (argc > 1)
1072             memcpy(start_argv + 1, argv, (argc - 1) * sizeof(const char **));
1073         
1074         rv = APR_EINIT;
1075         if (StartService(schService, argc, start_argv)
1076             && signal_service_transition(schService, 0, /* test only */
1077                                          SERVICE_START_PENDING, 
1078                                          SERVICE_RUNNING))
1079                 rv = APR_SUCCESS;
1080
1081         if (rv != APR_SUCCESS)
1082             rv = apr_get_os_error();
1083         
1084         CloseServiceHandle(schService);
1085         CloseServiceHandle(schSCManager);
1086     }
1087     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1088     {
1089         STARTUPINFO si;           /* Filled in prior to call to CreateProcess */
1090         PROCESS_INFORMATION pi;   /* filled in on call to CreateProcess */
1091         char exe_path[MAX_PATH];
1092         char *pCommand;
1093         int i;
1094
1095         /* Locate the active top level window named service_name
1096          * provided the class is ApacheWin95ServiceMonitor
1097          */
1098         if (FindWindow("ApacheWin95ServiceMonitor", service_name)) {
1099             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1100                          "Service %s is already started!", display_name);
1101             return 0;
1102         }
1103
1104         /* This may not appear intuitive, but Win9x will not allow a process
1105          * to detach from the console without releasing the entire console.
1106          * Ergo, we must spawn a new process for the service to get back our
1107          * console window.
1108          * The config is pre-flighted, so there should be no danger of failure.
1109          */
1110         
1111         if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
1112         {
1113             apr_status_t rv = apr_get_os_error();
1114             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1115                          "GetModuleFileName failed");
1116             return rv;
1117         }
1118         
1119         pCommand = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice", 
1120                                exe_path, service_name);  
1121         for (i = 0; i < argc; ++i) {
1122             pCommand = apr_pstrcat(ptemp, pCommand,
1123                                    " \"", argv[i], "\"", NULL);
1124         }
1125         
1126         memset(&si, 0, sizeof(si));
1127         memset(&pi, 0, sizeof(pi));
1128         si.cb = sizeof(si);
1129         si.dwFlags     = STARTF_USESHOWWINDOW;
1130         si.wShowWindow = SW_HIDE;   /* This might be redundant */
1131         
1132         rv = APR_EINIT;
1133         if (CreateProcess(NULL, pCommand, NULL, NULL, FALSE, 
1134                            DETACHED_PROCESS, /* Creation flags */
1135                            NULL, NULL, &si, &pi)) 
1136         {
1137             DWORD code;
1138             while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) {
1139                 if (FindWindow("ApacheWin95ServiceMonitor", service_name)) {
1140                     rv = APR_SUCCESS;
1141                     break;
1142                 }
1143                 Sleep (1000);
1144             }
1145         }
1146         
1147         if (rv != APR_SUCCESS)
1148             rv = apr_get_os_error();
1149         
1150         CloseHandle(pi.hProcess);
1151         CloseHandle(pi.hThread);
1152     }    
1153
1154     if (rv == APR_SUCCESS)
1155         printf("The %s service is running.\n", display_name);
1156     else
1157         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
1158                      "%s: Failed to start the service process.",
1159                      display_name);
1160         
1161     return rv;
1162 }
1163
1164
1165 /* signal is zero to stop, non-zero for restart */
1166
1167 void mpm_signal_service(apr_pool_t *ptemp, int signal)
1168 {
1169     int success = FALSE;
1170     
1171     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) 
1172     {
1173         SC_HANDLE   schService;
1174         SC_HANDLE   schSCManager;
1175
1176         schSCManager = OpenSCManager(NULL, NULL, // default machine & database
1177                                      SC_MANAGER_ALL_ACCESS);
1178         
1179         if (!schSCManager) {
1180             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1181                          "Failed to open the NT Service Manager");
1182             return;
1183         }
1184         
1185         schService = OpenService(schSCManager, service_name, 
1186                                  SERVICE_ALL_ACCESS);
1187
1188         if (schService == NULL) {
1189             /* Could not open the service */
1190             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1191                          "Failed to open the %s Service", display_name);
1192             CloseServiceHandle(schSCManager);
1193             return;
1194         }
1195         
1196         if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
1197             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1198                          "Query of Service %s failed", display_name);
1199             CloseServiceHandle(schService);
1200             CloseServiceHandle(schSCManager);
1201             return;
1202         }
1203
1204         if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
1205             printf("The %s service is not started.\n", display_name);
1206             CloseServiceHandle(schService);
1207             CloseServiceHandle(schSCManager);
1208             return;
1209         }
1210         
1211         printf("The %s service is %s.\n", display_name, 
1212                signal ? "restarting" : "stopping");
1213
1214         if (!signal)
1215             success = signal_service_transition(schService, 
1216                                                 SERVICE_CONTROL_STOP, 
1217                                                 SERVICE_STOP_PENDING, 
1218                                                 SERVICE_STOPPED);
1219         else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1220             mpm_service_start(ptemp, 0, NULL);
1221             CloseServiceHandle(schService);
1222             CloseServiceHandle(schSCManager);
1223             return;
1224         }
1225         else
1226             success = signal_service_transition(schService, 
1227                                                 SERVICE_APACHE_RESTART, 
1228                                                 SERVICE_START_PENDING, 
1229                                                 SERVICE_RUNNING);
1230
1231         CloseServiceHandle(schService);
1232         CloseServiceHandle(schSCManager);
1233     }
1234     else /* !isWindowsNT() */
1235     {
1236         DWORD       service_pid;
1237         HANDLE      hwnd;
1238         char prefix[20];
1239         /* Locate the active top level window named service_name
1240          * provided the class is ApacheWin95ServiceMonitor
1241          */
1242         hwnd = FindWindow("ApacheWin95ServiceMonitor", service_name);
1243         if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
1244             globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
1245         else
1246         {
1247             globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
1248             if (!signal) {
1249                 printf("The %s service is not started.\n", display_name);
1250                 return;
1251             }
1252         }
1253
1254         printf("The %s service is %s.\n", display_name, 
1255                signal ? "restarting" : "stopping");
1256
1257         apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
1258         setup_signal_names(prefix);
1259
1260         if (!signal) 
1261         {
1262             int ticks = 60;
1263             ap_start_shutdown();
1264             while (--ticks)
1265             {
1266                 if (!IsWindow(hwnd)) {
1267                     success = TRUE;
1268                     break;
1269                 }
1270                 Sleep(1000);
1271             }
1272         }
1273         else /* !stop */
1274         {   
1275             /* TODO: Aught to add a little test to the restart logic, and
1276              * store the restart counter in the window's user dword.
1277              * Then we can hang on and report a successful restart.  But
1278              * that's a project for another day.
1279              */
1280             if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1281                 mpm_service_start(ptemp, 0, NULL);
1282                 return;
1283             }
1284             else {
1285                 success = TRUE;
1286                 ap_start_restart(1);
1287             }
1288         }
1289     }
1290
1291     if (success)
1292         printf("The %s service has %s.\n", display_name, 
1293                signal ? "restarted" : "stopped");
1294     else
1295         printf("Failed to %s the %s service.\n", 
1296                signal ? "restart" : "stop", display_name);
1297 }