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