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