]> granicus.if.org Git - apache/blob - server/mpm/winnt/service.c
Axe Win9x codepath, including Win32DisableAcceptEx logic. Starting clean.
[apache] / server / mpm / winnt / service.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* This module ALONE requires the window message API from user.h
18  * and the default APR include of windows.h will omit it, so
19  * preload the API symbols now...
20  */
21
22 #define _WINUSER_
23
24 #include "httpd.h"
25 #include "http_log.h"
26 #include "mpm_winnt.h"
27 #include "apr_strings.h"
28 #include "apr_lib.h"
29 #include "ap_regkey.h"
30
31 #ifdef NOUSER
32 #undef NOUSER
33 #endif
34 #undef _WINUSER_
35 #include <winuser.h>
36
37 static char *mpm_service_name = NULL;
38 static char *mpm_display_name = NULL;
39
40 static struct
41 {
42     HANDLE mpm_thread;       /* primary thread handle of the apache server */
43     HANDLE service_thread;   /* thread service/monitor handle */
44     DWORD  service_thread_id;/* thread service/monitor ID */
45     HANDLE service_init;     /* controller thread init mutex */
46     HANDLE service_term;     /* NT service thread kill signal */
47     SERVICE_STATUS ssStatus;
48     SERVICE_STATUS_HANDLE hServiceStatus;
49 } globdat;
50
51 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
52
53
54 #define PRODREGKEY "SOFTWARE\\" AP_SERVER_BASEVENDOR "\\" \
55                    AP_SERVER_BASEPRODUCT "\\" AP_SERVER_BASEREVISION
56
57 /*
58  * Get the server root from the registry into 'dir' which is
59  * size bytes long. Returns 0 if the server root was found
60  * or if the serverroot key does not exist (in which case
61  * dir will contain an empty string), or -1 if there was
62  * an error getting the key.
63  */
64 apr_status_t ap_registry_get_server_root(apr_pool_t *p, char **buf)
65 {
66     apr_status_t rv;
67     ap_regkey_t *key;
68
69     if ((rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, PRODREGKEY,
70                              APR_READ, p)) == APR_SUCCESS) {
71         rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
72         ap_regkey_close(key);
73         if (rv == APR_SUCCESS)
74             return rv;
75     }
76
77     if ((rv = ap_regkey_open(&key, AP_REGKEY_CURRENT_USER, PRODREGKEY,
78                              APR_READ, p)) == APR_SUCCESS) {
79         rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
80         ap_regkey_close(key);
81         if (rv == APR_SUCCESS)
82             return rv;
83     }
84
85     *buf = NULL;
86     return rv;
87 }
88
89
90 /* The service configuration's is stored under the following trees:
91  *
92  * HKLM\System\CurrentControlSet\Services\[service name]
93  *
94  *     \DisplayName
95  *     \ImagePath
96  *     \Parameters\ConfigArgs
97  */
98
99
100 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us
101  * to catch the non-zero conditions and inform the console process that
102  * the application died, and hang on to the console a bit longer.
103  *
104  * The macro only maps for http_main.c and other sources that include
105  * the service.h header, so we best assume it's an error to exit from
106  * _any_ other module.
107  *
108  * If ap_real_exit_code is reset to 0, it will not be set or trigger this
109  * behavior on exit.  All service and child processes are expected to
110  * reset this flag to zero to avoid undesireable side effects.
111  */
112 AP_DECLARE_DATA int ap_real_exit_code = 1;
113
114 void hold_console_open_on_error(void)
115 {
116     HANDLE hConIn;
117     HANDLE hConErr;
118     DWORD result;
119     time_t start;
120     time_t remains;
121     char *msg = "Note the errors or messages above, "
122                 "and press the <ESC> key to exit.  ";
123     CONSOLE_SCREEN_BUFFER_INFO coninfo;
124     INPUT_RECORD in;
125     char count[16];
126
127     if (!ap_real_exit_code)
128         return;
129     hConIn = GetStdHandle(STD_INPUT_HANDLE);
130     hConErr = GetStdHandle(STD_ERROR_HANDLE);
131     if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
132         return;
133     if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) || !result)
134         return;
135     if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
136         return;
137     if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
138         return;
139
140     start = time(NULL);
141     do
142     {
143         while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
144         {
145             if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
146                 return;
147             if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
148                     && (in.Event.KeyEvent.uChar.AsciiChar == 27))
149                 return;
150             if (in.EventType == MOUSE_EVENT
151                     && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
152                 return;
153         }
154         remains = ((start + 30) - time(NULL));
155         sprintf (count, "%d...", remains);
156         if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
157             return;
158         if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL)
159                 || !result)
160             return;
161     }
162     while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
163 }
164
165 static BOOL  die_on_logoff = FALSE;
166
167 static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
168 {
169     switch (ctrl_type)
170     {
171         case CTRL_BREAK_EVENT:
172             fprintf(stderr, "Apache server restarting...\n");
173             ap_signal_parent(SIGNAL_PARENT_RESTART);
174             return TRUE;
175         case CTRL_C_EVENT:
176             fprintf(stderr, "Apache server interrupted...\n");
177             /* for Interrupt signals, shut down the server.
178              * Tell the system we have dealt with the signal
179              * without waiting for Apache to terminate.
180              */
181             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
182             return TRUE;
183
184         case CTRL_CLOSE_EVENT:
185         case CTRL_LOGOFF_EVENT:
186         case CTRL_SHUTDOWN_EVENT:
187             /* for Terminate signals, shut down the server.
188              * Wait for Apache to terminate, but respond
189              * after a reasonable time to tell the system
190              * that we did attempt to shut ourself down.
191              */
192             fprintf(stderr, "Apache server shutdown initiated...\n");
193             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
194             Sleep(30000);
195             return TRUE;
196     }
197
198     /* We should never get here, but this is (mostly) harmless */
199     return FALSE;
200 }
201
202
203 static void stop_console_handler(void)
204 {
205     SetConsoleCtrlHandler(console_control_handler, FALSE);
206 }
207
208
209 void mpm_start_console_handler(void)
210 {
211     SetConsoleCtrlHandler(console_control_handler, TRUE);
212     atexit(stop_console_handler);
213 }
214
215
216 /* Special situation - children of services need to mind their
217  * P's & Q's and wait quietly, ignoring the mean OS signaling
218  * shutdown and other horrors, to kill them gracefully...
219  */
220
221 static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
222 {
223     switch (ctrl_type)
224     {
225         case CTRL_C_EVENT:
226         case CTRL_BREAK_EVENT:
227             /* for Interrupt signals, ignore them.
228              * The system will also signal the parent process,
229              * which will terminate Apache.
230              */
231             return TRUE;
232
233         case CTRL_CLOSE_EVENT:
234         case CTRL_LOGOFF_EVENT:
235         case CTRL_SHUTDOWN_EVENT:
236             /* for Shutdown signals, ignore them, but...             .
237              * The system will also signal the parent process,
238              * which will terminate Apache, so we need to wait.
239              */
240             Sleep(30000);
241             return TRUE;
242     }
243
244     /* We should never get here, but this is (mostly) harmless */
245     return FALSE;
246 }
247
248
249 void mpm_start_child_console_handler(void)
250 {
251     FreeConsole();
252 }
253
254
255 /**********************************
256   WinNT service control management
257  **********************************/
258
259 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
260 {
261     static int checkPoint = 1;
262     int rv = APR_SUCCESS;
263
264     if (globdat.hServiceStatus)
265     {
266         if (currentState == SERVICE_RUNNING) {
267             globdat.ssStatus.dwWaitHint = 0;
268             globdat.ssStatus.dwCheckPoint = 0;
269             globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
270         }
271         else if (currentState == SERVICE_STOPPED) {
272             globdat.ssStatus.dwWaitHint = 0;
273             globdat.ssStatus.dwCheckPoint = 0;
274             if (!exitCode && globdat.ssStatus.dwCurrentState
275                                            != SERVICE_STOP_PENDING) {
276                 /* An unexpected exit?  Better to error! */
277                 exitCode = 1;
278             }
279             if (exitCode) {
280                 globdat.ssStatus.dwWin32ExitCode =ERROR_SERVICE_SPECIFIC_ERROR;
281                 globdat.ssStatus.dwServiceSpecificExitCode = exitCode;
282             }
283         }
284         else {
285             globdat.ssStatus.dwCheckPoint = ++checkPoint;
286             globdat.ssStatus.dwControlsAccepted = 0;
287             if(waitHint)
288                 globdat.ssStatus.dwWaitHint = waitHint;
289         }
290
291         globdat.ssStatus.dwCurrentState = currentState;
292
293         rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
294     }
295     return(rv);
296 }
297
298 /* Set the service description regardless of platform.
299  * We revert to set_service_description, the
300  explicit
301  * way so any Apache management program can grab the
302  * description.  This would be bad on Win2000, since 
303  * it doesn't notify the service control manager of 
304  * the name change.
305  */
306
307 /* borrowed from mpm_winnt.c */
308 extern apr_pool_t *pconf;
309
310 /* Windows 2000 alone supports ChangeServiceConfig2 in order to
311  * register our server_version string... so we need some fixups
312  * to avoid binding to that function if we are on WinNT.
313  */
314 static void set_service_description(void)
315 {
316     const char *full_description;
317     SC_HANDLE schSCManager;
318     BOOL ret = 0;
319
320     /* Nothing to do if we are a console
321      */
322     if (!mpm_service_name)
323         return;
324
325     /* Time to fix up the description, upon each successful restart
326      */
327     full_description = ap_get_server_description();
328
329     if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
330           && (osver.dwMajorVersion > 4)
331           && (ChangeServiceConfig2)
332           && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)))
333     {
334         SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
335                                            SERVICE_CHANGE_CONFIG);
336         if (schService) {
337             /* Cast is necessary, ChangeServiceConfig2 handles multiple
338              * object types, some volatile, some not.
339              */
340             /* ###: utf-ize */
341             if (ChangeServiceConfig2(schService,
342                                      1 /* SERVICE_CONFIG_DESCRIPTION */,
343                                      (LPVOID) &full_description)) {
344                 full_description = NULL;
345             }
346             CloseServiceHandle(schService);
347         }
348         CloseServiceHandle(schSCManager);
349     }
350
351     if (full_description)
352     {
353         char szPath[MAX_PATH];
354         ap_regkey_t *svckey;
355         apr_status_t rv;
356
357         /* Find the Service key that Monitor Applications iterate */
358         apr_snprintf(szPath, sizeof(szPath),
359                      "SYSTEM\\CurrentControlSet\\Services\\%s",
360                      mpm_service_name);
361         rv = ap_regkey_open(&svckey, AP_REGKEY_LOCAL_MACHINE, szPath,
362                             APR_READ | APR_WRITE, pconf);
363         if (rv != APR_SUCCESS) {
364             return;
365         }
366         /* Attempt to set the Description value for our service */
367         ap_regkey_value_set(svckey, "Description", full_description, 0, pconf);
368         ap_regkey_close(svckey);
369     }
370 }
371
372 /* handle the SCM's ControlService() callbacks to our service */
373
374 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
375 {
376     if (dwCtrlCode == SERVICE_CONTROL_STOP)
377     {
378         ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
379         ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
380         return;
381     }
382     if (dwCtrlCode == SERVICE_APACHE_RESTART)
383     {
384         ap_signal_parent(SIGNAL_PARENT_RESTART);
385         ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
386         return;
387     }
388
389     ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
390 }
391
392
393 /* service_nt_main_fn is outside of the call stack and outside of the
394  * primary server thread... so now we _really_ need a placeholder!
395  * The winnt_rewrite_args has created and shared mpm_new_argv with us.
396  */
397 extern apr_array_header_t *mpm_new_argv;
398
399 /* ###: utf-ize */
400 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
401 {
402     const char *ignored;
403
404     /* args and service names live in the same pool */
405     mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
406
407     memset(&globdat.ssStatus, 0, sizeof(globdat.ssStatus));
408     globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
409     globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
410     globdat.ssStatus.dwCheckPoint = 1;
411
412     /* ###: utf-ize */
413     if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
414     {
415         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
416                      NULL, "Failure registering service handler");
417         return;
418     }
419
420     /* Report status, no errors, and buy 3 more seconds */
421     ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
422
423     /* We need to append all the command arguments passed via StartService()
424      * to our running service... which just got here via the SCM...
425      * but we hvae no interest in argv[0] for the mpm_new_argv list.
426      */
427     if (argc > 1)
428     {
429         char **cmb_data;
430
431         mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
432         cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
433
434         /* mpm_new_argv remains first (of lower significance) */
435         memcpy (cmb_data, mpm_new_argv->elts,
436                 mpm_new_argv->elt_size * mpm_new_argv->nelts);
437
438         /* Service args follow from StartService() invocation */
439         memcpy (cmb_data + mpm_new_argv->nelts, argv + 1,
440                 mpm_new_argv->elt_size * (argc - 1));
441
442         /* The replacement arg list is complete */
443         mpm_new_argv->elts = (char *)cmb_data;
444         mpm_new_argv->nelts = mpm_new_argv->nalloc;
445     }
446
447     /* Let the main thread continue now... but hang on to the
448      * signal_monitor event so we can take further action
449      */
450     SetEvent(globdat.service_init);
451
452     WaitForSingleObject(globdat.service_term, INFINITE);
453 }
454
455
456 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
457 {
458     apr_status_t rv = APR_SUCCESS;
459
460     SERVICE_TABLE_ENTRY dispatchTable[] =
461     {
462         { "", service_nt_main_fn },
463         { NULL, NULL }
464     };
465
466     /* ###: utf-ize */
467     if (!StartServiceCtrlDispatcher(dispatchTable))
468     {
469         /* This is a genuine failure of the SCM. */
470         rv = apr_get_os_error();
471         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
472                      "Error starting service control dispatcher");
473     }
474
475     return (rv);
476 }
477
478
479 apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
480                                   const char *set_name)
481 {
482     char key_name[MAX_PATH];
483     ap_regkey_t *key;
484     apr_status_t rv;
485
486     /* ### Needs improvement, on Win2K the user can _easily_
487      * change the display name to a string that doesn't reflect
488      * the internal service name + whitespace!
489      */
490     mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
491     apr_collapse_spaces((char*) mpm_service_name, set_name);
492     apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
493     rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, APR_READ, pconf);
494     if (rv == APR_SUCCESS) {
495         rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf);
496         ap_regkey_close(key);
497     }
498     if (rv != APR_SUCCESS) {
499         /* Take the given literal name if there is no service entry */
500         mpm_display_name = apr_pstrdup(p, set_name);
501     }
502     *display_name = mpm_display_name;
503     return rv;
504 }
505
506
507 apr_status_t mpm_merge_service_args(apr_pool_t *p,
508                                    apr_array_header_t *args,
509                                    int fixed_args)
510 {
511     apr_array_header_t *svc_args = NULL;
512     char conf_key[MAX_PATH];
513     char **cmb_data;
514     apr_status_t rv;
515     ap_regkey_t *key;
516
517     apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
518     rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p);
519     if (rv == APR_SUCCESS) {
520         rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p);
521         ap_regkey_close(key);
522     }
523     if (rv != APR_SUCCESS) {
524         if (rv == ERROR_FILE_NOT_FOUND) {
525             ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
526                          "No ConfigArgs registered for %s, perhaps "
527                          "this service is not installed?",
528                          mpm_service_name);
529             return APR_SUCCESS;
530         }
531         else
532             return (rv);
533     }
534
535     if (!svc_args || svc_args->nelts == 0) {
536         return (APR_SUCCESS);
537     }
538
539     /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
540      * call appended the arguments passed by StartService(), so it's
541      * time to _prepend_ the default arguments for the server from
542      * the service's default arguments (all others override them)...
543      */
544     args->nalloc = args->nelts + svc_args->nelts;
545     cmb_data = malloc(args->nalloc * sizeof(const char *));
546
547     /* First three args (argv[0], -f, path) remain first */
548     memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
549
550     /* Service args follow from service registry array */
551     memcpy(cmb_data + fixed_args, svc_args->elts,
552            svc_args->elt_size * svc_args->nelts);
553
554     /* Remaining new args follow  */
555     memcpy(cmb_data + fixed_args + svc_args->nelts,
556            (const char **)args->elts + fixed_args,
557            args->elt_size * (args->nelts - fixed_args));
558
559     args->elts = (char *)cmb_data;
560     args->nelts = args->nalloc;
561
562     return APR_SUCCESS;
563 }
564
565
566 void service_stopped(void)
567 {
568     /* Still have a thread & window to clean up, so signal now */
569     if (globdat.service_thread)
570     {
571         /* Stop logging to the event log */
572         mpm_nt_eventlog_stderr_flush();
573
574         /* Cause the service_nt_main_fn to complete */
575         ReleaseMutex(globdat.service_term);
576
577         ReportStatusToSCMgr(SERVICE_STOPPED, // service state
578                             NO_ERROR,        // exit code
579                             0);              // wait hint
580
581         WaitForSingleObject(globdat.service_thread, 5000);
582         CloseHandle(globdat.service_thread);
583     }
584 }
585
586
587 apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p)
588 {
589     HANDLE hProc = GetCurrentProcess();
590     HANDLE hThread = GetCurrentThread();
591     HANDLE waitfor[2];
592
593     /* Prevent holding open the (hidden) console */
594     ap_real_exit_code = 0;
595
596      /* GetCurrentThread returns a psuedo-handle, we need
597       * a real handle for another thread to wait upon.
598       */
599     if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
600                          0, FALSE, DUPLICATE_SAME_ACCESS)) {
601         return APR_ENOTHREAD;
602     }
603
604     globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
605     globdat.service_term = CreateMutex(NULL, TRUE, NULL);
606     if (!globdat.service_init || !globdat.service_term) {
607          return APR_EGENERAL;
608     }
609
610     globdat.service_thread = CreateThread(NULL, 65536,
611                                           service_nt_dispatch_thread,
612                                           NULL, stack_res_flag,
613                                           &globdat.service_thread_id);
614
615     if (!globdat.service_thread) {
616         return APR_ENOTHREAD;
617     }
618
619     waitfor[0] = globdat.service_init;
620     waitfor[1] = globdat.service_thread;
621
622     /* Wait for controlling thread init or termination */
623     if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
624         return APR_ENOTHREAD;
625     }
626
627     atexit(service_stopped);
628     *display_name = mpm_display_name;
629     return APR_SUCCESS;
630 }
631
632
633 apr_status_t mpm_service_started(void)
634 {
635     set_service_description();
636     ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0);
637     return APR_SUCCESS;
638 }
639
640
641 void mpm_service_stopping(void)
642 {
643     ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
644 }
645
646
647 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
648                                  const char * const * argv, int reconfig)
649 {
650     char key_name[MAX_PATH];
651     char exe_path[MAX_PATH];
652     char *launch_cmd;
653     ap_regkey_t *key;
654     apr_status_t rv;
655     SC_HANDLE   schService;
656     SC_HANDLE   schSCManager;
657
658     fprintf(stderr,reconfig ? "Reconfiguring the %s service\n"
659                    : "Installing the %s service\n", mpm_display_name);
660
661     /* ###: utf-ize */
662     if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
663     {
664         apr_status_t rv = apr_get_os_error();
665         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
666                      "GetModuleFileName failed");
667         return rv;
668     }
669
670     schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
671                                  SC_MANAGER_CREATE_SERVICE);
672     if (!schSCManager) {
673         rv = apr_get_os_error();
674         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
675                      "Failed to open the WinNT service manager, perhaps "
676                      "you forgot to log in as Adminstrator?");
677         return (rv);
678     }
679
680     launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
681
682     if (reconfig) {
683         /* ###: utf-ize */
684         schService = OpenService(schSCManager, mpm_service_name,
685                                  SERVICE_CHANGE_CONFIG);
686         if (!schService) {
687             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
688                          apr_get_os_error(), NULL,
689                          "OpenService failed");
690         }
691         /* ###: utf-ize */
692         else if (!ChangeServiceConfig(schService,
693                                       SERVICE_WIN32_OWN_PROCESS,
694                                       SERVICE_AUTO_START,
695                                       SERVICE_ERROR_NORMAL,
696                                       launch_cmd, NULL, NULL,
697                                       "Tcpip\0Afd\0", NULL, NULL,
698                                       mpm_display_name)) {
699             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
700                          apr_get_os_error(), NULL,
701                          "ChangeServiceConfig failed");
702
703             /* !schService aborts configuration below */
704             CloseServiceHandle(schService);
705             schService = NULL;
706             }
707         }
708     else {
709         /* RPCSS is the Remote Procedure Call (RPC) Locator required
710          * for DCOM communication pipes.  I am far from convinced we
711          * should add this to the default service dependencies, but
712          * be warned that future apache modules or ISAPI dll's may
713          * depend on it.
714          */
715         /* ###: utf-ize */
716         schService = CreateService(schSCManager,         // SCManager database
717                                    mpm_service_name,     // name of service
718                                    mpm_display_name,     // name to display
719                                    SERVICE_ALL_ACCESS,   // access required
720                                    SERVICE_WIN32_OWN_PROCESS,  // service type
721                                    SERVICE_AUTO_START,   // start type
722                                    SERVICE_ERROR_NORMAL, // error control type
723                                    launch_cmd,           // service's binary
724                                    NULL,                 // no load svc group
725                                    NULL,                 // no tag identifier
726                                    "Tcpip\0Afd\0",       // dependencies
727                                    NULL,                 // use SYSTEM account
728                                    NULL);                // no password
729
730         if (!schService)
731         {
732             rv = apr_get_os_error();
733             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
734                          "Failed to create WinNT Service Profile");
735             CloseServiceHandle(schSCManager);
736             return (rv);
737         }
738     }
739
740     CloseServiceHandle(schService);
741     CloseServiceHandle(schSCManager);
742
743     set_service_description();
744
745     /* Store the service ConfigArgs in the registry...
746      */
747     apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
748     rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
749                         APR_READ | APR_WRITE | APR_CREATE, pconf);
750     if (rv == APR_SUCCESS) {
751         rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf);
752         ap_regkey_close(key);
753     }
754     if (rv != APR_SUCCESS) {
755         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
756                      "%s: Failed to store the ConfigArgs in the registry.",
757                      mpm_display_name);
758         return (rv);
759     }
760     fprintf(stderr,"The %s service is successfully installed.\n", mpm_display_name);
761     return APR_SUCCESS;
762 }
763
764
765 apr_status_t mpm_service_uninstall(void)
766 {
767     apr_status_t rv;
768     SC_HANDLE schService;
769     SC_HANDLE schSCManager;
770
771     fprintf(stderr,"Removing the %s service\n", mpm_display_name);
772
773     schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
774                                  SC_MANAGER_CONNECT);
775     if (!schSCManager) {
776         rv = apr_get_os_error();
777         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
778                      "Failed to open the WinNT service manager.");
779         return (rv);
780     }
781
782     /* ###: utf-ize */
783     schService = OpenService(schSCManager, mpm_service_name, DELETE);
784
785     if (!schService) {
786         rv = apr_get_os_error();
787         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
788                         "%s: OpenService failed", mpm_display_name);
789         return (rv);
790     }
791
792     /* assure the service is stopped before continuing
793      *
794      * This may be out of order... we might not be able to be
795      * granted all access if the service is running anyway.
796      *
797      * And do we want to make it *this easy* for them
798      * to uninstall their service unintentionally?
799      */
800     /* ap_stop_service(schService);
801      */
802
803     if (DeleteService(schService) == 0) {
804         rv = apr_get_os_error();
805         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
806                      "%s: Failed to delete the service.", mpm_display_name);
807         return (rv);
808     }
809
810     CloseServiceHandle(schService);
811     CloseServiceHandle(schSCManager);
812
813     fprintf(stderr,"The %s service has been removed successfully.\n", mpm_display_name);
814     return APR_SUCCESS;
815 }
816
817
818 /* signal_service_transition is a simple thunk to signal the service
819  * and monitor its successful transition.  If the signal passed is 0,
820  * then the caller is assumed to already have performed some service
821  * operation to be monitored (such as StartService), and no actual
822  * ControlService signal is sent.
823  */
824
825 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
826 {
827     if (signal && !ControlService(schService, signal, &globdat.ssStatus))
828         return FALSE;
829
830     do {
831         Sleep(1000);
832         if (!QueryServiceStatus(schService, &globdat.ssStatus))
833             return FALSE;
834     } while (globdat.ssStatus.dwCurrentState == pending);
835
836     return (globdat.ssStatus.dwCurrentState == complete);
837 }
838
839
840 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
841                                const char * const * argv)
842 {
843     apr_status_t rv;
844     char **start_argv;
845     SC_HANDLE   schService;
846     SC_HANDLE   schSCManager;
847
848     fprintf(stderr,"Starting the %s service\n", mpm_display_name);
849
850     schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
851                                  SC_MANAGER_CONNECT);
852     if (!schSCManager) {
853         rv = apr_get_os_error();
854         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
855                      "Failed to open the WinNT service manager");
856         return (rv);
857     }
858
859     /* ###: utf-ize */
860     schService = OpenService(schSCManager, mpm_service_name,
861                              SERVICE_START | SERVICE_QUERY_STATUS);
862     if (!schService) {
863         rv = apr_get_os_error();
864         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
865                      "%s: Failed to open the service.", mpm_display_name);
866         CloseServiceHandle(schSCManager);
867         return (rv);
868     }
869
870     if (QueryServiceStatus(schService, &globdat.ssStatus)
871         && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
872         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
873                      "Service %s is already started!", mpm_display_name);
874         CloseServiceHandle(schService);
875         CloseServiceHandle(schSCManager);
876         return 0;
877     }
878
879     start_argv = malloc((argc + 1) * sizeof(const char **));
880     memcpy(start_argv, argv, argc * sizeof(const char **));
881     start_argv[argc] = NULL;
882
883     rv = APR_EINIT;
884     /* ###: utf-ize */
885     if (StartService(schService, argc, start_argv)
886         && signal_service_transition(schService, 0, /* test only */
887                                      SERVICE_START_PENDING,
888                                      SERVICE_RUNNING))
889         rv = APR_SUCCESS;
890     if (rv != APR_SUCCESS)
891         rv = apr_get_os_error();
892
893     CloseServiceHandle(schService);
894     CloseServiceHandle(schSCManager);
895
896     if (rv == APR_SUCCESS)
897         fprintf(stderr,"The %s service is running.\n", mpm_display_name);
898     else
899         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
900                      "%s: Failed to start the service process.",
901                      mpm_display_name);
902
903     return rv;
904 }
905
906
907 /* signal is zero to stop, non-zero for restart */
908
909 void mpm_signal_service(apr_pool_t *ptemp, int signal)
910 {
911     int success = FALSE;
912     SC_HANDLE   schService;
913     SC_HANDLE   schSCManager;
914
915     schSCManager = OpenSCManager(NULL, NULL, // default machine & database
916                                  SC_MANAGER_CONNECT);
917
918     if (!schSCManager) {
919         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
920                      "Failed to open the NT Service Manager");
921         return;
922     }
923
924     /* ###: utf-ize */
925     schService = OpenService(schSCManager, mpm_service_name,
926                              SERVICE_INTERROGATE | SERVICE_QUERY_STATUS |
927                              SERVICE_USER_DEFINED_CONTROL |
928                              SERVICE_START | SERVICE_STOP);
929
930     if (schService == NULL) {
931         /* Could not open the service */
932         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
933                      "Failed to open the %s Service", mpm_display_name);
934         CloseServiceHandle(schSCManager);
935         return;
936     }
937
938     if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
939         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
940                      "Query of Service %s failed", mpm_display_name);
941         CloseServiceHandle(schService);
942         CloseServiceHandle(schSCManager);
943         return;
944     }
945
946     if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
947         fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
948         CloseServiceHandle(schService);
949         CloseServiceHandle(schSCManager);
950         return;
951     }
952
953     fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
954             signal ? "restarting" : "stopping");
955
956     if (!signal)
957         success = signal_service_transition(schService,
958                                             SERVICE_CONTROL_STOP,
959                                             SERVICE_STOP_PENDING,
960                                             SERVICE_STOPPED);
961     else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
962         mpm_service_start(ptemp, 0, NULL);
963         CloseServiceHandle(schService);
964         CloseServiceHandle(schSCManager);
965         return;
966     }
967     else
968         success = signal_service_transition(schService,
969                                             SERVICE_APACHE_RESTART,
970                                             SERVICE_START_PENDING,
971                                             SERVICE_RUNNING);
972
973     CloseServiceHandle(schService);
974     CloseServiceHandle(schSCManager);
975
976     if (success)
977         fprintf(stderr,"The %s service has %s.\n", mpm_display_name,
978                signal ? "restarted" : "stopped");
979     else
980         fprintf(stderr,"Failed to %s the %s service.\n",
981                signal ? "restart" : "stop", mpm_display_name);
982 }