bozo: preserve all options over restart
[openafs.git] / src / WINNT / bosctlsvc / bosctlsvc.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /* This file implements the AFS BOS control service.  Basically, it provides
11  * a mechanism to start and stop the AFS bosserver via the NT SCM; it also
12  * supports bosserver restart.
13  */
14
15
16 #include <afs/param.h>
17 #include <afs/stds.h>
18
19 #include <stddef.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <errno.h>
23 #include <windows.h>
24 #include <time.h>
25 #include <process.h>
26
27 #include <WINNT/afsevent.h>
28 #include <WINNT/afsreg.h>
29 #include <afs/procmgmt.h>
30 #include <afs/dirpath.h>
31 #include <afs/bnode.h>
32 #include <afs/afsicf.h>
33
34 /* Define globals */
35
36 #define BOSSERVER_STARTMSG_EXE  "afslegal.exe"
37
38 #define BOSSERVER_WAIT_TIME_HINT  60  /* seconds */
39 #define BOSSERVER_STOP_TIME_MAX  (FSSDTIME + 60)  /* seconds */
40
41 #define BOS_CONTROLS_ACCEPTED  SERVICE_ACCEPT_STOP
42
43 static CRITICAL_SECTION bosCtlStatusLock;  /* protects bosCtlStatus */
44 static SERVICE_STATUS bosCtlStatus;
45 static SERVICE_STATUS_HANDLE bosCtlStatusHandle;
46
47 /* note: events arranged in priority order in case multiple signaled */
48 #define BOS_STOP_EVENT 0
49 #define BOS_EXIT_EVENT 1
50 #define BOS_EVENT_COUNT 2
51 static HANDLE bosCtlEvent[BOS_EVENT_COUNT];
52
53
54 /* Declare local functions */
55
56 static void AsyncSignalCatcher(int signo);
57
58 static void BosCtlStatusInit(DWORD initState);
59
60 static DWORD BosCtlStatusUpdate(DWORD newState,
61                                 DWORD exitCode,
62                                 BOOL isWin32Code);
63
64 static DWORD BosCtlStatusReport(void);
65
66 static void WINAPI BosCtlHandler(DWORD controlCode);
67
68 static void WINAPI BosCtlMain(DWORD argc,
69                               LPTSTR *argv);
70
71 static void BosserverDoStopEvent(pid_t cpid,
72                                  DWORD *stopStatus,
73                                  BOOL *isWin32Code);
74
75 static void BosserverDoExitEvent(pid_t cpid,
76                                  BOOL *doWait,
77                                  BOOL *doRestart,
78                                  DWORD *stopStatus,
79                                  BOOL *isWin32Code);
80
81 static void BosserverRun(DWORD argc,
82                          LPTSTR *argv,
83                          DWORD *stopStatus,
84                          BOOL *isWin32Code);
85
86 static void BosserverStartupMsgDisplay(void);
87
88
89
90
91 /*
92  * AsyncSignalCatcher() -- Handle asynchronous signals sent to process
93  */
94 static void
95 AsyncSignalCatcher(int signo)
96 {
97     if (signo == SIGCHLD) {
98         (void) SetEvent(bosCtlEvent[BOS_EXIT_EVENT]);
99     }
100 }
101
102
103 /*
104  * BosCtlStatusInit() -- initialize BOS control service status structure
105  */
106 static void
107 BosCtlStatusInit(DWORD initState)
108 {
109     bosCtlStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
110     bosCtlStatus.dwCurrentState = initState;
111
112     if (initState == SERVICE_RUNNING) {
113         bosCtlStatus.dwControlsAccepted = BOS_CONTROLS_ACCEPTED;
114     } else {
115         bosCtlStatus.dwControlsAccepted = 0;
116     }
117
118     bosCtlStatus.dwWin32ExitCode = 0;
119     bosCtlStatus.dwServiceSpecificExitCode = 0;
120     bosCtlStatus.dwCheckPoint = 0;
121     bosCtlStatus.dwWaitHint = BOSSERVER_WAIT_TIME_HINT * 1000; /* millisecs */
122
123     InitializeCriticalSection(&bosCtlStatusLock);
124 }
125
126
127 /*
128  * BosCtlStatusUpdate() -- update BOS control service status and report to SCM
129  */
130 static DWORD
131 BosCtlStatusUpdate(DWORD newState, DWORD exitCode, BOOL isWin32Code)
132 {
133     DWORD rc = 0;
134
135     EnterCriticalSection(&bosCtlStatusLock);
136
137     /* SERVICE_STOPPED is a terminal state; never transition out of it */
138     if (bosCtlStatus.dwCurrentState != SERVICE_STOPPED) {
139
140         if ((bosCtlStatus.dwCurrentState == newState) &&
141             (newState == SERVICE_START_PENDING ||
142              newState == SERVICE_STOP_PENDING)) {
143             /* continuing a pending state; increment checkpoint value */
144             bosCtlStatus.dwCheckPoint++;
145         } else {
146             /* not continuing a pending state; reset checkpoint value */
147             bosCtlStatus.dwCheckPoint = 0;
148         }
149
150         bosCtlStatus.dwCurrentState = newState;
151
152         if (newState == SERVICE_RUNNING) {
153             bosCtlStatus.dwControlsAccepted = BOS_CONTROLS_ACCEPTED;
154         } else {
155             bosCtlStatus.dwControlsAccepted = 0;
156         }
157
158         if (isWin32Code) {
159             bosCtlStatus.dwWin32ExitCode = exitCode;
160             bosCtlStatus.dwServiceSpecificExitCode = 0;
161         } else {
162             bosCtlStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
163             bosCtlStatus.dwServiceSpecificExitCode = exitCode;
164         }
165     }
166
167     if (!SetServiceStatus(bosCtlStatusHandle, &bosCtlStatus)) {
168         rc = GetLastError();
169     }
170
171     LeaveCriticalSection(&bosCtlStatusLock);
172
173     return rc;
174 }
175
176
177 /*
178  * BosCtlStatusReport() -- report current BOS control service status to SCM
179  */
180 static DWORD
181 BosCtlStatusReport(void)
182 {
183     DWORD rc = 0;
184
185     EnterCriticalSection(&bosCtlStatusLock);
186
187     if (!SetServiceStatus(bosCtlStatusHandle, &bosCtlStatus)) {
188         rc = GetLastError();
189     }
190
191     LeaveCriticalSection(&bosCtlStatusLock);
192
193     return rc;
194 }
195
196
197 /*
198  * BosCtlHandler() -- control handler for BOS control service
199  */
200 static void WINAPI
201 BosCtlHandler(DWORD controlCode)
202 {
203     switch (controlCode) {
204       case SERVICE_CONTROL_STOP:
205         (void) SetEvent(bosCtlEvent[BOS_STOP_EVENT]);
206         (void) BosCtlStatusUpdate(SERVICE_STOP_PENDING, 0, TRUE);
207         break;
208
209       default:
210         (void) BosCtlStatusReport();
211         break;
212     }
213 }
214
215
216 /*
217  * BosCtlMain() -- main function for BOS control service
218  */
219 static void WINAPI
220 BosCtlMain(DWORD argc, LPTSTR *argv)
221 {
222     DWORD status;
223     BOOL isWin32Code;
224     struct sigaction childAction;
225
226     /* Initialize status structure */
227     BosCtlStatusInit(SERVICE_START_PENDING);
228
229     /* Create events used by service control handler and signal handler */
230     if ((bosCtlEvent[BOS_STOP_EVENT] = CreateEvent(NULL,
231                                                    FALSE /* manual reset */,
232                                                    FALSE /* initial state */,
233                                                    TEXT("BosCtlSvc Stop Event"))) == NULL) {
234         status = GetLastError();
235     }
236
237     if ((bosCtlEvent[BOS_EXIT_EVENT] = CreateEvent(NULL,
238                                                    FALSE /* manual reset */,
239                                                    FALSE /* initial state */,
240                                                    TEXT("BosCtlSvc Exit Event"))) == NULL) {
241         status = GetLastError();
242     }
243
244     /* Register service control handler */
245     bosCtlStatusHandle = RegisterServiceCtrlHandler(AFSREG_SVR_SVC_NAME,
246                                                     BosCtlHandler);
247     if (bosCtlStatusHandle == 0) {
248         /* failed to register control handler for service; can not continue */
249         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_HANDLER_REG_FAILED,
250                                    (int)GetLastError(), NULL);
251         /* can not report status to SCM w/o a valid bosCtlStatusHandle */
252         return;
253     }
254
255     /* Stop immediately if required system resources could not be obtained */
256     if (bosCtlEvent[BOS_STOP_EVENT] == NULL ||
257         bosCtlEvent[BOS_EXIT_EVENT] == NULL) {
258         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_INSUFFICIENT_RESOURCES,
259                                    (int)status, NULL);
260         (void) BosCtlStatusUpdate(SERVICE_STOPPED, status, TRUE);
261         return;
262     }
263
264     /* Report pending start status */
265     if (status = BosCtlStatusUpdate(SERVICE_START_PENDING, 0, TRUE)) {
266         /* can't inform SCM of pending start; give up before really start */
267         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_SCM_COMM_FAILED,
268                                    (int)status, NULL);
269         (void) BosCtlStatusUpdate(SERVICE_STOPPED, status, TRUE);
270         return;
271     }
272
273     /* For XP SP2 and above, open required ports */
274     icf_CheckAndAddAFSPorts(AFS_PORTSET_SERVER);
275
276     /* Initialize the dirpath package so can access local bosserver binary */
277     if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
278         /* sw install directory probably not in registry; can not continue */
279         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_NO_INSTALL_DIR, 0, NULL);
280         (void) BosCtlStatusUpdate(SERVICE_STOPPED, 0, TRUE);
281         return;
282     }
283
284     /* Install SIGCHLD handler to catch bosserver restarts and failures */
285     childAction.sa_handler = AsyncSignalCatcher;
286     sigfillset(&childAction.sa_mask);
287     childAction.sa_flags = 0;
288
289     if (sigaction(SIGCHLD, &childAction, NULL)) {
290         /* handler install should never fail, but can't continue if it does */
291         status = errno;
292         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_INTERNAL_ERROR,
293                                    (int)status, NULL);
294         (void) BosCtlStatusUpdate(SERVICE_STOPPED, status, FALSE);
295         return;
296     }
297
298     /* Run the AFS bosserver, handling stop and exit events */
299     BosserverRun(argc, argv, &status, &isWin32Code);
300
301     (void) BosCtlStatusUpdate(SERVICE_STOPPED, status, isWin32Code);
302 }
303
304
305 /*
306  * BosserverDoStopEvent() -- Handle a stop event for the AFS bosserver.
307  */
308 static void
309 BosserverDoStopEvent(pid_t cpid, DWORD *stopStatus, BOOL *isWin32Code)
310 {
311     (void) BosCtlStatusUpdate(SERVICE_STOP_PENDING, 0, TRUE);
312
313     if (kill(cpid, SIGQUIT) == 0) {
314         /* bosserver has been told to stop; wait for this to happen */
315         BOOL gotWaitStatus = FALSE;
316         time_t timeStart = time(NULL);
317
318         do {
319             int waitStatus;
320             DWORD status;
321
322             if (waitpid(cpid, &waitStatus, WNOHANG) == cpid) {
323                 /* bosserver status available */
324                 if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
325                     /* bosserver exited w/o any error */
326                     *stopStatus = 0;
327                     *isWin32Code = TRUE;
328                 } else {
329                     *stopStatus = waitStatus;
330                     *isWin32Code = FALSE;
331                 }
332                 gotWaitStatus = TRUE;
333                 break;
334             }
335
336             /* wait for bosserver status to become available;
337              * update BOS control service status periodically.
338              */
339             status = WaitForSingleObject(bosCtlEvent[BOS_EXIT_EVENT],
340                                          BOSSERVER_WAIT_TIME_HINT * 1000 / 2);
341             if (status == WAIT_FAILED) {
342                 /* failed to wait on event; should never happen */
343                 Sleep(2000);  /* sleep to avoid tight loop if event problem */
344             }
345             (void) BosCtlStatusUpdate(SERVICE_STOP_PENDING, 0, TRUE);
346         } while (difftime(time(NULL), timeStart) < BOSSERVER_STOP_TIME_MAX);
347
348         if (!gotWaitStatus) {
349             /* timed out waiting to get bosserver status */
350             *stopStatus = EBUSY;
351             *isWin32Code = FALSE;
352
353             (void) ReportWarningEventAlt(AFSEVT_SVR_BCS_BOSSERVER_STOP_TIMEOUT,
354                                          (int)*stopStatus, NULL);
355         }
356
357     } else {
358         /* can't tell bosserver to stop; should never happen */
359         *stopStatus = errno;
360         *isWin32Code = FALSE;
361
362         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_BOSSERVER_STOP_FAILED,
363                                    (int)*stopStatus, NULL);
364     }
365 }
366
367
368 /*
369  * BosserverDoExitEvent() -- Handle an exit event for the AFS bosserver.
370  *
371  *     The output arguments for this function are set as follows:
372  *         Case 1: bosserver did not exit (spurious SIGCHLD);
373  *                 *doWait is set to TRUE.
374  *         Case 2: bosserver exited with restart code;
375  *                 *doRestart is set to TRUE, restartArgv[] is defined.
376  *         Case 3: bosserver exited with non-restart code;
377  *                 *stopStatus and *isWin32Code are defined.
378  */
379 static void
380 BosserverDoExitEvent(pid_t cpid,
381                      BOOL *doWait,
382                      BOOL *doRestart,
383                      DWORD *stopStatus,
384                      BOOL *isWin32Code)
385 {
386     int waitStatus;
387
388     *doWait = FALSE;
389     *doRestart = FALSE;
390
391     if (waitpid(cpid, &waitStatus, WNOHANG) == cpid) {
392         /* bosserver status available */
393
394         if (WIFEXITED(waitStatus)) {
395             /* bosserver exited normally; check for restart code */
396             int exitCode = WEXITSTATUS(waitStatus);
397
398             if (BOSEXIT_DORESTART(exitCode)) {
399                 /* bosserver requests restart */
400                 int i;
401                 *doRestart = TRUE;
402             }
403         }
404
405         if (!(*doRestart)) {
406             /* bosserver exited with non-restart code; set status */
407             *stopStatus = waitStatus;
408             *isWin32Code = FALSE;
409
410             (void) ReportWarningEventAlt(AFSEVT_SVR_BCS_BOSSERVER_EXIT,
411                                          (int)*stopStatus, NULL);
412         }
413
414     } else {
415         /* bosserver status NOT available; assume spurious SIGCHLD */
416         *doWait = TRUE;
417     }
418 }
419
420
421 /*
422  * BosserverRun() -- Run the AFS bosserver, handling stop and exit events.
423  *
424  *     Input args are those passed to the service's main function (BosCtlMain).
425  *     Output args are the stop status and status type of the bosserver.
426  */
427 static void
428 BosserverRun(DWORD argc,
429              LPTSTR *argv,
430              DWORD *stopStatus,
431              BOOL *isWin32Code)
432 {
433     DWORD status, i;
434     BOOL doRestart, doWait;
435     char **spawn_argv;
436
437     /* Display bosserver startup (legal) message; first start only */
438     /* BosserverStartupMsgDisplay(); */
439
440     /* Set env variable forcing process mgmt lib to spawn processes detached */
441     (void)putenv(PMGT_SPAWN_DETACHED_ENV_NAME "=1");
442
443     /* Alloc block with room for arguments plus a terminator */
444     spawn_argv = (char **)malloc((argc + 1) * sizeof(char *));
445
446     if (spawn_argv == NULL) {
447         /* failed to malloc required space; can not continue */
448         *stopStatus = ENOMEM;
449         *isWin32Code = FALSE;
450
451         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_INSUFFICIENT_RESOURCES,
452                                    (int)*stopStatus, NULL);
453         return;
454     }
455
456     /* Set initial bosserver args to those supplied via StartService() */
457     spawn_argv[0] = (char *)AFSDIR_SERVER_BOSVR_FILEPATH;
458
459     for (i = 1; i < argc; i++) {
460         spawn_argv[i] = argv[i];
461     }
462     spawn_argv[i] = NULL;
463
464     /* Start/restart bosserver and wait for either a stop or exit event */
465     doRestart = FALSE;
466
467     do {
468         pid_t cpid;
469
470         if (doRestart) {
471             /* restarting bosserver; log informational message */
472             (void) ReportInformationEventAlt(AFSEVT_SVR_BCS_BOSSERVER_RESTART,
473                                              NULL);
474             doRestart = FALSE;
475         }
476
477         cpid = spawnprocve(spawn_argv[0], spawn_argv, NULL, 0);
478
479         if (cpid == (pid_t)-1) {
480             /* failed to start/restart the bosserver process */
481             *stopStatus = errno;
482             *isWin32Code = FALSE;
483
484             (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_BOSSERVER_START_FAILED,
485                                        (int)*stopStatus, NULL);
486             break;
487         }
488
489         if (status = BosCtlStatusUpdate(SERVICE_RUNNING, 0, TRUE)) {
490             /* can't tell SCM we're running so quit; should never occur */
491             (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_SCM_COMM_FAILED,
492                                        (int)status, NULL);
493             (void) SetEvent(bosCtlEvent[BOS_STOP_EVENT]);
494         }
495
496         /* bosserver is running; wait for an event of interest */
497
498         Sleep(5000);  /* bosserver needs time to register signal handler */
499
500         do {
501             doWait = FALSE;
502
503             status = WaitForMultipleObjects(BOS_EVENT_COUNT,
504                                             bosCtlEvent, FALSE, INFINITE);
505
506             if ((status - WAIT_OBJECT_0) == BOS_STOP_EVENT) {
507                 /* stop event signaled */
508                 BosserverDoStopEvent(cpid, stopStatus, isWin32Code);
509
510             } else if ((status - WAIT_OBJECT_0) == BOS_EXIT_EVENT) {
511                 /* exit event signaled; see function comment for outcomes */
512                 BosserverDoExitEvent(cpid,
513                                      &doWait,
514                                      &doRestart,
515                                      stopStatus, isWin32Code);
516
517             } else {
518                 /* failed to wait on events; should never happen */
519                 Sleep(2000);  /* sleep to avoid tight loop if event problem */
520                 doWait = TRUE;
521             }
522         } while (doWait);
523     } while(doRestart);
524
525     return;
526 }
527
528
529 /*
530  * BosserverStartupMsgDisplay() -- display Windows version of AFS bosserver
531  *     startup (legal) message.
532  */
533 static void
534 BosserverStartupMsgDisplay(void)
535 {
536     char *msgPath;
537
538     if (!ConstructLocalBinPath(BOSSERVER_STARTMSG_EXE, &msgPath)) {
539         /* Use C runtime spawn; don't need all the machinery in the
540          * process management library.
541          */
542         (void)_spawnl(_P_DETACH, msgPath, BOSSERVER_STARTMSG_EXE, NULL);
543         free(msgPath);
544     }
545 }
546
547
548 /*
549  * main() -- start dispatcher thread for BOS control service
550  */
551 int main(void)
552 {
553     SERVICE_TABLE_ENTRY dispatchTable[] = {{AFSREG_SVR_SVC_NAME, BosCtlMain},
554                                            {NULL, NULL}};
555
556     (void) ReportInformationEventAlt(AFSEVT_SVR_BCS_STARTED, NULL);
557
558     if (!StartServiceCtrlDispatcher(dispatchTable)) {
559         /* unable to connect to SCM */
560         (void) ReportErrorEventAlt(AFSEVT_SVR_BCS_SCM_COMM_FAILED,
561                                    (int)GetLastError(), NULL);
562     }
563
564     (void) ReportInformationEventAlt(AFSEVT_SVR_BCS_STOPPED, NULL);
565     return 0;
566 }