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