More work on event queues. *_option returns new value (not previous).
[yaz-moved-to-github.git] / server / service.c
1 /*
2  * NT Service interface Utility.
3  *  Based on code written by
4  *     Chas Woodfield, Fretwell Downing Datasystems.
5  * $Log: service.c,v $
6  * Revision 1.4  2000-12-05 19:05:10  adam
7  * Service automatically starts in the directory from which it was installed.
8  *
9  * Revision 1.3  1999/06/10 11:45:30  adam
10  * Added bend_start, bend_stop handlers and removed pre_init.
11  * Handlers bend_start/bend_stop are called when service/daemon is
12  * started/stopped.
13  *
14  * Revision 1.2  1999/02/02 13:57:36  adam
15  * Uses preprocessor define WIN32 instead of WINDOWS to build code
16  * for Microsoft WIN32.
17  *
18  * Revision 1.1  1997/11/07 13:31:52  adam
19  * Added NT Service name part of statserv_options_block. Moved NT
20  * service utility to server library.
21  *
22  * Revision 1.6  1997/09/18 08:49:14  adam
23  * Option -runnormal no needed to run server in standalone mode.
24  *
25  * Revision 1.5  1997/09/17 12:10:43  adam
26  * YAZ version 1.4.
27  *
28  * Revision 1.4  1997/09/09 10:10:20  adam
29  * Another MSV5.0 port. Changed projects to include proper
30  * library/include paths.
31  * Server starts server in test-mode when no options are given.
32  *
33  * Revision 1.3  1997/09/04 13:50:30  adam
34  * Bug fix in ztest.
35  *
36  */
37
38 /************************************************************/
39 /* Note this file is shared by all processes                */
40 /* Should really put it somewhere other than here           */
41 /* For some strange reason it won't work when part of a lib */
42 /************************************************************/
43
44 #ifdef WIN32
45
46 #include <windows.h>
47 #include <stdio.h>
48 #include <tchar.h>
49 #include <direct.h>
50
51 #include "service.h"
52
53 static AppService *pService = NULL;
54 static BOOL bRunAsService = TRUE;
55 static void *pAppHandle = NULL;
56
57 /* Private functions to this module */
58 void Service_Create(LPTSTR pAppName, LPTSTR pServiceName, LPTSTR pServiceDisplayName, LPTSTR pDependancies, int argc, char **argv);
59 void Service_Delete();
60 void Service_Initialize();
61 BOOL NotifyServiceController();
62 BOOL UpdateServiceStatus(DWORD Status);
63 void FailServiceStart(DWORD Win32Code, DWORD PrivateCode);
64 void CmdInstallService(int argc, char *argv[], BOOL bAutoStart);
65 void CmdRemoveService();
66 LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize);
67 BOOL CheckServiceArguments(int argc, char *argv[]);
68
69 /* Callback functions for thee service manager */
70 void WINAPI ServiceMain(DWORD argc, LPTSTR argv[]);
71 void WINAPI ServiceControlHandler(DWORD fdwControl);
72
73 /* Function to handle Ctrl + C etc... */
74 BOOL EventHandlerRoutine(DWORD dwCtrlType);
75
76 void Service_Create(LPTSTR pAppName, LPTSTR pServiceName, LPTSTR pServiceDisplayName, LPTSTR pDependancies, int argc, char **argv)
77 {
78     pService = malloc(sizeof(AppService));
79     pService->pAppName = pAppName;
80     pService->pServiceName = pServiceName;
81     pService->pServiceDisplayName = pServiceDisplayName;
82     pService->pDependancies = pDependancies;
83     pService->hService = 0;
84     pService->ServiceTable[0].lpServiceName = pServiceName; 
85     pService->ServiceTable[0].lpServiceProc = ServiceMain; 
86     pService->ServiceTable[1].lpServiceName = NULL; 
87     pService->ServiceTable[1].lpServiceProc = NULL; 
88     pService->argc = argc;
89     pService->argv = argv;
90 }
91
92 void Service_Delete()
93 {
94     if (pService != NULL)
95     {
96         /* Mark the service as stopping */
97         UpdateServiceStatus(SERVICE_STOP_PENDING);
98
99         /* Stop the service */
100         StopAppService(pAppHandle);
101
102         /* Service has now stopped */
103         UpdateServiceStatus(SERVICE_STOPPED);
104
105         /* Free the memory */
106         free(pService);
107         pService = NULL;
108     }
109 }
110
111 void Service_Initialize()
112 {
113     if (pService != NULL)
114     {
115         /* Register ourselves with the control dispatcher */
116         StartServiceCtrlDispatcher(pService->ServiceTable);
117     }
118 }
119
120 void WINAPI ServiceMain(DWORD argc, LPTSTR argv[])
121 {
122     if (pService != NULL)
123     {
124         if (NotifyServiceController())
125         {
126             /* Set the status to pending */
127             UpdateServiceStatus(SERVICE_START_PENDING);
128
129             /* Lets attempt to start the service */
130             if (StartAppService(pAppHandle, pService->argc, pService->argv))
131             {
132                 /* Service is now up and running */
133                 UpdateServiceStatus(SERVICE_RUNNING);
134
135                 /* Lets wait for our clients */
136                 RunAppService(pAppHandle);
137             }
138             else
139             {
140                 FailServiceStart(GetLastError(), 0);
141                 Service_Delete();
142             }
143         }
144     }
145 }
146
147 BOOL NotifyServiceController()
148 {
149     if (pService == NULL)
150     {
151         return(FALSE);
152     }
153     else
154     {
155         if (bRunAsService)
156         {
157             pService->ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
158             pService->ServiceStatus.dwCurrentState = SERVICE_STOPPED;
159             pService->ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
160             pService->ServiceStatus.dwWin32ExitCode = 0;
161             pService->ServiceStatus.dwServiceSpecificExitCode = 0;
162             pService->ServiceStatus.dwCheckPoint = 0;
163             pService->ServiceStatus.dwWaitHint = 0;
164             pService->hService = RegisterServiceCtrlHandler(pService->pServiceName, ServiceControlHandler);
165
166             if (pService->hService)
167                 UpdateServiceStatus(SERVICE_START_PENDING);
168             else
169                 return(FALSE);
170         }
171         return(TRUE);
172     }
173 }
174
175 void WINAPI ServiceControlHandler(DWORD fdwControl)
176 {
177     if (pService != NULL)
178     {
179         switch (fdwControl)
180         {
181             case SERVICE_CONTROL_STOP:
182                 /* Update the service status to be pending */
183                 Service_Delete();
184                 break;
185
186             case SERVICE_CONTROL_INTERROGATE:
187                 UpdateServiceStatus(pService->ServiceStatus.dwCurrentState);
188                 break;
189
190             default:
191                 break;
192         }
193     }
194 }
195
196 BOOL UpdateServiceStatus(DWORD Status)
197 {
198     if (pService != NULL)
199     {
200         if (pService->hService)
201         {
202             pService->ServiceStatus.dwCurrentState = Status;
203             if ((Status == SERVICE_START_PENDING) || (Status == SERVICE_STOP_PENDING))
204             {
205                 pService->ServiceStatus.dwCheckPoint ++;
206                 pService->ServiceStatus.dwWaitHint = 5000;    /* 5 sec.*/
207             }
208             else
209             {
210                 pService->ServiceStatus.dwCheckPoint = 0;
211                 pService->ServiceStatus.dwWaitHint = 0;
212             }
213
214             return(SetServiceStatus(pService->hService, &pService->ServiceStatus));
215         }
216     }
217
218     return(FALSE);
219 }
220
221 void FailServiceStart(DWORD Win32Code, DWORD PrivateCode)
222 {
223     if (pService != NULL)
224     {
225         pService->ServiceStatus.dwWin32ExitCode = Win32Code;
226         pService->ServiceStatus.dwServiceSpecificExitCode = PrivateCode;
227         UpdateServiceStatus(SERVICE_STOPPED);
228     }
229 }
230
231 void CmdInstallService(int argc, char *argv[], BOOL bAutoStart)
232 {
233     if (pService != NULL)
234     {
235         SC_HANDLE   schService;
236         SC_HANDLE   schSCManager;
237
238         TCHAR szPath[2048];
239
240         if (GetModuleFileName(NULL, szPath, 512) == 0)
241         {
242             _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(pService->pServiceDisplayName), GetLastErrorText(pService->szErr, 256));
243         }
244         else
245         {
246             int i;
247             char cwdstr[_MAX_PATH];
248
249             if (!_getcwd(cwdstr, sizeof(cwdstr)))
250                 strcpy (cwdstr, ".");
251
252             strcat (szPath, TEXT(" -runservice \""));
253             strcat (szPath, cwdstr);
254             strcat (szPath, "\"");
255
256             for (i = 1; i < argc; i++)
257             {
258                 /* We will add the given command line arguments to the command */
259                 /* We are not interested in the install and remove options */
260                 if ((stricmp("-install", argv[i]) != 0) &&
261                     (stricmp("-installa", argv[i]) != 0) &&
262                     (stricmp("-remove", argv[i]) != 0))
263                 {
264                     strcat(szPath, TEXT(" "));
265                     strcat(szPath, argv[i]);
266                 }
267             }
268
269             schSCManager = OpenSCManager(NULL,                   /* machine (NULL == local) */
270                                          NULL,                   /* database (NULL == default) */
271                                          SC_MANAGER_ALL_ACCESS); /* access required */
272             if (schSCManager)
273             {
274                 schService = CreateService(schSCManager,               /* SCManager database */
275                                            TEXT(pService->pServiceName),        /* name of service */
276                                            TEXT(pService->pServiceDisplayName), /* name to display */
277                                            SERVICE_ALL_ACCESS,         /* desired access */
278                                            SERVICE_WIN32_OWN_PROCESS,  /* service type */
279                                            bAutoStart ? SERVICE_AUTO_START :
280                                                         SERVICE_DEMAND_START, /* start type */
281                                            SERVICE_ERROR_NORMAL,       /* error control type */
282                                            szPath,                     /* service's binary */
283                                            NULL,                       /* no load ordering group */
284                                            NULL,                       /* no tag identifier */
285                                            TEXT(pService->pDependancies),       /* dependencies */
286                                            NULL,                       /* LocalSystem account */
287                                            NULL);                      /* no password */
288
289                 if (schService)
290                 {
291                     _tprintf(TEXT("%s installed.\n"), TEXT(pService->pServiceDisplayName));
292                     CloseServiceHandle(schService);
293                 }
294                 else
295                 {
296                     _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(pService->szErr, 256));
297                 }
298
299                 CloseServiceHandle(schSCManager);
300             }
301             else
302                 _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(pService->szErr,256));
303         }
304     }
305 }
306
307 void CmdRemoveService()
308 {
309     if (pService != NULL)
310     {
311         SC_HANDLE   schService;
312         SC_HANDLE   schSCManager;
313
314         schSCManager = OpenSCManager(NULL,                   /* machine (NULL == local) */
315                                      NULL,                   /* database (NULL == default) */
316                                      SC_MANAGER_ALL_ACCESS); /* access required */
317         if (schSCManager)
318         {
319             schService = OpenService(schSCManager, TEXT(pService->pServiceName), SERVICE_ALL_ACCESS);
320
321             if (schService)
322             {
323                 /* try to stop the service */
324                 if (ControlService(schService, SERVICE_CONTROL_STOP, &pService->ServiceStatus))
325                 {
326                     _tprintf(TEXT("Stopping %s."), TEXT(pService->pServiceDisplayName));
327                     Sleep(1000);
328
329                     while (QueryServiceStatus(schService, &pService->ServiceStatus))
330                     {
331                         if (pService->ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING)
332                         {
333                             _tprintf(TEXT("."));
334                             Sleep( 1000 );
335                         }
336                         else
337                             break;
338                     }
339
340                     if (pService->ServiceStatus.dwCurrentState == SERVICE_STOPPED)
341                         _tprintf(TEXT("\n%s stopped.\n"), TEXT(pService->pServiceDisplayName));
342                     else
343                         _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(pService->pServiceDisplayName));
344
345                 }
346
347                 /* now remove the service */
348                 if(DeleteService(schService))
349                     _tprintf(TEXT("%s removed.\n"), TEXT(pService->pServiceDisplayName));
350                 else
351                     _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(pService->szErr,256));
352
353                 CloseServiceHandle(schService);
354             }
355             else
356                 _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(pService->szErr,256));
357
358             CloseServiceHandle(schSCManager);
359         }
360         else
361             _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(pService->szErr,256));
362     }
363 }
364
365 LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize)
366 {
367     DWORD dwRet;
368     LPTSTR lpszTemp = NULL;
369
370     dwRet = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
371                           NULL,
372                           GetLastError(),
373                           LANG_NEUTRAL,
374                           (LPTSTR)&lpszTemp,
375                           0,
376                           NULL);
377
378     /* supplied buffer is not long enough */
379     if (!dwRet || ((long)dwSize < (long)dwRet + 14))
380         lpszBuf[0] = TEXT('\0');
381     else
382     {
383         lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0');  /* remove cr and newline character */
384         _stprintf(lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError());
385     }
386
387     if (lpszTemp)
388         LocalFree((HLOCAL)lpszTemp);
389
390     return(lpszBuf);
391 }
392
393 BOOL CheckServiceArguments(int argc, char *argv[])
394 {
395     int i;
396
397     /* Lets process the arguments */
398     for (i = 1; i < argc; i++)
399     {
400         if (stricmp("-install", argv[i]) == 0)
401         {
402             /* They want to install the service */
403             CmdInstallService(argc, argv, FALSE);
404
405             /* We don't carry on, after we have installed the service */
406             return(FALSE);
407         }
408         else if (stricmp("-installa", argv[i]) == 0)
409         {
410             /* They want to install the service */
411             CmdInstallService(argc, argv, TRUE);
412
413             /* We don't carry on, after we have installed the service */
414             return(FALSE);
415         }
416         else if (stricmp("-remove", argv[i]) == 0)
417         {
418             /* Here they want to remove it */
419             CmdRemoveService();
420
421             /* We don't carry on, after we have removed the service */
422             return(FALSE);
423         }
424         else if (stricmp ("-runservice", argv[i]) == 0)
425         {
426             /* We can carry on, if we reached here */
427             chdir(argv[i+1]);
428             argv[i] = "";
429             argv[i+1] = "";
430             return(TRUE);
431         }
432     }
433     bRunAsService = FALSE;
434     return(TRUE);
435 }
436
437 BOOL SetupService(int argc, char *argv[], void *pHandle, LPTSTR pAppName, LPTSTR pServiceName, LPTSTR pServiceDisplayName, LPTSTR pDependancies)
438 {
439     BOOL bDeleteService = TRUE;
440     BOOL bResult = FALSE;
441
442     /* Save the handle for later use */
443     pAppHandle = pHandle;
444
445     /* Create our service class */
446     Service_Create(pAppName, pServiceName, pServiceDisplayName, pDependancies, argc, argv);
447
448     if (CheckServiceArguments(argc, argv))
449     {
450         if (bRunAsService)
451         {
452             /* No need to set the console control handler, as the service manager handles all this for us */
453             Service_Initialize();
454             bDeleteService = FALSE;
455         }
456         else
457         {
458             /* Set the console control handler for exiting the program */
459             SetConsoleCtrlHandler((PHANDLER_ROUTINE)EventHandlerRoutine, TRUE);
460
461             /* Now do the main work */
462             ServiceMain(argc, argv);
463         }
464
465         /* We have been successful initializing, so let the caller know */
466         bResult = TRUE;
467     }
468
469     if (bDeleteService)
470     {
471         /* Finished with the service now */
472         Service_Delete();
473     }
474     return(bResult);
475 }
476
477 BOOL EventHandlerRoutine(DWORD dwCtrlType)
478 {
479     /* This routine dosn't seem to get called all the time, Why ??? */
480     switch (dwCtrlType)
481     {
482         case CTRL_C_EVENT:        /* A CTRL+C signal was received, either from keyboard input or from a signal generated by the GenerateConsoleCtrlEvent function.*/
483         case CTRL_BREAK_EVENT:    /* A CTRL+BREAK signal was received, either from keyboard input or from a signal generated by GenerateConsoleCtrlEvent.*/
484         case CTRL_CLOSE_EVENT:    /* A signal that the system sends to all processes attached to a console when the user closes the console (either by choosing the Close command from the console window's System menu, or by choosing the End Task command from the Task List).*/
485         case CTRL_LOGOFF_EVENT:   /* A signal that the system sends to all console processes when a user is logging off. This signal does not indicate which user is logging off, so no assumptions can be made.*/
486         case CTRL_SHUTDOWN_EVENT: /* A signal that the system sends to all console processes when the system */
487             /* We are basically shutting down, so call Service_Delete */
488             Service_Delete();
489             return(FALSE);
490             break;
491
492         default:
493             /* we are not handling this one, so return FALSE */
494             return(FALSE);
495     }
496 }
497 #endif