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