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