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