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