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