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