Happy new year
[yaz-moved-to-github.git] / src / sc.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2009 Index Data
3  * See the file LICENSE for details.
4  */
5
6 /**
7  * \file sc.c
8  * \brief Windows Service Control
9  */
10
11 #ifdef WIN32
12 #include <windows.h>
13 #include <tchar.h>
14 #include <direct.h>
15 #endif
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <yaz/xmalloc.h>
20 #include <yaz/log.h>
21 #include <yaz/sc.h>
22 #include <yaz/wrbuf.h>
23
24 struct sc_s {
25     int install_flag;
26     int start_flag;
27     int remove_flag;
28     int run_flag;
29     char *service_name;
30     char *display_name;
31     int (*sc_main)(yaz_sc_t s, int argc, char **argv);
32     void (*sc_stop)(yaz_sc_t s);
33     int argc;
34     char **argv;
35 #ifdef WIN32
36     SERVICE_STATUS_HANDLE   gSvcStatusHandle;
37     SERVICE_STATUS          gSvcStatus;
38 #endif
39 };
40
41
42 yaz_sc_t yaz_sc_create(const char *service_name, const char *display_name)
43 {
44     yaz_sc_t s = (yaz_sc_t) xmalloc(sizeof(*s));
45
46     s->service_name = service_name ? xstrdup(service_name) : 0;
47     s->display_name = display_name ? xstrdup(display_name) : 0;
48     s->install_flag = 0;
49     s->start_flag = 0;
50     s->remove_flag = 0;
51     s->run_flag = 0;
52     s->sc_main = 0;
53     s->sc_stop = 0;
54 #ifdef WIN32
55     s->gSvcStatusHandle = 0;
56 #endif
57     return s;
58 }
59
60 #ifdef WIN32
61 static void parse_args(yaz_sc_t s, int *argc_p, char ***argv_p)
62 {
63     int skip_opt = 0;
64     const char *log_file = 0;
65     int i;
66
67     /* now look for the service arguments */
68     skip_opt = 0;
69     for (i = 1; i < *argc_p; i++)
70     {
71         const char *opt = (*argv_p)[i];
72         if (!strcmp(opt, "-install"))
73         {
74             s->install_flag = 1;
75             skip_opt = 1;
76             break;
77         }
78         else if (!strcmp(opt, "-installa"))
79         {
80             s->install_flag = 1;
81             s->start_flag = 1;
82             skip_opt = 1;
83             break;
84         }
85         else if (!strcmp(opt, "-remove"))
86         {
87             s->remove_flag = 1;
88             skip_opt = 1;
89             break;
90         }
91         else if (!strcmp(opt, "-run") && i < *argc_p-1)
92         {
93             /* -run dir */
94             const char *dir = (*argv_p)[i+1];
95             s->run_flag = 1;
96             chdir(dir);
97             skip_opt = 2;
98             break;
99         }
100     }
101     *argc_p = *argc_p - skip_opt;
102     for (; i < *argc_p; i++)
103         (*argv_p)[i] = (*argv_p)[i + skip_opt];
104
105     /* now look for the service arguments */
106     /* we must have a YAZ log file to work with */
107     skip_opt = 0;
108     for (i = 1; i < *argc_p; i++)
109     {
110         const char *opt = (*argv_p)[i];
111         if (opt[0] == '-' && opt[1] == 'l')
112         {
113             if (opt[2])
114             {
115                 log_file = opt+2;
116                 skip_opt = 1;
117                 break;
118             }
119             else if (i < *argc_p - 1)
120             {
121                 log_file = (*argv_p)[i+1];
122                 skip_opt = 2;
123                 break;
124             }
125         }
126     }
127     if (log_file)
128         yaz_log_init_file(log_file);
129     else
130     {
131         if (s->install_flag)
132         {
133             yaz_log(YLOG_FATAL, "Must specify -l logfile for service to install");
134             exit(1);
135         }
136     }
137     if (s->run_flag)
138     {   /* remove  -l logfile for a running service */
139         *argc_p = *argc_p - skip_opt;
140         for (; i < *argc_p; i++)
141             (*argv_p)[i] = (*argv_p)[i + skip_opt];
142
143     }
144 }
145
146 VOID sc_ReportSvcStatus(yaz_sc_t s, 
147                         DWORD dwCurrentState,
148                         DWORD dwWin32ExitCode,
149                         DWORD dwWaitHint)
150 {
151     if (s->gSvcStatusHandle)
152     {
153         static DWORD dwCheckPoint = 1;
154
155         // Fill in the SERVICE_STATUS structure.
156
157         s->gSvcStatus.dwCurrentState = dwCurrentState;
158         s->gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
159         s->gSvcStatus.dwWaitHint = dwWaitHint;
160
161         if (dwCurrentState == SERVICE_START_PENDING)
162             s->gSvcStatus.dwControlsAccepted = 0;
163         else 
164             s->gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
165
166         if ( (dwCurrentState == SERVICE_RUNNING) ||
167              (dwCurrentState == SERVICE_STOPPED) )
168             s->gSvcStatus.dwCheckPoint = 0;
169         else 
170             s->gSvcStatus.dwCheckPoint = dwCheckPoint++;
171
172         // Report the status of the service to the SCM.
173         SetServiceStatus(s->gSvcStatusHandle, &s->gSvcStatus );
174     }
175 }
176
177 static yaz_sc_t global_sc = 0;
178
179 VOID WINAPI sc_SvcCtrlHandler(DWORD dwCtrl)                                                      
180 {
181     switch(dwCtrl) 
182     {  
183     case SERVICE_CONTROL_STOP: 
184         yaz_log(YLOG_LOG, "Service %s to stop", global_sc->service_name);
185         sc_ReportSvcStatus(global_sc, SERVICE_STOP_PENDING, NO_ERROR, 0);
186         global_sc->sc_stop(global_sc);
187         sc_ReportSvcStatus(global_sc, SERVICE_STOPPED, NO_ERROR, 0);
188         return;
189     case SERVICE_CONTROL_INTERROGATE: 
190         break; 
191     default: 
192         break;
193     }
194 }
195
196 static void WINAPI sc_service_main(DWORD argc, char **argv)
197 {
198     yaz_sc_t s = global_sc;
199     int ret_code;
200
201     yaz_log(YLOG_LOG, "Service %s starting", s->service_name);
202
203     s->gSvcStatusHandle = RegisterServiceCtrlHandler( 
204         s->service_name, sc_SvcCtrlHandler);
205
206     if (!s->gSvcStatusHandle)
207     { 
208         yaz_log(YLOG_FATAL|YLOG_ERRNO, "RegisterServiceCtrlHandler");
209         return; 
210     } 
211
212     s->gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 
213     s->gSvcStatus.dwServiceSpecificExitCode = 0;    
214
215     sc_ReportSvcStatus(s, SERVICE_START_PENDING, NO_ERROR, 3000);
216
217     ret_code = s->sc_main(s, s->argc, s->argv);
218         
219     sc_ReportSvcStatus(s, SERVICE_STOPPED,
220                        ret_code ? ERROR_SERVICE_SPECIFIC_ERROR : NO_ERROR, ret_code);
221 }
222 #endif
223
224 void yaz_sc_running(yaz_sc_t s)
225 {
226 #ifdef WIN32
227     sc_ReportSvcStatus(s, SERVICE_RUNNING, NO_ERROR, 0);
228 #endif
229 }
230
231 int yaz_sc_program(yaz_sc_t s, int argc, char **argv,
232                    int (*sc_main)(yaz_sc_t s, int argc, char **argv),
233                    void (*sc_stop)(yaz_sc_t s))
234
235 {
236     s->sc_main = sc_main;
237     s->sc_stop = sc_stop;
238 #ifdef WIN32
239     parse_args(s, &argc, &argv);
240
241     if (s->install_flag || s->remove_flag)
242     {
243         SC_HANDLE manager = OpenSCManager(NULL /* machine */,
244                                           NULL /* database */,
245                                           SC_MANAGER_ALL_ACCESS);
246         if (manager == NULL)
247         {
248             yaz_log(YLOG_FATAL|YLOG_ERRNO, "OpenSCManager failed");
249             exit(1);
250         }
251         if (s->install_flag)
252         {
253             SC_HANDLE schService = 0;
254             TCHAR szPath[2048];
255             int i;
256             WRBUF w = wrbuf_alloc();
257             char cwdstr[_MAX_PATH];
258
259             if (!_getcwd(cwdstr, sizeof(cwdstr)))
260                 strcpy (cwdstr, ".");
261
262             if (GetModuleFileName(NULL, szPath, 2048) == 0)
263             {
264                 yaz_log(YLOG_FATAL, "GetModuleFileName failed");
265                 exit(1);
266             }
267             wrbuf_puts(w, szPath);
268             for (i = 1; i < argc; i++)
269             {
270                 wrbuf_puts(w, " ");
271                 wrbuf_puts(w, argv[i]);
272             }
273             wrbuf_puts(w, " -run \"");
274             wrbuf_puts(w, cwdstr);
275             wrbuf_puts(w, "\"");
276             yaz_log(YLOG_LOG, "path: %s", wrbuf_cstr(w));
277
278             schService = 
279                 CreateService( 
280                     manager,          /* SCM database */
281                     TEXT(s->service_name), /* name of service */
282                     TEXT(s->display_name), /* service name to display */
283                     SERVICE_ALL_ACCESS,        /* desired access */
284                     SERVICE_WIN32_OWN_PROCESS, /* service type */
285                     s->start_flag ?
286                     SERVICE_AUTO_START : SERVICE_DEMAND_START, /* start type */
287                     SERVICE_ERROR_NORMAL,      /* error control type */
288                     wrbuf_cstr(w),             /* path to service's binary */
289                     NULL,                      /* no load ordering group */
290                     NULL,                      /* no tag identifier */
291                     NULL,                      /* no dependencies */
292                     NULL,                      /* LocalSystem account */
293                     NULL);                     /* no password */
294             if (schService == NULL) 
295             {
296                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Service %s could not be installed",
297                         s->service_name);
298                 CloseServiceHandle(manager);
299                 exit(1);
300             }
301             yaz_log(YLOG_LOG, "Installed service %s", s->service_name);
302             CloseServiceHandle(schService);
303             wrbuf_destroy(w);
304         }
305         else if (s->remove_flag)
306         {
307             SC_HANDLE schService = 0;
308             SERVICE_STATUS serviceStatus;
309                         
310             schService = OpenService(manager, TEXT(s->service_name), SERVICE_ALL_ACCESS);
311             if (schService == NULL) 
312             {
313                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Service %s could not be opened",
314                         s->service_name);
315                 CloseServiceHandle(manager);
316                 exit(1);
317             }
318             /* try to stop the service */
319             if (ControlService(schService, SERVICE_CONTROL_STOP, &serviceStatus))
320             {
321                 yaz_log(YLOG_LOG, "Service %s being stopped", s->service_name);
322                 Sleep(1000);
323                 while (QueryServiceStatus(schService, &serviceStatus))
324                 {
325                     if (serviceStatus.dwCurrentState != SERVICE_STOP_PENDING)
326                         break;
327                     Sleep( 1000 );
328                 }
329                 if (serviceStatus.dwCurrentState != SERVICE_STOPPED)
330                     yaz_log(YLOG_LOG|YLOG_FATAL, "Service failed to stop");
331             }
332             if (DeleteService(schService))
333                 yaz_log(YLOG_LOG, "Service %s removed", s->service_name);
334             else
335                 yaz_log(YLOG_FATAL, "Service %s could not be removed", s->service_name);
336             CloseServiceHandle(schService);
337         }
338         CloseServiceHandle(manager);
339         exit(0);
340     }
341     global_sc = s;
342     if (s->run_flag)
343     {
344         SERVICE_TABLE_ENTRY dt[2];
345
346         dt[0].lpServiceName = s->service_name;
347         dt[0].lpServiceProc = sc_service_main;
348         dt[1].lpServiceName = 0;
349         dt[1].lpServiceProc = 0;
350
351         s->argc = argc;
352         s->argv = argv;
353         if (!StartServiceCtrlDispatcher(dt))
354         {
355             yaz_log(YLOG_FATAL|YLOG_ERRNO, "Service %s could not be controlled",
356                     s->service_name);
357         }
358         return 0;
359     }
360 #endif /* WIN32 */
361     /* run the program standalone (with no service) */
362     return s->sc_main(s, argc, argv);
363 }
364
365 void yaz_sc_destroy(yaz_sc_t *s)
366 {
367     xfree((*s)->service_name);
368     xfree((*s)->display_name);
369     xfree(*s);
370     *s = 0;
371 }
372
373 /*
374  * Local variables:
375  * c-basic-offset: 4
376  * indent-tabs-mode: nil
377  * End:
378  * vim: shiftwidth=4 tabstop=8 expandtab
379  */
380