]>
Commit | Line | Data |
---|---|---|
832b75ed | 1 | /* |
4d59bff9 | 2 | * os_win32/daemon_win32.cpp |
832b75ed | 3 | * |
a86ec89e | 4 | * Home page of code is: http://www.smartmontools.org |
832b75ed | 5 | * |
ff28b140 | 6 | * Copyright (C) 2004-18 Christian Franke |
832b75ed | 7 | * |
ff28b140 | 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
832b75ed GG |
9 | */ |
10 | ||
ee38a438 | 11 | #define WINVER 0x0600 |
cfbba5b9 GI |
12 | #define _WIN32_WINNT WINVER |
13 | ||
ee38a438 GI |
14 | #include "daemon_win32.h" |
15 | ||
ff28b140 | 16 | const char * daemon_win32_cpp_cvsid = "$Id: daemon_win32.cpp 4842 2018-12-02 16:07:26Z chrfranke $" |
ee38a438 GI |
17 | DAEMON_WIN32_H_CVSID; |
18 | ||
832b75ed GG |
19 | #include <stdio.h> |
20 | #include <stdlib.h> | |
21 | #include <signal.h> | |
22 | #include <io.h> | |
23 | ||
24 | #define WIN32_LEAN_AND_MEAN | |
832b75ed GG |
25 | #include <windows.h> |
26 | #ifdef _DEBUG | |
27 | #include <crtdbg.h> | |
28 | #endif | |
29 | ||
832b75ed GG |
30 | |
31 | ///////////////////////////////////////////////////////////////////////////// | |
32 | ||
832b75ed GG |
33 | // Prevent spawning of child process if debugging |
34 | #ifdef _DEBUG | |
35 | #define debugging() IsDebuggerPresent() | |
36 | #else | |
37 | #define debugging() FALSE | |
38 | #endif | |
39 | ||
40 | ||
41 | #define EVT_NAME_LEN 260 | |
42 | ||
43 | // Internal events (must be > SIGUSRn) | |
44 | #define EVT_RUNNING 100 // Exists when running, signaled on creation | |
45 | #define EVT_DETACHED 101 // Signaled when child detaches from console | |
46 | #define EVT_RESTART 102 // Signaled when child should restart | |
47 | ||
48 | static void make_name(char * name, int sig) | |
49 | { | |
ee38a438 GI |
50 | int i; |
51 | if (!GetModuleFileNameA(NULL, name, EVT_NAME_LEN-10)) | |
52 | strcpy(name, "DaemonEvent"); | |
53 | for (i = 0; name[i]; i++) { | |
54 | char c = name[i]; | |
55 | if (!( ('0' <= c && c <= '9') | |
56 | || ('A' <= c && c <= 'Z') | |
57 | || ('a' <= c && c <= 'z'))) | |
58 | name[i] = '_'; | |
59 | } | |
60 | sprintf(name+strlen(name), "-%d", sig); | |
832b75ed GG |
61 | } |
62 | ||
63 | ||
64 | static HANDLE create_event(int sig, BOOL initial, BOOL errmsg, BOOL * exists) | |
65 | { | |
ee38a438 GI |
66 | char name[EVT_NAME_LEN]; |
67 | HANDLE h; | |
68 | if (sig >= 0) | |
69 | make_name(name, sig); | |
70 | else | |
71 | name[0] = 0; | |
72 | if (exists) | |
73 | *exists = FALSE; | |
74 | if (!(h = CreateEventA(NULL, FALSE, initial, (name[0] ? name : NULL)))) { | |
75 | if (errmsg) | |
76 | fprintf(stderr, "CreateEvent(.,\"%s\"): Error=%ld\n", name, GetLastError()); | |
77 | return 0; | |
78 | } | |
79 | ||
80 | if (GetLastError() == ERROR_ALREADY_EXISTS) { | |
81 | if (!exists) { | |
82 | if (errmsg) | |
83 | fprintf(stderr, "CreateEvent(.,\"%s\"): Exists\n", name); | |
84 | CloseHandle(h); | |
85 | return 0; | |
86 | } | |
87 | *exists = TRUE; | |
88 | } | |
89 | return h; | |
832b75ed GG |
90 | } |
91 | ||
92 | ||
93 | static HANDLE open_event(int sig) | |
94 | { | |
ee38a438 GI |
95 | char name[EVT_NAME_LEN]; |
96 | make_name(name, sig); | |
97 | return OpenEventA(EVENT_MODIFY_STATE, FALSE, name); | |
832b75ed GG |
98 | } |
99 | ||
100 | ||
101 | static int event_exists(int sig) | |
102 | { | |
ee38a438 GI |
103 | char name[EVT_NAME_LEN]; |
104 | HANDLE h; | |
105 | make_name(name, sig); | |
106 | if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name))) | |
107 | return 0; | |
108 | CloseHandle(h); | |
109 | return 1; | |
832b75ed GG |
110 | } |
111 | ||
112 | ||
113 | static int sig_event(int sig) | |
114 | { | |
ee38a438 GI |
115 | char name[EVT_NAME_LEN]; |
116 | HANDLE h; | |
117 | make_name(name, sig); | |
118 | if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name))) { | |
119 | make_name(name, EVT_RUNNING); | |
120 | if (!(h = OpenEvent(EVENT_MODIFY_STATE, FALSE, name))) | |
121 | return -1; | |
122 | CloseHandle(h); | |
123 | return 0; | |
124 | } | |
125 | SetEvent(h); | |
126 | CloseHandle(h); | |
127 | return 1; | |
832b75ed GG |
128 | } |
129 | ||
130 | ||
131 | static void daemon_help(FILE * f, const char * ident, const char * message) | |
132 | { | |
ee38a438 GI |
133 | fprintf(f, |
134 | "%s: %s.\n" | |
135 | "Use \"%s status|stop|reload|restart|sigusr1|sigusr2\" to control daemon.\n", | |
136 | ident, message, ident); | |
137 | fflush(f); | |
832b75ed GG |
138 | } |
139 | ||
140 | ||
141 | ///////////////////////////////////////////////////////////////////////////// | |
142 | // Parent Process | |
143 | ||
144 | ||
145 | static BOOL WINAPI parent_console_handler(DWORD event) | |
146 | { | |
ee38a438 GI |
147 | switch (event) { |
148 | case CTRL_C_EVENT: | |
149 | case CTRL_BREAK_EVENT: | |
150 | return TRUE; // Ignore | |
151 | } | |
152 | return FALSE; // continue with next handler ... | |
832b75ed GG |
153 | } |
154 | ||
155 | ||
156 | static int parent_main(HANDLE rev) | |
157 | { | |
ee38a438 GI |
158 | HANDLE dev; |
159 | HANDLE ht[2]; | |
160 | char * cmdline; | |
161 | STARTUPINFO si; | |
162 | PROCESS_INFORMATION pi; | |
163 | DWORD rc, exitcode; | |
164 | ||
165 | // Ignore ^C, ^BREAK in parent | |
166 | SetConsoleCtrlHandler(parent_console_handler, TRUE/*add*/); | |
167 | ||
168 | // Create event used by child to signal daemon_detach() | |
169 | if (!(dev = create_event(EVT_DETACHED, FALSE/*not signaled*/, TRUE, NULL/*must not exist*/))) { | |
170 | CloseHandle(rev); | |
171 | return 101; | |
172 | } | |
173 | ||
174 | // Restart process with same args | |
175 | cmdline = GetCommandLineA(); | |
176 | memset(&si, 0, sizeof(si)); si.cb = sizeof(si); | |
177 | ||
178 | if (!CreateProcessA( | |
179 | NULL, cmdline, | |
180 | NULL, NULL, TRUE/*inherit*/, | |
181 | 0, NULL, NULL, &si, &pi)) { | |
182 | fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError()); | |
183 | CloseHandle(rev); CloseHandle(dev); | |
184 | return 101; | |
185 | } | |
186 | CloseHandle(pi.hThread); | |
187 | ||
188 | // Wait for daemon_detach() or exit() | |
189 | ht[0] = dev; ht[1] = pi.hProcess; | |
190 | rc = WaitForMultipleObjects(2, ht, FALSE/*or*/, INFINITE); | |
191 | if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+2)) { | |
192 | fprintf(stderr, "WaitForMultipleObjects returns %lX\n", rc); | |
193 | TerminateProcess(pi.hProcess, 200); | |
194 | } | |
195 | CloseHandle(rev); CloseHandle(dev); | |
196 | ||
197 | // Get exit code | |
198 | if (!GetExitCodeProcess(pi.hProcess, &exitcode)) | |
199 | exitcode = 201; | |
200 | else if (exitcode == STILL_ACTIVE) // detach()ed, assume OK | |
201 | exitcode = 0; | |
202 | ||
203 | CloseHandle(pi.hProcess); | |
204 | return exitcode; | |
832b75ed GG |
205 | } |
206 | ||
207 | ||
208 | ///////////////////////////////////////////////////////////////////////////// | |
209 | // Child Process | |
210 | ||
211 | ||
212 | static int svc_mode; // Running as service? | |
213 | static int svc_paused; // Service paused? | |
214 | ||
215 | static void service_report_status(int state, int waithint); | |
216 | ||
217 | ||
218 | // Tables of signal handler and corresponding events | |
219 | typedef void (*sigfunc_t)(int); | |
220 | ||
221 | #define MAX_SIG_HANDLERS 8 | |
222 | ||
223 | static int num_sig_handlers = 0; | |
224 | static sigfunc_t sig_handlers[MAX_SIG_HANDLERS]; | |
225 | static int sig_numbers[MAX_SIG_HANDLERS]; | |
226 | static HANDLE sig_events[MAX_SIG_HANDLERS]; | |
227 | ||
228 | static HANDLE sighup_handle, sigint_handle, sigbreak_handle; | |
229 | static HANDLE sigterm_handle, sigusr1_handle; | |
230 | ||
231 | static HANDLE running_event; | |
232 | ||
233 | static int reopen_stdin, reopen_stdout, reopen_stderr; | |
234 | ||
235 | ||
236 | // Handler for windows console events | |
237 | ||
238 | static BOOL WINAPI child_console_handler(DWORD event) | |
239 | { | |
ee38a438 GI |
240 | // Caution: runs in a new thread |
241 | // TODO: Guard with a mutex | |
242 | HANDLE h = 0; | |
243 | switch (event) { | |
244 | case CTRL_C_EVENT: // <CONTROL-C> (SIGINT) | |
245 | h = sigint_handle; break; | |
246 | case CTRL_BREAK_EVENT: // <CONTROL-Break> (SIGBREAK/SIGQUIT) | |
247 | case CTRL_CLOSE_EVENT: // User closed console or abort via task manager | |
248 | h = sigbreak_handle; break; | |
249 | case CTRL_LOGOFF_EVENT: // Logout/Shutdown (SIGTERM) | |
250 | case CTRL_SHUTDOWN_EVENT: | |
251 | h = sigterm_handle; break; | |
252 | } | |
253 | if (!h) | |
254 | return FALSE; // continue with next handler | |
255 | // Signal event | |
256 | if (!SetEvent(h)) | |
257 | return FALSE; | |
258 | return TRUE; | |
832b75ed GG |
259 | } |
260 | ||
261 | ||
262 | static void child_exit(void) | |
263 | { | |
ee38a438 GI |
264 | int i; |
265 | char * cmdline; | |
266 | HANDLE rst; | |
267 | STARTUPINFO si; | |
268 | PROCESS_INFORMATION pi; | |
269 | ||
270 | for (i = 0; i < num_sig_handlers; i++) | |
271 | CloseHandle(sig_events[i]); | |
272 | num_sig_handlers = 0; | |
273 | CloseHandle(running_event); running_event = 0; | |
274 | ||
275 | // Restart? | |
276 | if (!(rst = open_event(EVT_RESTART))) | |
277 | return; // No => normal exit | |
278 | ||
279 | // Yes => Signal exit and restart process | |
280 | Sleep(500); | |
281 | SetEvent(rst); | |
282 | CloseHandle(rst); | |
283 | Sleep(500); | |
284 | ||
285 | cmdline = GetCommandLineA(); | |
286 | memset(&si, 0, sizeof(si)); si.cb = sizeof(si); | |
287 | si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; | |
288 | ||
289 | if (!CreateProcessA( | |
290 | NULL, cmdline, | |
291 | NULL, NULL, TRUE/*inherit*/, | |
292 | 0, NULL, NULL, &si, &pi)) { | |
293 | fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError()); | |
294 | } | |
295 | CloseHandle(pi.hThread); CloseHandle(pi.hProcess); | |
832b75ed GG |
296 | } |
297 | ||
298 | static int child_main(HANDLE hev,int (*main_func)(int, char **), int argc, char **argv) | |
299 | { | |
ee38a438 GI |
300 | // Keep EVT_RUNNING open until exit |
301 | running_event = hev; | |
832b75ed | 302 | |
ee38a438 GI |
303 | // Install console handler |
304 | SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/); | |
832b75ed | 305 | |
ee38a438 GI |
306 | // Install restart handler |
307 | atexit(child_exit); | |
832b75ed | 308 | |
ee38a438 GI |
309 | // Continue in main_func() to do the real work |
310 | return main_func(argc, argv); | |
832b75ed GG |
311 | } |
312 | ||
313 | ||
314 | // Simulate signal() | |
315 | ||
316 | sigfunc_t daemon_signal(int sig, sigfunc_t func) | |
317 | { | |
ee38a438 GI |
318 | int i; |
319 | HANDLE h; | |
320 | if (func == SIG_DFL || func == SIG_IGN) | |
321 | return func; // TODO | |
322 | for (i = 0; i < num_sig_handlers; i++) { | |
323 | if (sig_numbers[i] == sig) { | |
324 | sigfunc_t old = sig_handlers[i]; | |
325 | sig_handlers[i] = func; | |
326 | return old; | |
327 | } | |
328 | } | |
329 | if (num_sig_handlers >= MAX_SIG_HANDLERS) | |
330 | return SIG_ERR; | |
331 | if (!(h = create_event((!svc_mode ? sig : -1), FALSE, TRUE, NULL))) | |
332 | return SIG_ERR; | |
333 | sig_events[num_sig_handlers] = h; | |
334 | sig_numbers[num_sig_handlers] = sig; | |
335 | sig_handlers[num_sig_handlers] = func; | |
336 | switch (sig) { | |
337 | case SIGHUP: sighup_handle = h; break; | |
338 | case SIGINT: sigint_handle = h; break; | |
339 | case SIGTERM: sigterm_handle = h; break; | |
340 | case SIGBREAK: sigbreak_handle = h; break; | |
341 | case SIGUSR1: sigusr1_handle = h; break; | |
342 | } | |
343 | num_sig_handlers++; | |
344 | return SIG_DFL; | |
832b75ed GG |
345 | } |
346 | ||
347 | ||
348 | // strsignal() | |
349 | ||
350 | const char * daemon_strsignal(int sig) | |
351 | { | |
ee38a438 GI |
352 | switch (sig) { |
353 | case SIGHUP: return "SIGHUP"; | |
354 | case SIGINT: return "SIGINT"; | |
355 | case SIGTERM: return "SIGTERM"; | |
356 | case SIGBREAK:return "SIGBREAK"; | |
357 | case SIGUSR1: return "SIGUSR1"; | |
358 | case SIGUSR2: return "SIGUSR2"; | |
359 | default: return "*UNKNOWN*"; | |
360 | } | |
832b75ed GG |
361 | } |
362 | ||
363 | ||
364 | // Simulate sleep() | |
365 | ||
366 | void daemon_sleep(int seconds) | |
367 | { | |
ee38a438 GI |
368 | do { |
369 | if (num_sig_handlers <= 0) { | |
370 | Sleep(seconds*1000L); | |
371 | } | |
372 | else { | |
373 | // Wait for any signal or timeout | |
374 | DWORD rc = WaitForMultipleObjects(num_sig_handlers, sig_events, | |
375 | FALSE/*OR*/, seconds*1000L); | |
376 | if (rc != WAIT_TIMEOUT) { | |
377 | if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+(unsigned)num_sig_handlers)) { | |
378 | fprintf(stderr,"WaitForMultipleObjects returns %lu\n", rc); | |
379 | Sleep(seconds*1000L); | |
380 | return; | |
381 | } | |
382 | // Call Handler | |
383 | sig_handlers[rc-WAIT_OBJECT_0](sig_numbers[rc-WAIT_OBJECT_0]); | |
384 | break; | |
385 | } | |
386 | } | |
387 | } while (svc_paused); | |
832b75ed GG |
388 | } |
389 | ||
390 | ||
391 | // Disable/Enable console | |
392 | ||
393 | void daemon_disable_console() | |
394 | { | |
ee38a438 GI |
395 | SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/); |
396 | reopen_stdin = reopen_stdout = reopen_stderr = 0; | |
397 | if (isatty(fileno(stdin))) { | |
398 | fclose(stdin); reopen_stdin = 1; | |
399 | } | |
400 | if (isatty(fileno(stdout))) { | |
401 | fclose(stdout); reopen_stdout = 1; | |
402 | } | |
403 | if (isatty(fileno(stderr))) { | |
404 | fclose(stderr); reopen_stderr = 1; | |
405 | } | |
406 | FreeConsole(); | |
407 | SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/); | |
832b75ed GG |
408 | } |
409 | ||
410 | int daemon_enable_console(const char * title) | |
411 | { | |
ee38a438 GI |
412 | BOOL ok; |
413 | SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/); | |
414 | ok = AllocConsole(); | |
415 | SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/); | |
416 | if (!ok) | |
417 | return -1; | |
418 | if (title) | |
419 | SetConsoleTitleA(title); | |
420 | if (reopen_stdin) | |
421 | freopen("conin$", "r", stdin); | |
422 | if (reopen_stdout) | |
423 | freopen("conout$", "w", stdout); | |
424 | if (reopen_stderr) | |
425 | freopen("conout$", "w", stderr); | |
426 | reopen_stdin = reopen_stdout = reopen_stderr = 0; | |
427 | return 0; | |
832b75ed GG |
428 | } |
429 | ||
430 | ||
431 | // Detach daemon from console & parent | |
432 | ||
433 | int daemon_detach(const char * ident) | |
434 | { | |
ee38a438 GI |
435 | if (!svc_mode) { |
436 | if (ident) { | |
437 | // Print help | |
438 | FILE * f = ( isatty(fileno(stdout)) ? stdout | |
439 | : isatty(fileno(stderr)) ? stderr : NULL); | |
440 | if (f) | |
441 | daemon_help(f, ident, "now detaches from console into background mode"); | |
442 | } | |
443 | // Signal detach to parent | |
444 | if (sig_event(EVT_DETACHED) != 1) { | |
445 | if (!debugging()) | |
446 | return -1; | |
447 | } | |
448 | daemon_disable_console(); | |
449 | } | |
450 | else { | |
451 | // Signal end of initialization to service control manager | |
452 | service_report_status(SERVICE_RUNNING, 0); | |
453 | reopen_stdin = reopen_stdout = reopen_stderr = 1; | |
454 | } | |
455 | ||
456 | return 0; | |
832b75ed GG |
457 | } |
458 | ||
459 | ||
832b75ed GG |
460 | ///////////////////////////////////////////////////////////////////////////// |
461 | // Initd Functions | |
462 | ||
463 | static int wait_signaled(HANDLE h, int seconds) | |
464 | { | |
ee38a438 GI |
465 | int i; |
466 | for (i = 0; ; ) { | |
467 | if (WaitForSingleObject(h, 1000L) == WAIT_OBJECT_0) | |
468 | return 0; | |
469 | if (++i >= seconds) | |
470 | return -1; | |
471 | fputchar('.'); fflush(stdout); | |
472 | } | |
832b75ed GG |
473 | } |
474 | ||
475 | ||
476 | static int wait_evt_running(int seconds, int exists) | |
477 | { | |
ee38a438 GI |
478 | int i; |
479 | if (event_exists(EVT_RUNNING) == exists) | |
480 | return 0; | |
481 | for (i = 0; ; ) { | |
482 | Sleep(1000); | |
483 | if (event_exists(EVT_RUNNING) == exists) | |
484 | return 0; | |
485 | if (++i >= seconds) | |
486 | return -1; | |
487 | fputchar('.'); fflush(stdout); | |
488 | } | |
832b75ed GG |
489 | } |
490 | ||
491 | ||
492 | static int is_initd_command(char * s) | |
493 | { | |
ee38a438 GI |
494 | if (!strcmp(s, "status")) |
495 | return EVT_RUNNING; | |
496 | if (!strcmp(s, "stop")) | |
497 | return SIGTERM; | |
498 | if (!strcmp(s, "reload")) | |
499 | return SIGHUP; | |
500 | if (!strcmp(s, "sigusr1")) | |
501 | return SIGUSR1; | |
502 | if (!strcmp(s, "sigusr2")) | |
503 | return SIGUSR2; | |
504 | if (!strcmp(s, "restart")) | |
505 | return EVT_RESTART; | |
506 | return -1; | |
832b75ed GG |
507 | } |
508 | ||
509 | ||
510 | static int initd_main(const char * ident, int argc, char **argv) | |
511 | { | |
ee38a438 GI |
512 | int rc; |
513 | if (argc < 2) | |
514 | return -1; | |
515 | if ((rc = is_initd_command(argv[1])) < 0) | |
516 | return -1; | |
517 | if (argc != 2) { | |
518 | printf("%s: no arguments allowed for command %s\n", ident, argv[1]); | |
519 | return 1; | |
520 | } | |
521 | ||
522 | switch (rc) { | |
523 | default: | |
524 | case EVT_RUNNING: | |
525 | printf("Checking for %s:", ident); fflush(stdout); | |
526 | rc = event_exists(EVT_RUNNING); | |
527 | puts(rc ? " running" : " not running"); | |
528 | return (rc ? 0 : 1); | |
529 | ||
530 | case SIGTERM: | |
531 | printf("Stopping %s:", ident); fflush(stdout); | |
532 | rc = sig_event(SIGTERM); | |
533 | if (rc <= 0) { | |
534 | puts(rc < 0 ? " not running" : " error"); | |
535 | return (rc < 0 ? 0 : 1); | |
536 | } | |
537 | rc = wait_evt_running(10, 0); | |
538 | puts(!rc ? " done" : " timeout"); | |
539 | return (!rc ? 0 : 1); | |
540 | ||
541 | case SIGHUP: | |
542 | printf("Reloading %s:", ident); fflush(stdout); | |
543 | rc = sig_event(SIGHUP); | |
544 | puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running"); | |
545 | return (rc > 0 ? 0 : 1); | |
546 | ||
547 | case SIGUSR1: | |
548 | case SIGUSR2: | |
549 | printf("Sending SIGUSR%d to %s:", (rc-SIGUSR1+1), ident); fflush(stdout); | |
550 | rc = sig_event(rc); | |
551 | puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running"); | |
552 | return (rc > 0 ? 0 : 1); | |
553 | ||
554 | case EVT_RESTART: | |
555 | { | |
556 | HANDLE rst; | |
557 | printf("Stopping %s:", ident); fflush(stdout); | |
558 | if (event_exists(EVT_DETACHED)) { | |
559 | puts(" not detached, cannot restart"); | |
560 | return 1; | |
561 | } | |
562 | if (!(rst = create_event(EVT_RESTART, FALSE, FALSE, NULL))) { | |
563 | puts(" error"); | |
564 | return 1; | |
565 | } | |
566 | rc = sig_event(SIGTERM); | |
567 | if (rc <= 0) { | |
568 | puts(rc < 0 ? " not running" : " error"); | |
569 | CloseHandle(rst); | |
570 | return 1; | |
571 | } | |
572 | rc = wait_signaled(rst, 10); | |
573 | CloseHandle(rst); | |
574 | if (rc) { | |
575 | puts(" timeout"); | |
576 | return 1; | |
577 | } | |
578 | puts(" done"); | |
579 | Sleep(100); | |
580 | ||
581 | printf("Starting %s:", ident); fflush(stdout); | |
582 | rc = wait_evt_running(10, 1); | |
583 | puts(!rc ? " done" : " error"); | |
584 | return (!rc ? 0 : 1); | |
585 | } | |
586 | } | |
832b75ed GG |
587 | } |
588 | ||
589 | ||
590 | ///////////////////////////////////////////////////////////////////////////// | |
591 | // Windows Service Functions | |
592 | ||
593 | int daemon_winsvc_exitcode; // Set by app to exit(code) | |
594 | ||
595 | static SERVICE_STATUS_HANDLE svc_handle; | |
596 | static SERVICE_STATUS svc_status; | |
597 | ||
598 | ||
599 | // Report status to SCM | |
600 | ||
601 | static void service_report_status(int state, int seconds) | |
602 | { | |
ee38a438 GI |
603 | // TODO: Avoid race |
604 | static DWORD checkpoint = 1; | |
605 | svc_status.dwCurrentState = state; | |
606 | svc_status.dwWaitHint = seconds*1000; | |
607 | switch (state) { | |
608 | default: | |
609 | svc_status.dwCheckPoint = checkpoint++; | |
610 | break; | |
611 | case SERVICE_RUNNING: | |
612 | case SERVICE_STOPPED: | |
613 | svc_status.dwCheckPoint = 0; | |
614 | } | |
615 | switch (state) { | |
616 | case SERVICE_START_PENDING: | |
617 | case SERVICE_STOP_PENDING: | |
618 | svc_status.dwControlsAccepted = 0; | |
619 | break; | |
620 | default: | |
621 | svc_status.dwControlsAccepted = | |
622 | SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN| | |
623 | SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_PARAMCHANGE; | |
624 | break; | |
625 | } | |
626 | SetServiceStatus(svc_handle, &svc_status); | |
832b75ed GG |
627 | } |
628 | ||
629 | ||
630 | // Control the service, called by SCM | |
631 | ||
632 | static void WINAPI service_control(DWORD ctrlcode) | |
633 | { | |
ee38a438 GI |
634 | switch (ctrlcode) { |
635 | case SERVICE_CONTROL_STOP: | |
636 | case SERVICE_CONTROL_SHUTDOWN: | |
637 | service_report_status(SERVICE_STOP_PENDING, 30); | |
638 | svc_paused = 0; | |
639 | SetEvent(sigterm_handle); | |
640 | break; | |
641 | case SERVICE_CONTROL_PARAMCHANGE: // Win2000/XP | |
642 | service_report_status(svc_status.dwCurrentState, 0); | |
643 | svc_paused = 0; | |
644 | SetEvent(sighup_handle); // reload | |
645 | break; | |
646 | case SERVICE_CONTROL_PAUSE: | |
647 | service_report_status(SERVICE_PAUSED, 0); | |
648 | svc_paused = 1; | |
649 | break; | |
650 | case SERVICE_CONTROL_CONTINUE: | |
651 | service_report_status(SERVICE_RUNNING, 0); | |
652 | { | |
653 | int was_paused = svc_paused; | |
654 | svc_paused = 0; | |
655 | SetEvent(was_paused ? sighup_handle : sigusr1_handle); // reload:recheck | |
656 | } | |
657 | break; | |
658 | case SERVICE_CONTROL_INTERROGATE: | |
659 | default: // unknown | |
660 | service_report_status(svc_status.dwCurrentState, 0); | |
661 | break; | |
662 | } | |
832b75ed GG |
663 | } |
664 | ||
665 | ||
666 | // Exit handler for service | |
667 | ||
668 | static void service_exit(void) | |
669 | { | |
ee38a438 GI |
670 | // Close signal events |
671 | int i; | |
672 | for (i = 0; i < num_sig_handlers; i++) | |
673 | CloseHandle(sig_events[i]); | |
674 | num_sig_handlers = 0; | |
675 | ||
676 | // Set exitcode | |
677 | if (daemon_winsvc_exitcode) { | |
678 | svc_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; | |
679 | svc_status.dwServiceSpecificExitCode = daemon_winsvc_exitcode; | |
680 | } | |
681 | // Report stopped | |
682 | service_report_status(SERVICE_STOPPED, 0); | |
832b75ed GG |
683 | } |
684 | ||
685 | ||
686 | // Variables for passing main(argc, argv) from daemon_main to service_main() | |
687 | static int (*svc_main_func)(int, char **); | |
688 | static int svc_main_argc; | |
689 | static char ** svc_main_argv; | |
690 | ||
691 | // Main function for service, called by service dispatcher | |
692 | ||
ee38a438 | 693 | static void WINAPI service_main(DWORD /*argc*/, LPSTR * argv) |
832b75ed | 694 | { |
ee38a438 GI |
695 | char path[MAX_PATH], *p; |
696 | ||
697 | // Register control handler | |
698 | svc_handle = RegisterServiceCtrlHandler(argv[0], service_control); | |
832b75ed | 699 | |
ee38a438 GI |
700 | // Init service status |
701 | svc_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; | |
702 | service_report_status(SERVICE_START_PENDING, 10); | |
832b75ed | 703 | |
ee38a438 GI |
704 | // Service started in \windows\system32, change to .exe directory |
705 | if (GetModuleFileNameA(NULL, path, sizeof(path)) && (p = strrchr(path, '\\'))) { | |
706 | *p = 0; SetCurrentDirectoryA(path); | |
707 | } | |
832b75ed | 708 | |
ee38a438 GI |
709 | // Install exit handler |
710 | atexit(service_exit); | |
832b75ed | 711 | |
ee38a438 GI |
712 | // Do the real work, service status later updated by daemon_detach() |
713 | daemon_winsvc_exitcode = svc_main_func(svc_main_argc, svc_main_argv); | |
832b75ed | 714 | |
ee38a438 GI |
715 | exit(daemon_winsvc_exitcode); |
716 | // ... continued in service_exit() | |
832b75ed GG |
717 | } |
718 | ||
719 | ||
720 | ///////////////////////////////////////////////////////////////////////////// | |
721 | // Windows Service Admin Functions | |
722 | ||
832b75ed | 723 | |
ee38a438 GI |
724 | // Make registry key name for event message file |
725 | static bool make_evtkey(char * buf, unsigned size, const char * ident) | |
726 | { | |
727 | static const char prefix[] = "SYSTEM\\CurrentControlSet\\Services\\Eventlog\\Application\\"; | |
728 | const unsigned pfxlen = sizeof(prefix)-1; | |
729 | unsigned idlen = strlen(ident); | |
730 | if (pfxlen + idlen >= size) { | |
731 | printf(" Buffer overflow\n"); | |
732 | return false; | |
733 | } | |
734 | memcpy(buf, prefix, pfxlen); | |
735 | memcpy(buf+pfxlen, ident, idlen+1); | |
736 | return true; | |
737 | } | |
738 | ||
739 | // Install this exe as event message file | |
740 | static void inst_evtmsg(const char * ident) | |
741 | { | |
742 | printf("Installing event message file for %s:", ident); fflush(stdout); | |
743 | ||
744 | char mypath[MAX_PATH]; | |
745 | if (!GetModuleFileNameA((HMODULE)0, mypath, sizeof(mypath))) { | |
746 | printf(" unknown program path, Error=%ld\n", GetLastError()); | |
747 | return; | |
748 | } | |
749 | ||
750 | char subkey[MAX_PATH]; | |
751 | if (!make_evtkey(subkey, sizeof(subkey), ident)) | |
752 | return; | |
753 | ||
754 | HKEY hk; | |
755 | LONG err = RegCreateKeyExA(HKEY_LOCAL_MACHINE, subkey, 0, (char *)0, 0, KEY_ALL_ACCESS, | |
756 | (SECURITY_ATTRIBUTES *)0, &hk, (DWORD *)0); | |
757 | if (err != ERROR_SUCCESS) { | |
758 | printf(" RegCreateKeyEx failed, error=%ld\n", err); | |
759 | return; | |
760 | } | |
761 | ||
762 | err = RegSetValueExA(hk, "EventMessageFile", 0, REG_SZ, | |
763 | (const BYTE *)mypath, strlen(mypath)+1); | |
764 | if (err == ERROR_SUCCESS) { | |
765 | DWORD val = EVENTLOG_INFORMATION_TYPE | |
766 | |EVENTLOG_WARNING_TYPE | |
767 | |EVENTLOG_ERROR_TYPE; | |
768 | err = RegSetValueExA(hk, "TypesSupported", 0, REG_DWORD, | |
769 | (const BYTE *)&val, sizeof(val)); | |
770 | } | |
771 | if (err != ERROR_SUCCESS) | |
772 | printf(" RegSetValueEx failed, error=%ld\n", err); | |
773 | ||
774 | RegCloseKey(hk); | |
775 | puts(" done"); | |
776 | } | |
777 | ||
778 | // Uninstall event message file | |
779 | static void uninst_evtmsg(const char * ident) | |
832b75ed | 780 | { |
ee38a438 GI |
781 | printf("Removing event message file for %s:", ident); fflush(stdout); |
782 | ||
783 | char subkey[MAX_PATH]; | |
784 | if (!make_evtkey(subkey, sizeof(subkey), ident)) | |
785 | return; | |
786 | ||
787 | LONG err = RegDeleteKeyA(HKEY_LOCAL_MACHINE, subkey); | |
788 | if (err != ERROR_SUCCESS && err != ERROR_FILE_NOT_FOUND) { | |
789 | printf(" RegDeleteKey failed, error=%ld\n", err); | |
790 | return; | |
791 | } | |
792 | puts(" done"); | |
832b75ed GG |
793 | } |
794 | ||
795 | ||
796 | // Service install/remove commands | |
797 | ||
798 | static int svcadm_main(const char * ident, const daemon_winsvc_options * svc_opts, | |
799 | int argc, char **argv ) | |
800 | { | |
ee38a438 GI |
801 | int remove; long err; |
802 | SC_HANDLE hm, hs; | |
803 | ||
804 | if (argc < 2) | |
805 | return -1; | |
806 | if (!strcmp(argv[1], "install")) | |
807 | remove = 0; | |
808 | else if (!strcmp(argv[1], "remove")) { | |
809 | if (argc != 2) { | |
810 | printf("%s: no arguments allowed for command remove\n", ident); | |
811 | return 1; | |
812 | } | |
813 | remove = 1; | |
814 | } | |
815 | else | |
816 | return -1; | |
817 | ||
818 | printf("%s service %s:", (!remove?"Installing":"Removing"), ident); fflush(stdout); | |
819 | ||
820 | // Open SCM | |
821 | if (!(hm = OpenSCManager(NULL/*local*/, NULL/*default*/, SC_MANAGER_ALL_ACCESS))) { | |
822 | if ((err = GetLastError()) == ERROR_ACCESS_DENIED) | |
823 | puts(" access to SCManager denied"); | |
824 | else | |
825 | printf(" cannot open SCManager, Error=%ld\n", err); | |
826 | return 1; | |
827 | } | |
828 | ||
829 | if (!remove) { | |
830 | char path[MAX_PATH+100]; | |
831 | int i; | |
832 | // Get program path | |
833 | if (!GetModuleFileNameA(NULL, path, MAX_PATH)) { | |
834 | printf(" unknown program path, Error=%ld\n", GetLastError()); | |
835 | CloseServiceHandle(hm); | |
836 | return 1; | |
837 | } | |
838 | // Add quotes if necessary | |
839 | if (strchr(path, ' ')) { | |
840 | i = strlen(path); | |
841 | path[i+1] = '"'; path[i+2] = 0; | |
842 | while (--i >= 0) | |
843 | path[i+1] = path[i]; | |
844 | path[0] = '"'; | |
845 | } | |
846 | // Append options | |
847 | strcat(path, " "); strcat(path, svc_opts->cmd_opt); | |
848 | for (i = 2; i < argc; i++) { | |
849 | const char * s = argv[i]; | |
850 | if (strlen(path)+1+1+strlen(s)+1 >= sizeof(path)) | |
851 | break; | |
852 | // Add quotes if necessary | |
853 | if (strchr(s, ' ') && !strchr(s, '"')) { | |
854 | strcat(path, " \""); strcat(path, s); strcat(path, "\""); | |
855 | } | |
856 | else { | |
857 | strcat(path, " "); strcat(path, s); | |
858 | } | |
859 | } | |
860 | // Create | |
861 | if (!(hs = CreateService(hm, | |
862 | svc_opts->svcname, svc_opts->dispname, | |
863 | SERVICE_ALL_ACCESS, | |
3d17a85c | 864 | SERVICE_WIN32_OWN_PROCESS, |
ee38a438 GI |
865 | SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path, |
866 | NULL/*no load ordering*/, NULL/*no tag id*/, | |
ff28b140 | 867 | ""/*no dependencies*/, NULL/*local system account*/, NULL/*no pw*/))) { |
ee38a438 GI |
868 | if ((err = GetLastError()) == ERROR_SERVICE_EXISTS) |
869 | puts(" the service is already installed"); | |
870 | else if (err == ERROR_SERVICE_MARKED_FOR_DELETE) | |
871 | puts(" service is still running and marked for deletion\n" | |
872 | "Stop the service and retry install"); | |
873 | else | |
874 | printf(" failed, Error=%ld\n", err); | |
875 | CloseServiceHandle(hm); | |
876 | return 1; | |
877 | } | |
878 | // Set optional description | |
879 | if (svc_opts->descript) { | |
880 | SERVICE_DESCRIPTIONA sd = { const_cast<char *>(svc_opts->descript) }; | |
881 | ChangeServiceConfig2A(hs, SERVICE_CONFIG_DESCRIPTION, &sd); | |
882 | } | |
883 | // Enable delayed auto start if supported | |
884 | OSVERSIONINFOA ver; ver.dwOSVersionInfoSize = sizeof(ver); | |
885 | if ( GetVersionExA(&ver) | |
886 | && ver.dwPlatformId == VER_PLATFORM_WIN32_NT | |
887 | && ver.dwMajorVersion >= 6 /* Vista */ ) { | |
d2e702cf GI |
888 | // SERVICE_{,CONFIG_}DELAYED_AUTO_START_INFO are missing in older MinGW headers |
889 | struct /* SERVICE_DELAYED_AUTO_START_INFO */ { | |
890 | BOOL fDelayedAutostart; | |
891 | } sdasi = { TRUE }; | |
892 | // typedef char ASSERT_sizeof_sdasi[sizeof(sdasi) == sizeof(SERVICE_DELAYED_AUTO_START_INFO) ? 1 : -1]; | |
893 | // typedef char ASSERT_const_scdasi[SERVICE_CONFIG_DELAYED_AUTO_START_INFO == 3 ? 1 : -1]; | |
894 | ChangeServiceConfig2A(hs, 3 /* SERVICE_CONFIG_DELAYED_AUTO_START_INFO */, &sdasi); | |
ee38a438 GI |
895 | } |
896 | } | |
897 | else { | |
898 | // Open | |
899 | if (!(hs = OpenService(hm, svc_opts->svcname, SERVICE_ALL_ACCESS))) { | |
900 | puts(" not found"); | |
901 | CloseServiceHandle(hm); | |
902 | return 1; | |
903 | } | |
904 | // TODO: Stop service if running | |
905 | // Remove | |
906 | if (!DeleteService(hs)) { | |
907 | if ((err = GetLastError()) == ERROR_SERVICE_MARKED_FOR_DELETE) | |
908 | puts(" service is still running and marked for deletion\n" | |
909 | "Stop the service to remove it"); | |
910 | else | |
911 | printf(" failed, Error=%ld\n", err); | |
912 | CloseServiceHandle(hs); CloseServiceHandle(hm); | |
913 | return 1; | |
914 | } | |
915 | } | |
916 | puts(" done"); | |
917 | CloseServiceHandle(hs); CloseServiceHandle(hm); | |
918 | ||
919 | // Install/Remove event message file registry entry | |
920 | if (!remove) { | |
921 | inst_evtmsg(ident); | |
922 | } | |
923 | else { | |
924 | uninst_evtmsg(ident); | |
925 | } | |
926 | ||
927 | return 0; | |
832b75ed GG |
928 | } |
929 | ||
930 | ||
931 | ///////////////////////////////////////////////////////////////////////////// | |
932 | // Main Function | |
933 | ||
934 | // This function must be called from main() | |
935 | // main_func is the function doing the real work | |
936 | ||
937 | int daemon_main(const char * ident, const daemon_winsvc_options * svc_opts, | |
938 | int (*main_func)(int, char **), int argc, char **argv ) | |
939 | { | |
ee38a438 | 940 | int rc; |
832b75ed | 941 | #ifdef _DEBUG |
ee38a438 GI |
942 | // Enable Debug heap checks |
943 | _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | |
944 | |_CRTDBG_ALLOC_MEM_DF|_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_LEAK_CHECK_DF); | |
832b75ed GG |
945 | #endif |
946 | ||
ee38a438 GI |
947 | // Check for [status|stop|reload|restart|sigusr1|sigusr2] parameters |
948 | if ((rc = initd_main(ident, argc, argv)) >= 0) | |
949 | return rc; | |
950 | // Check for [install|remove] parameters | |
951 | if (svc_opts && (rc = svcadm_main(ident, svc_opts, argc, argv)) >= 0) | |
952 | return rc; | |
953 | ||
954 | // Run as service if svc_opts.cmd_opt is given as first(!) argument | |
955 | svc_mode = (svc_opts && argc >= 2 && !strcmp(argv[1], svc_opts->cmd_opt)); | |
956 | ||
957 | if (!svc_mode) { | |
958 | // Daemon: Try to simulate a Unix-like daemon | |
959 | HANDLE rev; | |
960 | BOOL exists; | |
961 | ||
962 | // Create main event to detect process type: | |
963 | // 1. new: parent process => start child and wait for detach() or exit() of child. | |
964 | // 2. exists && signaled: child process => do the real work, signal detach() to parent | |
965 | // 3. exists && !signaled: already running => exit() | |
966 | if (!(rev = create_event(EVT_RUNNING, TRUE/*signaled*/, TRUE, &exists))) | |
967 | return 100; | |
968 | ||
969 | if (!exists && !debugging()) { | |
970 | // Event new => parent process | |
971 | return parent_main(rev); | |
972 | } | |
973 | ||
974 | if (WaitForSingleObject(rev, 0) == WAIT_OBJECT_0) { | |
975 | // Event was signaled => In child process | |
976 | return child_main(rev, main_func, argc, argv); | |
977 | } | |
978 | ||
979 | // Event no longer signaled => Already running! | |
980 | daemon_help(stdout, ident, "already running"); | |
981 | CloseHandle(rev); | |
982 | return 1; | |
983 | } | |
984 | else { | |
985 | // Service: Start service_main() via SCM | |
986 | SERVICE_TABLE_ENTRY service_table[] = { | |
987 | { (char*)svc_opts->svcname, service_main }, { NULL, NULL } | |
988 | }; | |
989 | ||
990 | svc_main_func = main_func; | |
991 | svc_main_argc = argc; | |
992 | svc_main_argv = argv; | |
993 | if (!StartServiceCtrlDispatcher(service_table)) { | |
994 | printf("%s: cannot dispatch service, Error=%ld\n" | |
995 | "Option \"%s\" cannot be used to start %s as a service from console.\n" | |
996 | "Use \"%s install ...\" to install the service\n" | |
997 | "and \"net start %s\" to start it.\n", | |
998 | ident, GetLastError(), svc_opts->cmd_opt, ident, ident, ident); | |
832b75ed GG |
999 | |
1000 | #ifdef _DEBUG | |
ee38a438 GI |
1001 | if (debugging()) |
1002 | service_main(argc, argv); | |
832b75ed | 1003 | #endif |
ee38a438 GI |
1004 | return 100; |
1005 | } | |
1006 | Sleep(1000); | |
1007 | ExitThread(0); // Do not redo exit() processing | |
1008 | /*NOTREACHED*/ | |
1009 | return 0; | |
1010 | } | |
832b75ed GG |
1011 | } |
1012 | ||
1013 | ||
1014 | ///////////////////////////////////////////////////////////////////////////// | |
1015 | // Test Program | |
1016 | ||
1017 | #ifdef TEST | |
1018 | ||
1019 | static volatile sig_atomic_t caughtsig = 0; | |
1020 | ||
1021 | static void sig_handler(int sig) | |
1022 | { | |
ee38a438 | 1023 | caughtsig = sig; |
832b75ed GG |
1024 | } |
1025 | ||
1026 | static void test_exit(void) | |
1027 | { | |
ee38a438 | 1028 | printf("Main exit\n"); |
832b75ed GG |
1029 | } |
1030 | ||
1031 | int test_main(int argc, char **argv) | |
1032 | { | |
ee38a438 GI |
1033 | int i; |
1034 | int debug = 0; | |
1035 | char * cmd = 0; | |
1036 | ||
1037 | printf("PID=%ld\n", GetCurrentProcessId()); | |
1038 | for (i = 0; i < argc; i++) { | |
1039 | printf("%d: \"%s\"\n", i, argv[i]); | |
1040 | if (!strcmp(argv[i],"-d")) | |
1041 | debug = 1; | |
1042 | } | |
1043 | if (argc > 1 && argv[argc-1][0] != '-') | |
1044 | cmd = argv[argc-1]; | |
1045 | ||
1046 | daemon_signal(SIGINT, sig_handler); | |
1047 | daemon_signal(SIGBREAK, sig_handler); | |
1048 | daemon_signal(SIGTERM, sig_handler); | |
1049 | daemon_signal(SIGHUP, sig_handler); | |
1050 | daemon_signal(SIGUSR1, sig_handler); | |
1051 | daemon_signal(SIGUSR2, sig_handler); | |
1052 | ||
1053 | atexit(test_exit); | |
1054 | ||
1055 | if (!debug) { | |
1056 | printf("Preparing to detach...\n"); | |
1057 | Sleep(2000); | |
1058 | daemon_detach("test"); | |
1059 | printf("Detached!\n"); | |
1060 | } | |
1061 | ||
1062 | for (;;) { | |
1063 | daemon_sleep(1); | |
1064 | printf("."); fflush(stdout); | |
1065 | if (caughtsig) { | |
1066 | if (caughtsig == SIGUSR2) { | |
1067 | debug ^= 1; | |
1068 | if (debug) | |
1069 | daemon_enable_console("Daemon[Debug]"); | |
1070 | else | |
1071 | daemon_disable_console(); | |
1072 | } | |
1073 | else if (caughtsig == SIGUSR1 && cmd) { | |
1074 | char inpbuf[200], outbuf[1000]; int rc; | |
1075 | strcpy(inpbuf, "Hello\nWorld!\n"); | |
1076 | rc = daemon_spawn(cmd, inpbuf, strlen(inpbuf), outbuf, sizeof(outbuf)); | |
1077 | if (!debug) | |
1078 | daemon_enable_console("Command output"); | |
1079 | printf("\"%s\" returns %d\n", cmd, rc); | |
1080 | if (rc >= 0) | |
1081 | printf("output:\n%s.\n", outbuf); | |
1082 | fflush(stdout); | |
1083 | if (!debug) { | |
1084 | Sleep(10000); daemon_disable_console(); | |
1085 | } | |
1086 | } | |
1087 | printf("[PID=%ld: Signal=%d]", GetCurrentProcessId(), caughtsig); fflush(stdout); | |
1088 | if (caughtsig == SIGTERM || caughtsig == SIGBREAK) | |
1089 | break; | |
1090 | caughtsig = 0; | |
1091 | } | |
1092 | } | |
1093 | printf("\nExiting on signal %d\n", caughtsig); | |
1094 | return 0; | |
832b75ed GG |
1095 | } |
1096 | ||
1097 | ||
1098 | int main(int argc, char **argv) | |
1099 | { | |
ee38a438 GI |
1100 | static const daemon_winsvc_options svc_opts = { |
1101 | "-s", "test", "Test Service", "Service to test daemon_win32.c Module" | |
1102 | }; | |
832b75ed | 1103 | |
ee38a438 | 1104 | return daemon_main("testd", &svc_opts, test_main, argc, argv); |
832b75ed GG |
1105 | } |
1106 | ||
1107 | #endif |