]> granicus.if.org Git - apache/blob - server/mpm/winnt/service.c
Update our copyright for this year.
[apache] / server / mpm / winnt / service.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2002 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 service_init;     /* controller thread init mutex */
88     HANDLE service_term;     /* NT service thread kill signal */
89     SERVICE_STATUS ssStatus;
90     SERVICE_STATUS_HANDLE hServiceStatus;
91 } globdat;
92
93 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
94
95 /* The service configuration's is stored under the following trees:
96  *
97  * HKLM\System\CurrentControlSet\Services\[service name]
98  *
99  *     \DisplayName
100  *     \ImagePath
101  *     \Parameters\ConfigArgs
102  *
103  * For Win9x, the launch service command is stored under:
104  *
105  * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name]
106  */
107
108
109 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us 
110  * to catch the non-zero conditions and inform the console process that
111  * the application died, and hang on to the console a bit longer.
112  *
113  * The macro only maps for http_main.c and other sources that include
114  * the service.h header, so we best assume it's an error to exit from
115  * _any_ other module.
116  *
117  * If real_exit_code is reset to 0, it will not be set or trigger this
118  * behavior on exit.  All service and child processes are expected to
119  * reset this flag to zero to avoid undesireable side effects.
120  */
121 int real_exit_code = 1;
122
123 void hold_console_open_on_error(void)
124 {
125     HANDLE hConIn;
126     HANDLE hConErr;
127     DWORD result;
128     time_t start;
129     time_t remains;
130     char *msg = "Note the errors or messages above, "
131                 "and press the <ESC> key to exit.  ";
132     CONSOLE_SCREEN_BUFFER_INFO coninfo;
133     INPUT_RECORD in;
134     char count[16];
135     
136     if (!real_exit_code)
137         return;
138     hConIn = GetStdHandle(STD_INPUT_HANDLE);
139     hConErr = GetStdHandle(STD_ERROR_HANDLE);
140     if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
141         return;
142     if (!WriteConsole(hConErr, msg, strlen(msg), &result, NULL) || !result)
143         return;
144     if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
145         return;
146     if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
147         return;
148         
149     start = time(NULL);
150     do
151     {
152         while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
153         {
154             if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
155                 return;
156             if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown 
157                     && (in.Event.KeyEvent.uChar.AsciiChar == 27))
158                 return;
159             if (in.EventType == MOUSE_EVENT 
160                     && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
161                 return;
162         }
163         remains = ((start + 30) - time(NULL)); 
164         sprintf (count, "%d...", remains);
165         if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
166             return;
167         if (!WriteConsole(hConErr, count, strlen(count), &result, NULL) 
168                 || !result)
169             return;
170     }
171     while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
172 }
173
174 static BOOL  die_on_logoff = FALSE;
175
176 static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg, 
177                                                 WPARAM wParam, LPARAM lParam)
178 {
179 /* This is the WndProc procedure for our invisible window.
180  * When the user shuts down the system, this window is sent
181  * a signal WM_ENDSESSION. We clean up by signaling Apache
182  * to shut down, and idle until Apache's primary thread quits.
183  */
184     if ((msg == WM_ENDSESSION) 
185             && (die_on_logoff || (lParam != ENDSESSION_LOGOFF)))
186     {
187         ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
188         if (wParam)
189             /* Don't leave this message until we are dead! */
190             WaitForSingleObject(globdat.mpm_thread, 30000);
191         return 0;
192     }
193     return (DefWindowProc(hWnd, msg, wParam, lParam));
194 }
195
196 static DWORD WINAPI monitor_service_9x_thread(void *service_name)
197 {
198     /* When running as a service under Windows 9x, there is no console
199      * window present, and no ConsoleCtrlHandler to call when the system 
200      * is shutdown.  If the WatchWindow thread is created with a NULL
201      * service_name argument, then the ...SystemMonitor window class is
202      * used to create the "Apache" window to watch for logoff and shutdown.
203      * If the service_name is provided, the ...ServiceMonitor window class
204      * is used to create the window named by the service_name argument,
205      * and the logoff message is ignored.
206      */
207     WNDCLASS wc;
208     HWND hwndMain;
209     MSG msg;
210     
211     wc.style         = CS_GLOBALCLASS;
212     wc.lpfnWndProc   = monitor_service_9x_proc; 
213     wc.cbClsExtra    = 0;
214     wc.cbWndExtra    = 0; 
215     wc.hInstance     = NULL;
216     wc.hIcon         = NULL;
217     wc.hCursor       = NULL;
218     wc.hbrBackground = NULL;
219     wc.lpszMenuName  = NULL;
220     if (service_name)
221         wc.lpszClassName = "ApacheWin95ServiceMonitor";
222     else
223         wc.lpszClassName = "ApacheWin95SystemMonitor";
224  
225     die_on_logoff = service_name ? FALSE : TRUE;
226
227     if (!RegisterClass(&wc)) 
228     {
229         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), 
230                      NULL, "Could not register window class for WatchWindow");
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         globdat.service_thread_id = 0;
247         return 0;
248     }
249
250     /* If we succeed, eliminate the console window.
251      * Signal the parent we are all set up, and
252      * watch the message queue while the window lives.
253      */
254     FreeConsole();
255     SetEvent(globdat.service_init);
256
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             ap_signal_parent(SIGNAL_PARENT_RESTART);
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             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
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             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
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 /* Set the service description regardless of platform.
410  * We revert to set_service_description on NT/9x, the
411  * very long way so any Apache management program can grab the
412  * description.  This would be bad on Win2000, since it wouldn't
413  * notify the service control manager of the name change.
414  */
415
416 /* Windows 2000 alone supports ChangeServiceConfig2 in order to
417  * register our server_version string... so we need some fixups
418  * to avoid binding to that function if we are on WinNT/9x.
419  */
420 static void set_service_description(void)
421 {
422     const char *full_description;
423     SC_HANDLE schSCManager;
424     BOOL ret = 0;
425
426     /* Nothing to do if we are a console
427      */
428     if (!mpm_service_name)
429         return;
430
431     /* Time to fix up the description, upon each successful restart
432      */
433     full_description = ap_get_server_version();
434
435     if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT) 
436           && (osver.dwMajorVersion > 4) 
437           && (ChangeServiceConfig2)
438           && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)))
439     {    
440         SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
441                                                SERVICE_ALL_ACCESS);
442         if (schService) {
443             /* Cast is necessary, ChangeServiceConfig2 handles multiple
444              * object types, some volatile, some not.
445              */
446             if (ChangeServiceConfig2(schService,
447                                      1 /* SERVICE_CONFIG_DESCRIPTION */,
448                                      (LPVOID) &full_description)) {
449                 full_description = NULL;
450             }
451             CloseServiceHandle(schService);
452         }
453         CloseServiceHandle(schSCManager);
454     }
455
456     if (full_description) 
457     {
458         char szPath[MAX_PATH];
459         HKEY hkey;
460
461         /* Create/Find the Service key that Monitor Applications iterate */
462         apr_snprintf(szPath, sizeof(szPath), 
463                      "SYSTEM\\CurrentControlSet\\Services\\%s", 
464                      mpm_service_name);
465         if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szPath, 0, KEY_SET_VALUE, &hkey) 
466                 != ERROR_SUCCESS) {
467             return;
468         }
469
470         /* Attempt to set the Description value for our service */
471         RegSetValueEx(hkey, "Description", 0, REG_SZ,  
472                       (unsigned char *) full_description, 
473                       strlen(full_description) + 1);
474         RegCloseKey(hkey);
475     }
476 }
477
478 /* handle the SCM's ControlService() callbacks to our service */
479
480 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
481 {
482     if (dwCtrlCode == SERVICE_CONTROL_STOP)
483     {
484         ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
485         globdat.ssStatus.dwCurrentState = SERVICE_STOP_PENDING;
486         ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 3000);
487         return;
488     }
489     if (dwCtrlCode == SERVICE_APACHE_RESTART)
490     {
491         ap_signal_parent(SIGNAL_PARENT_RESTART);
492         globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
493         ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 3000);
494         return;
495     }
496     
497     ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);            
498 }
499
500
501 long __stdcall service_stderr_thread(LPVOID hPipe)
502 {
503     HANDLE hPipeRead = (HANDLE) hPipe;
504     HANDLE hEventSource;
505     char errbuf[256], *errread = errbuf;
506     const char *errarg[9];
507     DWORD errres;
508     HKEY hk;
509     
510     errarg[0] = "The Apache service named";
511     errarg[1] = mpm_display_name;
512     errarg[2] = "reported the following error:\r\n>>>";
513     errarg[3] = errbuf;
514     errarg[4] = NULL;
515     errarg[5] = NULL;
516     errarg[6] = NULL;
517     errarg[7] = NULL;
518     errarg[8] = NULL;
519     
520     /* What are we going to do in here, bail on the user?  not. */
521     if (!RegCreateKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services"
522                       "\\EventLog\\Application\\Apache Service", &hk)) 
523     {
524         /* The stock message file */
525         char *netmsgkey = "%SystemRoot%\\System32\\netmsg.dll";
526         DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | 
527                        EVENTLOG_INFORMATION_TYPE; 
528  
529         RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ,
530                           (LPBYTE) netmsgkey, strlen(netmsgkey) + 1);
531         
532         RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD,
533                           (LPBYTE) &dwData, sizeof(dwData));
534         RegCloseKey(hk);
535     }
536
537     hEventSource = RegisterEventSource(NULL, "Apache Service");
538
539     while (ReadFile(hPipeRead, errread, 
540                     sizeof(errbuf) - (errread - errbuf) - 1, &errres, NULL))
541     {
542         if (errres)
543         {
544             /* NULL terminate */
545             errread[errres] = '\0';
546
547             /* Process complete lines */
548             while (*errread) 
549             {
550                 char *erreol;
551                 int errlen;
552
553                 /* Trim leading whitespace */
554                 errread = errbuf;
555                 while (apr_isspace(*errread)) {
556                     ++errread;
557                 }
558                 if (!*errread) {
559                     errread = errbuf;
560                     *errread = '\0';
561                     continue;
562                 }
563                 /* Find eol, but only re-Read if the buffer is unfilled */
564                 erreol = strchr(errread, '\n');
565                 if (!erreol && (errread > errbuf)) {
566                     errlen = strlen(errbuf);
567                     memmove(errbuf, errread, errlen + 1);
568                     errread = errbuf + errlen;
569                     continue;
570                 }
571                 *erreol = '\0';
572
573                 /* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9'
574                  * The event code in netmsg.dll is 3299
575                  */
576                 ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, 
577                             3299, NULL, 9, 0, errarg, NULL);
578                 
579                 if (!erreol) {
580                     errread = errbuf;
581                     *errread = '\0';
582                     continue;
583                 }
584                 errread = erreol + 1;
585             }
586         }
587     }
588
589     if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) {
590         apr_snprintf(errbuf, sizeof(errbuf),
591                      "Win32 error %d reading stderr pipe stream\r\n", 
592                      GetLastError());
593
594         ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, 
595                     3299, NULL, 9, 0, errarg, NULL);
596     }
597     CloseHandle(hPipeRead);
598     return 0;
599 }
600
601
602 /* service_nt_main_fn is outside of the call stack and outside of the
603  * primary server thread... so now we _really_ need a placeholder!
604  * The winnt_rewrite_args has created and shared mpm_new_argv with us.
605  */
606 extern apr_array_header_t *mpm_new_argv;
607
608 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
609 {
610     HANDLE waitfor[2];
611     HANDLE hPipeRead = NULL;
612     HANDLE hPipeWrite = NULL;
613     HANDLE hDup;
614     HANDLE thread;
615     DWORD  threadid;
616     const char *ignored;
617
618     /* args and service names live in the same pool */
619     mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
620
621     globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
622     globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
623     globdat.ssStatus.dwServiceSpecificExitCode = 0;
624     globdat.ssStatus.dwCheckPoint = 1;
625
626     if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
627     {
628         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), 
629                      NULL, "Failure registering service handler");
630         return;
631     }
632     /* Report status, no errors, and buy 3 more seconds */
633     ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 3000);
634     
635     /* Create a pipe to send stderr messages to the system error log 
636      */
637     if (CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0)) 
638     {
639         HANDLE hProc = GetCurrentProcess();
640         if (DuplicateHandle(hProc, hPipeWrite, hProc, &hDup,
641                             0, TRUE, GENERIC_WRITE))
642         {
643             CloseHandle(hPipeWrite);
644             hPipeWrite = hDup;
645             thread = (HANDLE) _beginthreadex(NULL, 0, service_stderr_thread, 
646                                              (LPVOID) hPipeRead, 0, &threadid);
647             if (thread)
648             {
649                 FILE *fl, flip;
650                 int fd;
651
652                 CloseHandle(thread);
653         
654                 /* Flush, commit and close stderr.  This is typically a noop
655                  * in Win2K/XP since services start with NULL std handles,
656                  * but is required for NT 4.0 and a decent saftey anyways.
657                  */
658                 fflush(stderr);
659                 _commit(2 /* stderr */);
660                 fclose(stderr);
661
662                 /* The fdopen mode "wcb" is write, binary, so that simple 
663                  * strings are not buffered for \n -> crlf munging, and
664                  * commit-on-write.  Used setvbuf to assure no buffering.
665                  */
666                 if (((fd = _open_osfhandle((long) hPipeWrite, 
667                                            _O_WRONLY | _O_BINARY) != -1)
668                         && (dup2(fd, 2 /* stderr */) == 0)
669                         && ((fl = _fdopen(2 /* stderr */, "wcb")) != NULL))) {
670                     _close(fd);
671                     flip = *stderr;
672                     *stderr = *fl;
673                     *fl = flip;
674                     setvbuf(stderr, NULL, _IONBF, 0);
675                 }
676
677                 /* The code above _will_ corrupt the StdHandle... 
678                  * and we must do so anyways.  We set this up only
679                  * after we initialized the posix stderr API.
680                  */
681                 SetStdHandle(STD_ERROR_HANDLE, hPipeWrite);
682             }
683             else
684             {
685                 CloseHandle(hPipeRead);
686                 CloseHandle(hPipeWrite);
687                 hPipeWrite = NULL;
688             }            
689         }
690         else
691         {
692             CloseHandle(hPipeRead);
693             CloseHandle(hPipeWrite);
694             hPipeWrite = NULL;
695         }            
696     }
697
698     /* We need to append all the command arguments passed via StartService() 
699      * to our running service... which just got here via the SCM...
700      * but we hvae no interest in argv[0] for the mpm_new_argv list.
701      */
702     if (argc > 1) 
703     {
704         char **cmb_data;
705
706         mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
707         cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
708
709         /* mpm_new_argv remains first (of lower significance) */
710         memcpy (cmb_data, mpm_new_argv->elts, 
711                 mpm_new_argv->elt_size * mpm_new_argv->nelts);
712         
713         /* Service args follow from StartService() invocation */
714         memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, 
715                 mpm_new_argv->elt_size * (argc - 1));
716         
717         /* The replacement arg list is complete */
718         mpm_new_argv->elts = (char *)cmb_data;
719         mpm_new_argv->nelts = mpm_new_argv->nalloc;
720     }
721
722     /* Let the main thread continue now... but hang on to the
723      * signal_monitor event so we can take further action
724      */
725     SetEvent(globdat.service_init);
726
727     waitfor[0] = globdat.service_term;
728     waitfor[1] = globdat.mpm_thread;
729     WaitForMultipleObjects(2, waitfor, FALSE, INFINITE);
730
731     /* The process is ready to terminate, or already has */
732     CloseHandle(hPipeWrite);
733 }
734
735
736 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
737 {
738     apr_status_t rv = APR_SUCCESS;
739
740     SERVICE_TABLE_ENTRY dispatchTable[] =
741     {
742         { "", service_nt_main_fn },
743         { NULL, NULL }
744     };
745
746     if (!StartServiceCtrlDispatcher(dispatchTable))
747     {
748         /* This is a genuine failure of the SCM. */
749         rv = apr_get_os_error();
750         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
751                      "Error starting service control dispatcher");
752     }
753
754     globdat.service_thread_id = 0;
755     return (rv);
756 }
757
758
759 apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, 
760                                   const char *set_name)
761 {
762     char key_name[MAX_PATH];
763     apr_status_t rv;
764
765     /* ### Needs improvement, on Win2K the user can _easily_ 
766      * change the display name to a string that doesn't reflect 
767      * the internal service name + whitespace!
768      */
769     mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
770     apr_collapse_spaces((char*) mpm_service_name, set_name);
771     apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
772     rv = ap_registry_get_value(p, key_name, "DisplayName", &mpm_display_name);
773     if (rv != APR_SUCCESS) {
774         /* Take the given literal name if there is no service entry */
775         mpm_display_name = apr_pstrdup(p, set_name);
776     }
777     *display_name = mpm_display_name;
778     return rv;
779 }
780
781
782 apr_status_t mpm_merge_service_args(apr_pool_t *p, 
783                                    apr_array_header_t *args, 
784                                    int fixed_args)
785 {
786     apr_array_header_t *svc_args = NULL;
787     char conf_key[MAX_PATH];
788     char **cmb_data;
789     apr_status_t rv;
790
791     apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
792     rv = ap_registry_get_array(p, conf_key, "ConfigArgs", &svc_args);
793     if (rv != APR_SUCCESS) {
794         if (rv == ERROR_FILE_NOT_FOUND) {
795             ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, NULL,
796                          "No ConfigArgs registered for %s, perhaps "
797                          "this service is not installed?", 
798                          mpm_service_name);
799             return APR_SUCCESS;
800         }
801         else
802             return (rv);        
803     }
804
805     if (!svc_args || svc_args->nelts == 0) {
806         return (APR_SUCCESS);
807     }
808
809     /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
810      * call appended the arguments passed by StartService(), so it's  
811      * time to _prepend_ the default arguments for the server from 
812      * the service's default arguments (all others override them)...
813      */
814     args->nalloc = args->nelts + svc_args->nelts;
815     cmb_data = malloc(args->nalloc * sizeof(const char *));
816
817     /* First three args (argv[0], -f, path) remain first */
818     memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
819     
820     /* Service args follow from service registry array */
821     memcpy(cmb_data + fixed_args, svc_args->elts, 
822            svc_args->elt_size * svc_args->nelts);
823     
824     /* Remaining new args follow  */
825     memcpy(cmb_data + fixed_args + svc_args->nelts,
826            (const char **)args->elts + fixed_args, 
827            args->elt_size * (args->nelts - fixed_args));
828     
829     args->elts = (char *)cmb_data;
830     args->nelts = args->nalloc;
831
832     return APR_SUCCESS;
833 }
834
835
836 void service_stopped(void)
837 {
838     /* Still have a thread & window to clean up, so signal now */
839     if (globdat.service_thread_id)
840     {
841         if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
842         {
843             ReportStatusToSCMgr(SERVICE_STOPPED,    // service state
844                                 NO_ERROR,           // exit code
845                                 0);                 // wait hint
846
847             /* Cause the service_nt_main_fn to complete */
848             SetEvent(globdat.service_term);
849         }
850         else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
851         {
852             RegisterServiceProcess(0, 0);
853             PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0);
854         }
855
856         WaitForSingleObject(globdat.service_thread, 5000);
857         CloseHandle(globdat.service_thread);
858     }
859 }
860
861
862 apr_status_t mpm_service_to_start(const char **display_name)
863 {
864     HANDLE hProc = GetCurrentProcess();
865     HANDLE hThread = GetCurrentThread();
866     HANDLE waitfor[2];
867
868     if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
869                          0, FALSE, DUPLICATE_SAME_ACCESS)) {
870         return APR_ENOTHREAD;
871     }
872     
873     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
874     {
875         globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
876         globdat.service_term = CreateEvent(NULL, FALSE, FALSE, NULL);
877         if (globdat.service_init)
878             globdat.service_thread = (HANDLE) _beginthreadex(NULL, 0, 
879                                                   service_nt_dispatch_thread, 
880                                                   NULL, 0, 
881                                                   &globdat.service_thread_id);
882     }
883     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
884     {
885         if (!RegisterServiceProcess(0, 1)) 
886             return GetLastError();
887
888         globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
889         if (globdat.service_init)
890             globdat.service_thread = (HANDLE) _beginthreadex(NULL, 0,
891                                                   monitor_service_9x_thread, 
892                                                   (LPVOID) mpm_service_name, 0,
893                                                   &globdat.service_thread_id);
894     }
895
896     if (globdat.service_init && globdat.service_thread) 
897     {
898         waitfor[0] = globdat.service_init;
899         waitfor[1] = globdat.service_thread;
900     
901         /* Wait for controlling thread init or termination */
902         if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
903             CloseHandle(globdat.service_thread);
904             CloseHandle(globdat.mpm_thread);
905             return APR_ENOTHREAD;
906         }
907     }
908
909     if (globdat.service_thread_id)
910         atexit(service_stopped);
911     else if (globdat.service_thread)
912         CloseHandle(globdat.service_thread);
913
914     *display_name = mpm_display_name; 
915     return APR_SUCCESS;
916 }
917
918
919 apr_status_t mpm_service_started(void)
920 {
921     set_service_description();
922     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
923     {
924         ReportStatusToSCMgr(SERVICE_RUNNING,    // service state
925                             NO_ERROR,           // exit code
926                             0);                 // wait hint
927     }
928     return APR_SUCCESS;
929 }
930
931
932 void mpm_service_stopping(void)
933 {
934     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
935         ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state
936                             NO_ERROR,             // exit code
937                             3000);                // wait hint
938 }
939
940
941 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, 
942                                  const char * const * argv)
943 {
944     char key_name[MAX_PATH];
945     char exe_path[MAX_PATH];
946     char *launch_cmd;
947     apr_status_t(rv);
948     
949     printf("Installing the %s service\n", mpm_display_name);
950
951     if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
952     {
953         apr_status_t rv = apr_get_os_error();
954         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
955                      "GetModuleFileName failed");
956         return rv;
957     }
958
959     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
960     {
961         SC_HANDLE   schService;
962         SC_HANDLE   schSCManager;
963     
964         // TODO: Determine the minimum permissions required for security
965         schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
966                                      SC_MANAGER_ALL_ACCESS);
967         if (!schSCManager) {
968             rv = apr_get_os_error();
969             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
970                          "Failed to open the WinNT service manager");
971             return (rv);
972         }
973
974         launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
975
976         /* RPCSS is the Remote Procedure Call (RPC) Locator required for DCOM 
977          * communication pipes.  I am far from convinced we should add this to
978          * the default service dependencies, but be warned that future apache 
979          * modules or ISAPI dll's may depend on it.
980          */
981         schService = CreateService(schSCManager,         // SCManager database
982                                    mpm_service_name,     // name of service
983                                    mpm_display_name,     // name to display
984                                    SERVICE_ALL_ACCESS,   // access required
985                                    SERVICE_WIN32_OWN_PROCESS,  // service type
986                                    SERVICE_AUTO_START,   // start type
987                                    SERVICE_ERROR_NORMAL, // error control type
988                                    launch_cmd,           // service's binary
989                                    NULL,                 // no load svc group
990                                    NULL,                 // no tag identifier
991                                    "Tcpip\0Afd\0",       // dependencies
992                                    NULL,                 // use SYSTEM account
993                                    NULL);                // no password
994
995         if (!schService) 
996         {
997             rv = apr_get_os_error();
998             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
999                          "Failed to create WinNT Service Profile");
1000             CloseServiceHandle(schSCManager);
1001             return (rv);
1002         }
1003
1004         CloseServiceHandle(schService);
1005         CloseServiceHandle(schSCManager);
1006     }
1007     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1008     {
1009         /* Store the launch command in the registry */
1010         launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice", 
1011                                  exe_path, mpm_service_name);
1012         rv = ap_registry_store_value(SERVICECONFIG9X, mpm_service_name, launch_cmd);
1013         if (rv != APR_SUCCESS) {
1014             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
1015                          "%s: Failed to add the RunServices registry entry.", 
1016                          mpm_display_name);
1017             return (rv);
1018         }
1019
1020         apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
1021         rv = ap_registry_store_value(key_name, "ImagePath", launch_cmd);
1022         if (rv != APR_SUCCESS) {
1023             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
1024                          "%s: Failed to store ImagePath in the registry.", 
1025                          mpm_display_name);
1026             return (rv);
1027         }
1028         rv = ap_registry_store_value(key_name, "DisplayName", mpm_display_name);
1029         if (rv != APR_SUCCESS) {
1030             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
1031                          "%s: Failed to store DisplayName in the registry.", 
1032                          mpm_display_name);
1033             return (rv);
1034         }
1035     }
1036
1037     set_service_description();
1038
1039     /* For both WinNT & Win9x store the service ConfigArgs in the registry...
1040      */
1041     apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
1042     rv = ap_registry_store_array(ptemp, key_name, "ConfigArgs", argc, argv);
1043     if (rv != APR_SUCCESS) {
1044         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, 
1045                      "%s: Failed to store the ConfigArgs in the registry.", 
1046                      mpm_display_name);
1047         return (rv);
1048     }
1049     printf("The %s service is successfully installed.\n", mpm_display_name);
1050     return APR_SUCCESS;
1051 }
1052
1053
1054 apr_status_t mpm_service_uninstall(void)
1055 {
1056     char key_name[MAX_PATH];
1057     apr_status_t rv;
1058
1059     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1060     {
1061         SC_HANDLE schService;
1062         SC_HANDLE schSCManager;
1063
1064         printf("Removing the %s service\n", mpm_display_name);
1065
1066         // TODO: Determine the minimum permissions required for security
1067         schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1068                                      SC_MANAGER_ALL_ACCESS);
1069         if (!schSCManager) {
1070             rv = apr_get_os_error();
1071             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1072                          "Failed to open the WinNT service manager.");
1073             return (rv);
1074         }
1075         
1076         schService = OpenService(schSCManager, mpm_service_name, SERVICE_ALL_ACCESS);
1077
1078         if (!schService) {
1079            rv = apr_get_os_error();
1080            ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1081                         "%s: OpenService failed", mpm_display_name);
1082            return (rv);
1083         }
1084         
1085         /* assure the service is stopped before continuing
1086          *
1087          * This may be out of order... we might not be able to be
1088          * granted all access if the service is running anyway.
1089          *
1090          * And do we want to make it *this easy* for them
1091          * to uninstall their service unintentionally?
1092          */
1093         // ap_stop_service(schService);
1094
1095         if (DeleteService(schService) == 0) {
1096             rv = apr_get_os_error();
1097             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1098                          "%s: Failed to delete the service.", mpm_display_name);
1099             return (rv);
1100         }
1101         
1102         CloseServiceHandle(schService);        
1103         CloseServiceHandle(schSCManager);
1104     }
1105     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1106     {
1107         printf("Removing the %s service\n", mpm_display_name);
1108
1109         /* TODO: assure the service is stopped before continuing */
1110
1111         if (ap_registry_delete_value(SERVICECONFIG9X, mpm_service_name)) {
1112             rv = apr_get_os_error();
1113             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1114                          "%s: Failed to remove the RunServices registry "
1115                          "entry.", mpm_display_name);
1116             return (rv);
1117         }
1118         
1119         /* we blast Services/us, not just the Services/us/Parameters branch */
1120         apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
1121         if (ap_registry_delete_key(key_name)) 
1122         {
1123             rv = apr_get_os_error();
1124             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1125                          "%s: Failed to remove the service config from the "
1126                          "registry.", mpm_display_name);
1127             return (rv);
1128         }
1129     }
1130     printf("The %s service has been removed successfully.\n", mpm_display_name);
1131     return APR_SUCCESS;
1132 }
1133
1134
1135 /* signal_service_transition is a simple thunk to signal the service
1136  * and monitor its successful transition.  If the signal passed is 0,
1137  * then the caller is assumed to already have performed some service 
1138  * operation to be monitored (such as StartService), and no actual
1139  * ControlService signal is sent.
1140  */
1141
1142 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
1143 {
1144     if (signal && !ControlService(schService, signal, &globdat.ssStatus)) 
1145         return FALSE;
1146     
1147     do {
1148         Sleep(1000);    
1149         if (!QueryServiceStatus(schService, &globdat.ssStatus))
1150             return FALSE;
1151     } while (globdat.ssStatus.dwCurrentState == pending);
1152         
1153     return (globdat.ssStatus.dwCurrentState == complete);
1154 }
1155
1156
1157 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, 
1158                                const char * const * argv)
1159 {
1160     apr_status_t rv;
1161     
1162     printf("Starting the %s service\n", mpm_display_name);
1163
1164     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
1165     {
1166         char **start_argv;
1167         SC_HANDLE   schService;
1168         SC_HANDLE   schSCManager;
1169
1170         // TODO: Determine the minimum permissions required for security
1171         schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
1172                                      SC_MANAGER_ALL_ACCESS);
1173         if (!schSCManager) {
1174             rv = apr_get_os_error();
1175             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1176                          "Failed to open the WinNT service manager");
1177             return (rv);
1178         }
1179
1180         schService = OpenService(schSCManager, mpm_service_name, 
1181                                  SERVICE_START | SERVICE_QUERY_STATUS);
1182         if (!schService) {
1183             rv = apr_get_os_error();
1184             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1185                          "%s: Failed to open the service.", mpm_display_name);
1186             CloseServiceHandle(schSCManager);
1187             return (rv);
1188         }
1189
1190         if (QueryServiceStatus(schService, &globdat.ssStatus)
1191             && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
1192             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1193                          "Service %s is already started!", mpm_display_name);
1194             CloseServiceHandle(schService);
1195             CloseServiceHandle(schSCManager);
1196             return 0;
1197         }
1198         
1199         argc += 1;
1200         start_argv = malloc(argc * sizeof(const char **));
1201         start_argv[0] = mpm_service_name;
1202         if (argc > 1)
1203             memcpy(start_argv + 1, argv, (argc - 1) * sizeof(const char **));
1204         
1205         rv = APR_EINIT;
1206         if (StartService(schService, argc, start_argv)
1207             && signal_service_transition(schService, 0, /* test only */
1208                                          SERVICE_START_PENDING, 
1209                                          SERVICE_RUNNING))
1210                 rv = APR_SUCCESS;
1211
1212         if (rv != APR_SUCCESS)
1213             rv = apr_get_os_error();
1214         
1215         CloseServiceHandle(schService);
1216         CloseServiceHandle(schSCManager);
1217     }
1218     else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */
1219     {
1220         STARTUPINFO si;           /* Filled in prior to call to CreateProcess */
1221         PROCESS_INFORMATION pi;   /* filled in on call to CreateProcess */
1222         char exe_path[MAX_PATH];
1223         char exe_cmd[MAX_PATH * 4];
1224         char *next_arg;
1225         int i;
1226
1227         /* Locate the active top level window named service_name
1228          * provided the class is ApacheWin95ServiceMonitor
1229          */
1230         if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1231             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
1232                          "Service %s is already started!", mpm_display_name);
1233             return 0;
1234         }
1235
1236         /* This may not appear intuitive, but Win9x will not allow a process
1237          * to detach from the console without releasing the entire console.
1238          * Ergo, we must spawn a new process for the service to get back our
1239          * console window.
1240          * The config is pre-flighted, so there should be no danger of failure.
1241          */
1242         
1243         if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
1244         {
1245             apr_status_t rv = apr_get_os_error();
1246             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
1247                          "GetModuleFileName failed");
1248             return rv;
1249         }
1250         
1251         apr_snprintf(exe_cmd, sizeof(exe_cmd), 
1252                      "\"%s\" -n %s -k runservice", 
1253                      exe_path, mpm_service_name);  
1254         next_arg = strchr(exe_cmd, '\0');
1255         for (i = 0; i < argc; ++i) {
1256             apr_snprintf(next_arg, sizeof(exe_cmd) - (next_arg - exe_cmd), 
1257                          " \"%s\"", argv[i]);
1258             next_arg = strchr(exe_cmd, '\0');
1259         }
1260         
1261         memset(&si, 0, sizeof(si));
1262         memset(&pi, 0, sizeof(pi));
1263         si.cb = sizeof(si);
1264         si.dwFlags     = STARTF_USESHOWWINDOW;
1265         si.wShowWindow = SW_HIDE;   /* This might be redundant */
1266         
1267         rv = APR_EINIT;
1268         if (CreateProcess(NULL, exe_cmd, NULL, NULL, FALSE, 
1269                            DETACHED_PROCESS, /* Creation flags */
1270                            NULL, NULL, &si, &pi)) 
1271         {
1272             DWORD code;
1273             while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) {
1274                 if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) {
1275                     rv = APR_SUCCESS;
1276                     break;
1277                 }
1278                 Sleep (1000);
1279             }
1280         }
1281         
1282         if (rv != APR_SUCCESS)
1283             rv = apr_get_os_error();
1284         
1285         CloseHandle(pi.hProcess);
1286         CloseHandle(pi.hThread);
1287     }    
1288
1289     if (rv == APR_SUCCESS)
1290         printf("The %s service is running.\n", mpm_display_name);
1291     else
1292         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
1293                      "%s: Failed to start the service process.",
1294                      mpm_display_name);
1295         
1296     return rv;
1297 }
1298
1299
1300 /* signal is zero to stop, non-zero for restart */
1301
1302 void mpm_signal_service(apr_pool_t *ptemp, int signal)
1303 {
1304     int success = FALSE;
1305     
1306     if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) 
1307     {
1308         SC_HANDLE   schService;
1309         SC_HANDLE   schSCManager;
1310
1311         schSCManager = OpenSCManager(NULL, NULL, // default machine & database
1312                                      SC_MANAGER_ALL_ACCESS);
1313         
1314         if (!schSCManager) {
1315             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1316                          "Failed to open the NT Service Manager");
1317             return;
1318         }
1319         
1320         schService = OpenService(schSCManager, mpm_service_name, 
1321                                  SERVICE_ALL_ACCESS);
1322
1323         if (schService == NULL) {
1324             /* Could not open the service */
1325             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1326                          "Failed to open the %s Service", mpm_display_name);
1327             CloseServiceHandle(schSCManager);
1328             return;
1329         }
1330         
1331         if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
1332             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
1333                          "Query of Service %s failed", mpm_display_name);
1334             CloseServiceHandle(schService);
1335             CloseServiceHandle(schSCManager);
1336             return;
1337         }
1338
1339         if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
1340             printf("The %s service is not started.\n", mpm_display_name);
1341             CloseServiceHandle(schService);
1342             CloseServiceHandle(schSCManager);
1343             return;
1344         }
1345         
1346         printf("The %s service is %s.\n", mpm_display_name, 
1347                signal ? "restarting" : "stopping");
1348
1349         if (!signal)
1350             success = signal_service_transition(schService, 
1351                                                 SERVICE_CONTROL_STOP, 
1352                                                 SERVICE_STOP_PENDING, 
1353                                                 SERVICE_STOPPED);
1354         else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1355             mpm_service_start(ptemp, 0, NULL);
1356             CloseServiceHandle(schService);
1357             CloseServiceHandle(schSCManager);
1358             return;
1359         }
1360         else
1361             success = signal_service_transition(schService, 
1362                                                 SERVICE_APACHE_RESTART, 
1363                                                 SERVICE_START_PENDING, 
1364                                                 SERVICE_RUNNING);
1365
1366         CloseServiceHandle(schService);
1367         CloseServiceHandle(schSCManager);
1368     }
1369     else /* !isWindowsNT() */
1370     {
1371         DWORD       service_pid;
1372         HANDLE      hwnd;
1373         char prefix[20];
1374         /* Locate the active top level window named service_name
1375          * provided the class is ApacheWin95ServiceMonitor
1376          */
1377         hwnd = FindWindow("ApacheWin95ServiceMonitor", mpm_service_name);
1378         if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
1379             globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
1380         else
1381         {
1382             globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
1383             if (!signal) {
1384                 printf("The %s service is not started.\n", mpm_display_name);
1385                 return;
1386             }
1387         }
1388
1389         printf("The %s service is %s.\n", mpm_display_name, 
1390                signal ? "restarting" : "stopping");
1391
1392         apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
1393         setup_signal_names(prefix);
1394
1395         if (!signal) 
1396         {
1397             int ticks = 60;
1398             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
1399             while (--ticks)
1400             {
1401                 if (!IsWindow(hwnd)) {
1402                     success = TRUE;
1403                     break;
1404                 }
1405                 Sleep(1000);
1406             }
1407         }
1408         else /* !stop */
1409         {   
1410             /* TODO: Aught to add a little test to the restart logic, and
1411              * store the restart counter in the window's user dword.
1412              * Then we can hang on and report a successful restart.  But
1413              * that's a project for another day.
1414              */
1415             if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
1416                 mpm_service_start(ptemp, 0, NULL);
1417                 return;
1418             }
1419             else {
1420                 success = TRUE;
1421                 ap_signal_parent(SIGNAL_PARENT_RESTART);
1422             }
1423         }
1424     }
1425
1426     if (success)
1427         printf("The %s service has %s.\n", mpm_display_name, 
1428                signal ? "restarted" : "stopped");
1429     else
1430         printf("Failed to %s the %s service.\n", 
1431                signal ? "restart" : "stop", mpm_display_name);
1432 }