Re-implemented the element name encoding as Adams suggestion: <e tag="value"> when...
[yaz-moved-to-github.git] / src / sc.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2010 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                 if (strchr(argv[i], ' '))
272                     wrbuf_puts(w, "\"");
273                 wrbuf_puts(w, argv[i]);
274                 if (strchr(argv[i], ' '))
275                     wrbuf_puts(w, "\"");
276             }
277             wrbuf_puts(w, " -run \"");
278             wrbuf_puts(w, cwdstr);
279             wrbuf_puts(w, "\"");
280             yaz_log(YLOG_LOG, "path: %s", wrbuf_cstr(w));
281
282             schService = 
283                 CreateService( 
284                     manager,          /* SCM database */
285                     TEXT(s->service_name), /* name of service */
286                     TEXT(s->display_name), /* service name to display */
287                     SERVICE_ALL_ACCESS,        /* desired access */
288                     SERVICE_WIN32_OWN_PROCESS, /* service type */
289                     s->start_flag ?
290                     SERVICE_AUTO_START : SERVICE_DEMAND_START, /* start type */
291                     SERVICE_ERROR_NORMAL,      /* error control type */
292                     wrbuf_cstr(w),             /* path to service's binary */
293                     NULL,                      /* no load ordering group */
294                     NULL,                      /* no tag identifier */
295                     NULL,                      /* no dependencies */
296                     NULL,                      /* LocalSystem account */
297                     NULL);                     /* no password */
298             if (schService == NULL) 
299             {
300                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Service %s could not be installed",
301                         s->service_name);
302                 CloseServiceHandle(manager);
303                 exit(1);
304             }
305             yaz_log(YLOG_LOG, "Installed service %s", s->service_name);
306             CloseServiceHandle(schService);
307             wrbuf_destroy(w);
308         }
309         else if (s->remove_flag)
310         {
311             SC_HANDLE schService = 0;
312             SERVICE_STATUS serviceStatus;
313                         
314             schService = OpenService(manager, TEXT(s->service_name), SERVICE_ALL_ACCESS);
315             if (schService == NULL) 
316             {
317                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Service %s could not be opened",
318                         s->service_name);
319                 CloseServiceHandle(manager);
320                 exit(1);
321             }
322             /* try to stop the service */
323             if (ControlService(schService, SERVICE_CONTROL_STOP, &serviceStatus))
324             {
325                 yaz_log(YLOG_LOG, "Service %s being stopped", s->service_name);
326                 Sleep(1000);
327                 while (QueryServiceStatus(schService, &serviceStatus))
328                 {
329                     if (serviceStatus.dwCurrentState != SERVICE_STOP_PENDING)
330                         break;
331                     Sleep( 1000 );
332                 }
333                 if (serviceStatus.dwCurrentState != SERVICE_STOPPED)
334                     yaz_log(YLOG_LOG|YLOG_FATAL, "Service failed to stop");
335             }
336             if (DeleteService(schService))
337                 yaz_log(YLOG_LOG, "Service %s removed", s->service_name);
338             else
339                 yaz_log(YLOG_FATAL, "Service %s could not be removed", s->service_name);
340             CloseServiceHandle(schService);
341         }
342         CloseServiceHandle(manager);
343         exit(0);
344     }
345     global_sc = s;
346     if (s->run_flag)
347     {
348         SERVICE_TABLE_ENTRY dt[2];
349
350         dt[0].lpServiceName = s->service_name;
351         dt[0].lpServiceProc = sc_service_main;
352         dt[1].lpServiceName = 0;
353         dt[1].lpServiceProc = 0;
354
355         s->argc = argc;
356         s->argv = argv;
357         if (!StartServiceCtrlDispatcher(dt))
358         {
359             yaz_log(YLOG_FATAL|YLOG_ERRNO, "Service %s could not be controlled",
360                     s->service_name);
361         }
362         return 0;
363     }
364 #endif /* WIN32 */
365     /* run the program standalone (with no service) */
366     return s->sc_main(s, argc, argv);
367 }
368
369 void yaz_sc_destroy(yaz_sc_t *s)
370 {
371     xfree((*s)->service_name);
372     xfree((*s)->display_name);
373     xfree(*s);
374     *s = 0;
375 }
376
377 /*
378  * Local variables:
379  * c-basic-offset: 4
380  * c-file-style: "Stroustrup"
381  * indent-tabs-mode: nil
382  * End:
383  * vim: shiftwidth=4 tabstop=8 expandtab
384  */
385