root/trunk/smartmontools/os_win32/daemon_win32.cpp @ 3206

Revision 3206, 31.8 KB (checked in by chrfranke, 3 years ago)

daemon_win32.cpp: Remove duplicate assignment (Ticket #120).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * os_win32/daemon_win32.cpp
3 *
4 * Home page of code is: http://smartmontools.sourceforge.net
5 *
6 * Copyright (C) 2004-10 Christian Franke <smartmontools-support@lists.sourceforge.net>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2, or (at your option)
11 * any later version.
12 *
13 * You should have received a copy of the GNU General Public License
14 * (for example COPYING); if not, write to the Free
15 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 *
17 */
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <signal.h>
22#include <io.h>
23
24#define WIN32_LEAN_AND_MEAN
25// Need MB_SERVICE_NOTIFICATION (NT4/2000/XP), IsDebuggerPresent() (Win98/ME/NT4/2000/XP)
26#define _WIN32_WINNT 0x0400
27#include <windows.h>
28#ifdef _DEBUG
29#include <crtdbg.h>
30#endif
31
32#include "daemon_win32.h"
33
34const char * daemon_win32_cpp_cvsid = "$Id$"
35DAEMON_WIN32_H_CVSID;
36
37
38/////////////////////////////////////////////////////////////////////////////
39
40#define ARGUSED(x) ((void)(x))
41
42// Prevent spawning of child process if debugging
43#ifdef _DEBUG
44#define debugging() IsDebuggerPresent()
45#else
46#define debugging() FALSE
47#endif
48
49
50#define EVT_NAME_LEN 260
51
52// Internal events (must be > SIGUSRn)
53#define EVT_RUNNING   100 // Exists when running, signaled on creation
54#define EVT_DETACHED  101 // Signaled when child detaches from console
55#define EVT_RESTART   102 // Signaled when child should restart
56
57static void make_name(char * name, int sig)
58{
59        int i;
60        if (!GetModuleFileNameA(NULL, name, EVT_NAME_LEN-10))
61                strcpy(name, "DaemonEvent");
62        for (i = 0; name[i]; i++) {
63                char c = name[i];
64                if (!(   ('0' <= c && c <= '9')
65                      || ('A' <= c && c <= 'Z')
66                      || ('a' <= c && c <= 'z')))
67                          name[i] = '_';
68        }
69        sprintf(name+strlen(name), "-%d", sig);
70}
71
72
73static HANDLE create_event(int sig, BOOL initial, BOOL errmsg, BOOL * exists)
74{
75        char name[EVT_NAME_LEN];
76        HANDLE h;
77        if (sig >= 0)
78                make_name(name, sig);
79        else
80                name[0] = 0;
81        if (exists)
82                *exists = FALSE;
83        if (!(h = CreateEventA(NULL, FALSE, initial, (name[0] ? name : NULL)))) {
84                if (errmsg)
85                        fprintf(stderr, "CreateEvent(.,\"%s\"): Error=%ld\n", name, GetLastError());
86                return 0;
87        }
88
89        if (GetLastError() == ERROR_ALREADY_EXISTS) {
90                if (!exists) {
91                        if (errmsg)
92                                fprintf(stderr, "CreateEvent(.,\"%s\"): Exists\n", name);
93                        CloseHandle(h);
94                        return 0;
95                }
96                *exists = TRUE;
97        }
98        return h;
99}
100
101
102static HANDLE open_event(int sig)
103{
104        char name[EVT_NAME_LEN];
105        make_name(name, sig);
106        return OpenEventA(EVENT_MODIFY_STATE, FALSE, name);
107}
108
109
110static int event_exists(int sig)
111{
112        char name[EVT_NAME_LEN];
113        HANDLE h;
114        make_name(name, sig);
115        if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name)))
116                return 0;
117        CloseHandle(h);
118        return 1;
119}
120
121
122static int sig_event(int sig)
123{
124        char name[EVT_NAME_LEN];
125        HANDLE h;
126        make_name(name, sig);
127        if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name))) {
128                make_name(name, EVT_RUNNING);
129                if (!(h = OpenEvent(EVENT_MODIFY_STATE, FALSE, name)))
130                        return -1;
131                CloseHandle(h);
132                return 0;
133        }
134        SetEvent(h);
135        CloseHandle(h);
136        return 1;
137}
138
139
140static void daemon_help(FILE * f, const char * ident, const char * message)
141{
142        fprintf(f,
143                "%s: %s.\n"
144                "Use \"%s status|stop|reload|restart|sigusr1|sigusr2\" to control daemon.\n",
145                ident, message, ident);
146        fflush(f);
147}
148
149
150/////////////////////////////////////////////////////////////////////////////
151// Parent Process
152
153
154static BOOL WINAPI parent_console_handler(DWORD event)
155{
156        switch (event) {
157                case CTRL_C_EVENT:
158                case CTRL_BREAK_EVENT:
159                        return TRUE; // Ignore
160        }
161        return FALSE; // continue with next handler ...
162}
163
164
165static int parent_main(HANDLE rev)
166{
167        HANDLE dev;
168        HANDLE ht[2];
169        char * cmdline;
170        STARTUPINFO si;
171        PROCESS_INFORMATION pi;
172        DWORD rc, exitcode;
173
174        // Ignore ^C, ^BREAK in parent
175        SetConsoleCtrlHandler(parent_console_handler, TRUE/*add*/);
176
177        // Create event used by child to signal daemon_detach()
178        if (!(dev = create_event(EVT_DETACHED, FALSE/*not signaled*/, TRUE, NULL/*must not exist*/))) {
179                CloseHandle(rev);
180                return 101;
181        }
182
183        // Restart process with same args
184        cmdline = GetCommandLineA();
185        memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
186       
187        if (!CreateProcessA(
188                NULL, cmdline,
189                NULL, NULL, TRUE/*inherit*/,
190                0, NULL, NULL, &si, &pi)) {
191                fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError());
192                CloseHandle(rev); CloseHandle(dev);
193                return 101;
194        }
195        CloseHandle(pi.hThread);
196
197        // Wait for daemon_detach() or exit()
198        ht[0] = dev; ht[1] = pi.hProcess;
199        rc = WaitForMultipleObjects(2, ht, FALSE/*or*/, INFINITE);
200        if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+2)) {
201                fprintf(stderr, "WaitForMultipleObjects returns %lX\n", rc);
202                TerminateProcess(pi.hProcess, 200);
203        }
204        CloseHandle(rev); CloseHandle(dev);
205
206        // Get exit code
207        if (!GetExitCodeProcess(pi.hProcess, &exitcode))
208                exitcode = 201;
209        else if (exitcode == STILL_ACTIVE) // detach()ed, assume OK
210                exitcode = 0;
211
212        CloseHandle(pi.hProcess);
213        return exitcode;
214}
215
216
217/////////////////////////////////////////////////////////////////////////////
218// Child Process
219
220
221static int svc_mode;   // Running as service?
222static int svc_paused; // Service paused?
223
224static void service_report_status(int state, int waithint);
225
226
227// Tables of signal handler and corresponding events
228typedef void (*sigfunc_t)(int);
229
230#define MAX_SIG_HANDLERS 8
231
232static int num_sig_handlers = 0;
233static sigfunc_t sig_handlers[MAX_SIG_HANDLERS];
234static int sig_numbers[MAX_SIG_HANDLERS];
235static HANDLE sig_events[MAX_SIG_HANDLERS];
236
237static HANDLE sighup_handle, sigint_handle, sigbreak_handle;
238static HANDLE sigterm_handle, sigusr1_handle;
239
240static HANDLE running_event;
241
242static int reopen_stdin, reopen_stdout, reopen_stderr;
243
244
245// Handler for windows console events
246
247static BOOL WINAPI child_console_handler(DWORD event)
248{
249        // Caution: runs in a new thread
250        // TODO: Guard with a mutex
251        HANDLE h = 0;
252        switch (event) {
253                case CTRL_C_EVENT: // <CONTROL-C> (SIGINT)
254                        h = sigint_handle; break;
255                case CTRL_BREAK_EVENT: // <CONTROL-Break> (SIGBREAK/SIGQUIT)
256                case CTRL_CLOSE_EVENT: // User closed console or abort via task manager
257                        h = sigbreak_handle; break;
258                case CTRL_LOGOFF_EVENT: // Logout/Shutdown (SIGTERM)
259                case CTRL_SHUTDOWN_EVENT:
260                        h = sigterm_handle; break;
261        }
262        if (!h)
263                return FALSE; // continue with next handler
264        // Signal event
265        if (!SetEvent(h))
266                return FALSE;
267        return TRUE;
268}
269
270
271static void child_exit(void)
272{
273        int i;
274        char * cmdline;
275        HANDLE rst;
276        STARTUPINFO si;
277        PROCESS_INFORMATION pi;
278
279        for (i = 0; i < num_sig_handlers; i++)
280                CloseHandle(sig_events[i]);
281        num_sig_handlers = 0;
282        CloseHandle(running_event); running_event = 0;
283
284        // Restart?
285        if (!(rst = open_event(EVT_RESTART)))
286                return; // No => normal exit
287
288        // Yes => Signal exit and restart process
289        Sleep(500);
290        SetEvent(rst);
291        CloseHandle(rst);
292        Sleep(500);
293
294        cmdline = GetCommandLineA();
295        memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
296        si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE;
297
298        if (!CreateProcessA(
299                NULL, cmdline,
300                NULL, NULL, TRUE/*inherit*/,
301                0, NULL, NULL, &si, &pi)) {
302                fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError());
303        }
304        CloseHandle(pi.hThread); CloseHandle(pi.hProcess);
305}
306
307static int child_main(HANDLE hev,int (*main_func)(int, char **), int argc, char **argv)
308{
309        // Keep EVT_RUNNING open until exit
310        running_event = hev;
311
312        // Install console handler
313        SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/);
314
315        // Install restart handler
316        atexit(child_exit);
317
318        // Continue in main_func() to do the real work
319        return main_func(argc, argv);
320}
321
322
323// Simulate signal()
324
325sigfunc_t daemon_signal(int sig, sigfunc_t func)
326{
327        int i;
328        HANDLE h;
329        if (func == SIG_DFL || func == SIG_IGN)
330                return func; // TODO
331        for (i = 0; i < num_sig_handlers; i++) {
332                if (sig_numbers[i] == sig) {
333                        sigfunc_t old = sig_handlers[i];
334                        sig_handlers[i] = func;
335                        return old;
336                }
337        }
338        if (num_sig_handlers >= MAX_SIG_HANDLERS)
339                return SIG_ERR;
340        if (!(h = create_event((!svc_mode ? sig : -1), FALSE, TRUE, NULL)))
341                return SIG_ERR;
342        sig_events[num_sig_handlers]   = h;
343        sig_numbers[num_sig_handlers]  = sig;
344        sig_handlers[num_sig_handlers] = func;
345        switch (sig) {
346                case SIGHUP:   sighup_handle   = h; break;
347                case SIGINT:   sigint_handle   = h; break;
348                case SIGTERM:  sigterm_handle  = h; break;
349                case SIGBREAK: sigbreak_handle = h; break;
350                case SIGUSR1:  sigusr1_handle  = h; break;
351        }
352        num_sig_handlers++;
353        return SIG_DFL;
354}
355
356
357// strsignal()
358
359const char * daemon_strsignal(int sig)
360{
361        switch (sig) {
362                case SIGHUP:  return "SIGHUP";
363                case SIGINT:  return "SIGINT";
364                case SIGTERM: return "SIGTERM";
365                case SIGBREAK:return "SIGBREAK";
366                case SIGUSR1: return "SIGUSR1";
367                case SIGUSR2: return "SIGUSR2";
368                default:      return "*UNKNOWN*";
369        }
370}
371
372
373// Simulate sleep()
374
375void daemon_sleep(int seconds)
376{
377        do {
378                if (num_sig_handlers <= 0) {
379                        Sleep(seconds*1000L);
380                }
381                else {
382                        // Wait for any signal or timeout
383                        DWORD rc = WaitForMultipleObjects(num_sig_handlers, sig_events,
384                                FALSE/*OR*/, seconds*1000L);
385                        if (rc != WAIT_TIMEOUT) {
386                                if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+(unsigned)num_sig_handlers)) {
387                                        fprintf(stderr,"WaitForMultipleObjects returns %lu\n", rc);
388                                        Sleep(seconds*1000L);
389                                        return;
390                                }
391                                // Call Handler
392                                sig_handlers[rc-WAIT_OBJECT_0](sig_numbers[rc-WAIT_OBJECT_0]);
393                                break;
394                        }
395                }
396        } while (svc_paused);
397}
398
399
400// Disable/Enable console
401
402void daemon_disable_console()
403{
404        SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/);
405        reopen_stdin = reopen_stdout = reopen_stderr = 0;
406        if (isatty(fileno(stdin))) {
407                fclose(stdin); reopen_stdin = 1;
408        }
409        if (isatty(fileno(stdout))) {
410                fclose(stdout); reopen_stdout = 1;
411        }
412        if (isatty(fileno(stderr))) {
413                fclose(stderr); reopen_stderr = 1;
414        }
415        FreeConsole();
416        SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/);
417}
418
419int daemon_enable_console(const char * title)
420{
421        BOOL ok;
422        SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/);
423        ok = AllocConsole();
424        SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/);
425        if (!ok)
426                return -1;
427        if (title)
428                SetConsoleTitleA(title);
429        if (reopen_stdin)
430                freopen("conin$",  "r", stdin);
431        if (reopen_stdout)
432                freopen("conout$", "w", stdout);
433        if (reopen_stderr)
434                freopen("conout$", "w", stderr);
435        reopen_stdin = reopen_stdout = reopen_stderr = 0;
436        return 0;
437}
438
439
440// Detach daemon from console & parent
441
442int daemon_detach(const char * ident)
443{
444        if (!svc_mode) {
445                if (ident) {
446                        // Print help
447                        FILE * f = ( isatty(fileno(stdout)) ? stdout
448                                           : isatty(fileno(stderr)) ? stderr : NULL);
449                        if (f)
450                                daemon_help(f, ident, "now detaches from console into background mode");
451                }
452                // Signal detach to parent
453                if (sig_event(EVT_DETACHED) != 1) {
454                        if (!debugging())
455                                return -1;
456                }
457                daemon_disable_console();
458        }
459        else {
460                // Signal end of initialization to service control manager
461                service_report_status(SERVICE_RUNNING, 0);
462                reopen_stdin = reopen_stdout = reopen_stderr = 1;
463        }
464
465        return 0;
466}
467
468
469/////////////////////////////////////////////////////////////////////////////
470// MessageBox
471
472#ifndef _MT
473//MT runtime not necessary, because mbox_thread uses no unsafe lib functions
474//#error Program must be linked with multithreaded runtime library
475#endif
476
477static LONG mbox_count; // # mbox_thread()s
478static HANDLE mbox_mutex; // Show 1 box at a time (not necessary for service)
479
480typedef struct mbox_args_s {
481        HANDLE taken; const char * title, * text; int mode;
482} mbox_args;
483
484
485// Thread to display one message box
486
487static ULONG WINAPI mbox_thread(LPVOID arg)
488{
489        // Take args
490        mbox_args * mb = (mbox_args *)arg;
491        char title[100]; char text[1000]; int mode;
492        strncpy(title, mb->title, sizeof(title)-1); title[sizeof(title)-1] = 0;
493        strncpy(text , mb->text , sizeof(text )-1); text [sizeof(text )-1] = 0;
494        mode = mb->mode;
495        SetEvent(mb->taken);
496
497        // Show only one box at a time
498        WaitForSingleObject(mbox_mutex, INFINITE);
499        MessageBoxA(NULL, text, title, mode);
500        ReleaseMutex(mbox_mutex);
501
502        InterlockedDecrement(&mbox_count);
503        return 0;
504}
505
506
507// Display a message box
508int daemon_messagebox(int system, const char * title, const char * text)
509{
510        mbox_args mb;
511        HANDLE ht; DWORD tid;
512
513        // Create mutex during first call
514        if (!mbox_mutex)
515                mbox_mutex = CreateMutex(NULL/*!inherit*/, FALSE/*!owned*/, NULL/*unnamed*/);
516
517        // Allow at most 10 threads
518        if (InterlockedIncrement(&mbox_count) > 10) {
519                InterlockedDecrement(&mbox_count);
520                return -1;
521        }
522
523        // Create thread
524        mb.taken = CreateEvent(NULL/*!inherit*/, FALSE, FALSE/*!signaled*/, NULL/*unnamed*/);
525        mb.mode = MB_OK|MB_ICONWARNING
526                 |(svc_mode?MB_SERVICE_NOTIFICATION:0)
527                 |(system?MB_SYSTEMMODAL:MB_APPLMODAL);
528        mb.title = title;
529        mb.text = text;
530        if (!(ht = CreateThread(NULL, 0, mbox_thread, &mb, 0, &tid)))
531                return -1;
532
533        // Wait for args taken
534        if (WaitForSingleObject(mb.taken, 10000) != WAIT_OBJECT_0)
535                TerminateThread(ht, 0);
536        CloseHandle(mb.taken);
537        CloseHandle(ht);
538        return 0;
539}
540
541
542/////////////////////////////////////////////////////////////////////////////
543
544// Spawn a command and redirect <inpbuf >outbuf
545// return command's exitcode or -1 on error
546
547int daemon_spawn(const char * cmd,
548                 const char * inpbuf, int inpsize,
549                 char *       outbuf, int outsize )
550{
551        HANDLE pipe_inp_r, pipe_inp_w, pipe_out_r, pipe_out_w, pipe_err_w, h;
552        char temp_path[MAX_PATH];
553        DWORD flags, num_io, exitcode;
554        int use_file, state, i;
555        SECURITY_ATTRIBUTES sa;
556        STARTUPINFO si; PROCESS_INFORMATION pi;
557        HANDLE self = GetCurrentProcess();
558
559        if (GetVersion() & 0x80000000L) {
560                // Win9x/ME: A calling process never receives EOF if output of COMMAND.COM or
561                // any other DOS program is redirected via a pipe. Using a temp file instead.
562                use_file = 1; flags = DETACHED_PROCESS;
563        }
564        else {
565                // NT4/2000/XP: If DETACHED_PROCESS is used, CMD.EXE opens a new console window
566                // for each external command in a redirected .BAT file.
567                // Even (DETACHED_PROCESS|CREATE_NO_WINDOW) does not work.
568                use_file = 0; flags = CREATE_NO_WINDOW;
569        }
570
571        // Create stdin pipe with inheritable read side
572        memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa);
573        sa.bInheritHandle = TRUE;
574        if (!CreatePipe(&pipe_inp_r, &h, &sa/*inherit*/, inpsize*2+13))
575                return -1;
576        if (!DuplicateHandle(self, h, self, &pipe_inp_w,
577                0, FALSE/*!inherit*/, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE)) {
578                CloseHandle(pipe_inp_r);
579                return -1;
580        }
581
582        memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa);
583        sa.bInheritHandle = TRUE;
584        if (!use_file) {
585                // Create stdout pipe with inheritable write side
586                if (!CreatePipe(&h, &pipe_out_w, &sa/*inherit*/, outsize)) {
587                        CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
588                        return -1;
589                }
590        }
591        else {
592                // Create temp file with inheritable write handle
593                char temp_dir[MAX_PATH];
594                if (!GetTempPathA(sizeof(temp_dir), temp_dir))
595                        strcpy(temp_dir, ".");
596                if (!GetTempFileNameA(temp_dir, "out"/*prefix*/, 0/*create unique*/, temp_path)) {
597                        CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
598                        return -1;
599                }
600                if ((h = CreateFileA(temp_path, GENERIC_READ|GENERIC_WRITE,
601                        0/*no sharing*/, &sa/*inherit*/, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE) {
602                        CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
603                        return -1;
604                }
605                if (!DuplicateHandle(self, h, self, &pipe_out_w,
606                        GENERIC_WRITE, TRUE/*inherit*/, 0)) {
607                        CloseHandle(h); CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
608                        return -1;
609                }
610        }
611
612        if (!DuplicateHandle(self, h, self, &pipe_out_r,
613                GENERIC_READ, FALSE/*!inherit*/, DUPLICATE_CLOSE_SOURCE)) {
614                CloseHandle(pipe_out_w); CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
615                return -1;
616        }
617
618        // Create stderr handle as dup of stdout write side
619        if (!DuplicateHandle(self, pipe_out_w, self, &pipe_err_w,
620                0, TRUE/*inherit*/, DUPLICATE_SAME_ACCESS)) {
621                CloseHandle(pipe_out_r); CloseHandle(pipe_out_w);
622                CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
623                return -1;
624        }
625
626        // Create process with pipes/file as stdio
627        memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
628        si.hStdInput  = pipe_inp_r;
629        si.hStdOutput = pipe_out_w;
630        si.hStdError  = pipe_err_w;
631        si.dwFlags = STARTF_USESTDHANDLES;
632        if (!CreateProcessA(
633                NULL, (char*)cmd,
634                NULL, NULL, TRUE/*inherit*/,
635                flags/*DETACHED_PROCESS or CREATE_NO_WINDOW*/,
636                NULL, NULL, &si, &pi)) {
637                CloseHandle(pipe_err_w);
638                CloseHandle(pipe_out_r); CloseHandle(pipe_out_w);
639                CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w);
640                return -1;
641        }
642        CloseHandle(pi.hThread);
643        // Close inherited handles
644        CloseHandle(pipe_inp_r);
645        CloseHandle(pipe_out_w);
646        CloseHandle(pipe_err_w);
647
648        // Copy inpbuf to stdin
649        // convert \n => \r\n
650        for (i = 0; i < inpsize; ) {
651                int len = 0;
652                while (i+len < inpsize && inpbuf[i+len] != '\n')
653                        len++;
654                if (len > 0)
655                        WriteFile(pipe_inp_w, inpbuf+i, len, &num_io, NULL);
656                i += len;
657                if (i < inpsize) {
658                        WriteFile(pipe_inp_w, "\r\n", 2, &num_io, NULL);
659                        i++;
660                }
661        }
662        CloseHandle(pipe_inp_w);
663
664        exitcode = 42;
665        for (state = 0; state < 2; state++) {
666                // stdout pipe: read pipe first
667                // stdout file: wait for process first
668                if (state == use_file) {
669                        // Copy stdout to output buffer until full, rest to /dev/null
670                        // convert \r\n => \n
671                        if (use_file)
672                                SetFilePointer(pipe_out_r, 0, NULL, FILE_BEGIN);
673                        for (i = 0; ; ) {
674                                char buf[256];
675                                int j;
676                                if (!ReadFile(pipe_out_r, buf, sizeof(buf), &num_io, NULL) || num_io == 0)
677                                        break;
678                                for (j = 0; i < outsize-1 && j < (int)num_io; j++) {
679                                        if (buf[j] != '\r')
680                                                outbuf[i++] = buf[j];
681                                }
682                        }
683                        outbuf[i] = 0;
684                        CloseHandle(pipe_out_r);
685                        if (use_file)
686                                DeleteFileA(temp_path);
687                }
688                else {
689                        // Wait for process exitcode
690                        WaitForSingleObject(pi.hProcess, INFINITE);
691                        GetExitCodeProcess(pi.hProcess, &exitcode);
692                        CloseHandle(pi.hProcess);
693                }
694        }
695        return exitcode;
696}
697
698
699/////////////////////////////////////////////////////////////////////////////
700// Initd Functions
701
702static int wait_signaled(HANDLE h, int seconds)
703{
704        int i;
705        for (i = 0; ; ) {
706                if (WaitForSingleObject(h, 1000L) == WAIT_OBJECT_0)
707                        return 0;
708                if (++i >= seconds)
709                        return -1;
710                fputchar('.'); fflush(stdout);
711        }
712}
713
714
715static int wait_evt_running(int seconds, int exists)
716{
717        int i;
718        if (event_exists(EVT_RUNNING) == exists)
719                return 0;
720        for (i = 0; ; ) {
721                Sleep(1000);
722                if (event_exists(EVT_RUNNING) == exists)
723                        return 0;
724                if (++i >= seconds)
725                        return -1;
726                fputchar('.'); fflush(stdout);
727        }
728}
729
730
731static int is_initd_command(char * s)
732{
733        if (!strcmp(s, "status"))
734                return EVT_RUNNING;
735        if (!strcmp(s, "stop"))
736                return SIGTERM;
737        if (!strcmp(s, "reload"))
738                return SIGHUP;
739        if (!strcmp(s, "sigusr1"))
740                return SIGUSR1;
741        if (!strcmp(s, "sigusr2"))
742                return SIGUSR2;
743        if (!strcmp(s, "restart"))
744                return EVT_RESTART;
745        return -1;
746}
747
748
749static int initd_main(const char * ident, int argc, char **argv)
750{
751        int rc;
752        if (argc < 2)
753                return -1;
754        if ((rc = is_initd_command(argv[1])) < 0)
755                return -1;
756        if (argc != 2) {
757                printf("%s: no arguments allowed for command %s\n", ident, argv[1]);
758                return 1;
759        }
760
761        switch (rc) {
762                default:
763                case EVT_RUNNING:
764                        printf("Checking for %s:", ident); fflush(stdout);
765                        rc = event_exists(EVT_RUNNING);
766                        puts(rc ? " running" : " not running");
767                        return (rc ? 0 : 1);
768
769                case SIGTERM:
770                        printf("Stopping %s:", ident); fflush(stdout);
771                        rc = sig_event(SIGTERM);
772                        if (rc <= 0) {
773                                puts(rc < 0 ? " not running" : " error");
774                                return (rc < 0 ? 0 : 1);
775                        }
776                        rc = wait_evt_running(10, 0);
777                        puts(!rc ? " done" : " timeout");
778                        return (!rc ? 0 : 1);
779
780                case SIGHUP:
781                        printf("Reloading %s:", ident); fflush(stdout);
782                        rc = sig_event(SIGHUP);
783                        puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running");
784                        return (rc > 0 ? 0 : 1);
785
786                case SIGUSR1:
787                case SIGUSR2:
788                        printf("Sending SIGUSR%d to %s:", (rc-SIGUSR1+1), ident); fflush(stdout);
789                        rc = sig_event(rc);
790                        puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running");
791                        return (rc > 0 ? 0 : 1);
792
793                case EVT_RESTART:
794                        {
795                                HANDLE rst;
796                                printf("Stopping %s:", ident); fflush(stdout);
797                                if (event_exists(EVT_DETACHED)) {
798                                        puts(" not detached, cannot restart");
799                                        return 1;
800                                }
801                                if (!(rst = create_event(EVT_RESTART, FALSE, FALSE, NULL))) {
802                                        puts(" error");
803                                        return 1;
804                                }
805                                rc = sig_event(SIGTERM);
806                                if (rc <= 0) {
807                                        puts(rc < 0 ? " not running" : " error");
808                                        CloseHandle(rst);
809                                        return 1;
810                                }
811                                rc = wait_signaled(rst, 10);
812                                CloseHandle(rst);
813                                if (rc) {
814                                        puts(" timeout");
815                                        return 1;
816                                }
817                                puts(" done");
818                                Sleep(100);
819
820                                printf("Starting %s:", ident); fflush(stdout);
821                                rc = wait_evt_running(10, 1);
822                                puts(!rc ? " done" : " error");
823                                return (!rc ? 0 : 1);
824                        }
825        }
826}
827
828
829/////////////////////////////////////////////////////////////////////////////
830// Windows Service Functions
831
832int daemon_winsvc_exitcode; // Set by app to exit(code)
833
834static SERVICE_STATUS_HANDLE svc_handle;
835static SERVICE_STATUS svc_status;
836
837
838// Report status to SCM
839
840static void service_report_status(int state, int seconds)
841{
842        // TODO: Avoid race
843        static DWORD checkpoint = 1;
844        static DWORD accept_more = SERVICE_ACCEPT_PARAMCHANGE; // Win2000/XP
845        svc_status.dwCurrentState = state;
846        svc_status.dwWaitHint = seconds*1000;
847        switch (state) {
848                default:
849                        svc_status.dwCheckPoint = checkpoint++;
850                        break;
851                case SERVICE_RUNNING:
852                case SERVICE_STOPPED:
853                        svc_status.dwCheckPoint = 0;
854        }
855        switch (state) {
856                case SERVICE_START_PENDING:
857                case SERVICE_STOP_PENDING:
858                        svc_status.dwControlsAccepted = 0;
859                        break;
860                default:
861                        svc_status.dwControlsAccepted =
862                                SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|
863                                SERVICE_ACCEPT_PAUSE_CONTINUE|accept_more;
864                        break;
865        }
866        if (!SetServiceStatus(svc_handle, &svc_status)) {
867                if (svc_status.dwControlsAccepted & accept_more) {
868                        // Retry without SERVICE_ACCEPT_PARAMCHANGE (WinNT4)
869                        svc_status.dwControlsAccepted &= ~accept_more;
870                        accept_more = 0;
871                        SetServiceStatus(svc_handle, &svc_status);
872                }
873        }
874}
875
876
877// Control the service, called by SCM
878
879static void WINAPI service_control(DWORD ctrlcode)
880{
881        switch (ctrlcode) {
882                case SERVICE_CONTROL_STOP:
883                case SERVICE_CONTROL_SHUTDOWN:
884                        service_report_status(SERVICE_STOP_PENDING, 30);
885                        svc_paused = 0;
886                        SetEvent(sigterm_handle);
887                        break;
888                case SERVICE_CONTROL_PARAMCHANGE: // Win2000/XP
889                        service_report_status(svc_status.dwCurrentState, 0);
890                        svc_paused = 0;
891                        SetEvent(sighup_handle); // reload
892                        break;
893                case SERVICE_CONTROL_PAUSE:
894                        service_report_status(SERVICE_PAUSED, 0);
895                        svc_paused = 1;
896                        break;
897                case SERVICE_CONTROL_CONTINUE:
898                        service_report_status(SERVICE_RUNNING, 0);
899                        {
900                                int was_paused = svc_paused;
901                                svc_paused = 0;
902                                SetEvent(was_paused ? sighup_handle : sigusr1_handle); // reload:recheck
903                        }
904                        break;
905                case SERVICE_CONTROL_INTERROGATE:
906                default: // unknown
907                        service_report_status(svc_status.dwCurrentState, 0);
908                        break;
909        }
910}
911
912
913// Exit handler for service
914
915static void service_exit(void)
916{
917        // Close signal events
918        int i;
919        for (i = 0; i < num_sig_handlers; i++)
920                CloseHandle(sig_events[i]);
921        num_sig_handlers = 0;
922
923        // Set exitcode
924        if (daemon_winsvc_exitcode) {
925                svc_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
926                svc_status.dwServiceSpecificExitCode = daemon_winsvc_exitcode;
927        }
928        // Report stopped
929        service_report_status(SERVICE_STOPPED, 0);
930}
931
932
933// Variables for passing main(argc, argv) from daemon_main to service_main()
934static int (*svc_main_func)(int, char **);
935static int svc_main_argc;
936static char ** svc_main_argv;
937
938// Main function for service, called by service dispatcher
939
940static void WINAPI service_main(DWORD argc, LPSTR * argv)
941{
942        char path[MAX_PATH], *p;
943        ARGUSED(argc);
944
945        // Register control handler
946        svc_handle = RegisterServiceCtrlHandler(argv[0], service_control);
947
948        // Init service status
949        svc_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS;
950        service_report_status(SERVICE_START_PENDING, 10);
951
952        // Service started in \windows\system32, change to .exe directory
953        if (GetModuleFileNameA(NULL, path, sizeof(path)) && (p = strrchr(path, '\\'))) {
954                *p = 0; SetCurrentDirectoryA(path);
955        }
956       
957        // Install exit handler
958        atexit(service_exit);
959
960        // Do the real work, service status later updated by daemon_detach()
961        daemon_winsvc_exitcode = svc_main_func(svc_main_argc, svc_main_argv);
962
963        exit(daemon_winsvc_exitcode);
964        // ... continued in service_exit()
965}
966
967
968/////////////////////////////////////////////////////////////////////////////
969// Windows Service Admin Functions
970
971// Set Service description (Win2000/XP)
972
973static int svcadm_setdesc(SC_HANDLE hs, const char * desc)
974{
975        HINSTANCE hdll;
976        BOOL (WINAPI * ChangeServiceConfig2A_p)(SC_HANDLE, DWORD, LPVOID);
977        BOOL ret;
978        if (!(hdll = LoadLibraryA("ADVAPI32.DLL")))
979                return FALSE;
980        if (!((ChangeServiceConfig2A_p = (BOOL (WINAPI *)(SC_HANDLE, DWORD, LPVOID))GetProcAddress(hdll, "ChangeServiceConfig2A"))))
981                ret = FALSE;
982        else {
983                SERVICE_DESCRIPTIONA sd = { (char *)desc };
984                ret = ChangeServiceConfig2A_p(hs, SERVICE_CONFIG_DESCRIPTION, &sd);
985        }
986        FreeLibrary(hdll);
987        return ret;
988}
989
990
991// Service install/remove commands
992
993static int svcadm_main(const char * ident, const daemon_winsvc_options * svc_opts,
994                       int argc, char **argv                                      )
995{
996        int remove; long err;
997        SC_HANDLE hm, hs;
998
999        if (argc < 2)
1000                return -1;
1001        if (!strcmp(argv[1], "install"))
1002                remove = 0;
1003        else if (!strcmp(argv[1], "remove")) {
1004                if (argc != 2) {
1005                        printf("%s: no arguments allowed for command remove\n", ident);
1006                        return 1;
1007                }
1008                remove = 1;
1009        }
1010        else
1011                return -1;
1012
1013        printf("%s service %s:", (!remove?"Installing":"Removing"), ident); fflush(stdout);
1014
1015        // Open SCM
1016        if (!(hm = OpenSCManager(NULL/*local*/, NULL/*default*/, SC_MANAGER_ALL_ACCESS))) {
1017                if ((err = GetLastError()) == ERROR_ACCESS_DENIED)
1018                        puts(" access to SCManager denied");
1019                else if (err == ERROR_CALL_NOT_IMPLEMENTED)
1020                        puts(" services not implemented on this version of Windows");
1021                else
1022                        printf(" cannot open SCManager, Error=%ld\n", err);
1023                return 1;
1024        }
1025
1026        if (!remove) {
1027                char path[MAX_PATH+100];
1028                int i;
1029                // Get program path
1030                if (!GetModuleFileNameA(NULL, path, MAX_PATH)) {
1031                        printf(" unknown program path, Error=%ld\n", GetLastError());
1032                        CloseServiceHandle(hm);
1033                        return 1;
1034                }
1035                // Append options
1036                strcat(path, " "); strcat(path, svc_opts->cmd_opt);
1037                for (i = 2; i < argc; i++) {
1038                        const char * s = argv[i];
1039                        if (strlen(path)+strlen(s)+1 >= sizeof(path))
1040                                break;
1041                        strcat(path, " "); strcat(path, s);
1042                }
1043                // Create
1044                if (!(hs = CreateService(hm,
1045                        svc_opts->svcname, svc_opts->dispname,
1046                        SERVICE_ALL_ACCESS,
1047                        SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS,
1048                        SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path,
1049                        NULL/*no load ordering*/, NULL/*no tag id*/,
1050                        ""/*no depedencies*/, NULL/*local system account*/, NULL/*no pw*/))) {
1051                        if ((err = GetLastError()) == ERROR_SERVICE_EXISTS)
1052                                puts(" the service is already installed");
1053                        else if (err == ERROR_SERVICE_MARKED_FOR_DELETE)
1054                                puts(" service is still running and marked for deletion\n"
1055                                     "Stop the service and retry install");
1056                        else
1057                                printf(" failed, Error=%ld\n", err);
1058                        CloseServiceHandle(hm);
1059                        return 1;
1060                }
1061                // Set optional description
1062                if (svc_opts->descript)
1063                        svcadm_setdesc(hs, svc_opts->descript);
1064        }
1065        else {
1066                // Open
1067                if (!(hs = OpenService(hm, svc_opts->svcname, SERVICE_ALL_ACCESS))) {
1068                        puts(" not found");
1069                        CloseServiceHandle(hm);
1070                        return 1;
1071                }
1072                // TODO: Stop service if running
1073                // Remove
1074                if (!DeleteService(hs)) {
1075                        if ((err = GetLastError()) == ERROR_SERVICE_MARKED_FOR_DELETE)
1076                                puts(" service is still running and marked for deletion\n"
1077                                     "Stop the service to remove it");
1078                        else
1079                                printf(" failed, Error=%ld\n", err);
1080                        CloseServiceHandle(hs); CloseServiceHandle(hm);
1081                        return 1;
1082                }
1083        }
1084        puts(" done");
1085        CloseServiceHandle(hs); CloseServiceHandle(hm);
1086        return 0;
1087}
1088
1089
1090/////////////////////////////////////////////////////////////////////////////
1091// Main Function
1092
1093// This function must be called from main()
1094// main_func is the function doing the real work
1095
1096int daemon_main(const char * ident, const daemon_winsvc_options * svc_opts,
1097                int (*main_func)(int, char **), int argc, char **argv      )
1098{
1099        int rc;
1100#ifdef _DEBUG
1101        // Enable Debug heap checks
1102        _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)
1103                |_CRTDBG_ALLOC_MEM_DF|_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_LEAK_CHECK_DF);
1104#endif
1105
1106        // Check for [status|stop|reload|restart|sigusr1|sigusr2] parameters
1107        if ((rc = initd_main(ident, argc, argv)) >= 0)
1108                return rc;
1109        // Check for [install|remove] parameters
1110        if (svc_opts && (rc = svcadm_main(ident, svc_opts, argc, argv)) >= 0)
1111                return rc;
1112
1113        // Run as service if svc_opts.cmd_opt is given as first(!) argument
1114        svc_mode = (svc_opts && argc >= 2 && !strcmp(argv[1], svc_opts->cmd_opt));
1115
1116        if (!svc_mode) {
1117                // Daemon: Try to simulate a Unix-like daemon
1118                HANDLE rev;
1119                BOOL exists;
1120
1121                // Create main event to detect process type:
1122                // 1. new: parent process => start child and wait for detach() or exit() of child.
1123                // 2. exists && signaled: child process => do the real work, signal detach() to parent
1124                // 3. exists && !signaled: already running => exit()
1125                if (!(rev = create_event(EVT_RUNNING, TRUE/*signaled*/, TRUE, &exists)))
1126                        return 100;
1127
1128                if (!exists && !debugging()) {
1129                        // Event new => parent process
1130                        return parent_main(rev);
1131                }
1132
1133                if (WaitForSingleObject(rev, 0) == WAIT_OBJECT_0) {
1134                        // Event was signaled => In child process
1135                        return child_main(rev, main_func, argc, argv);
1136                }
1137
1138                // Event no longer signaled => Already running!
1139                daemon_help(stdout, ident, "already running");
1140                CloseHandle(rev);
1141                return 1;
1142        }
1143        else {
1144                // Service: Start service_main() via SCM
1145                SERVICE_TABLE_ENTRY service_table[] = {
1146                        { (char*)svc_opts->svcname, service_main }, { NULL, NULL }
1147                };
1148
1149                svc_main_func = main_func;
1150                svc_main_argc = argc;
1151                svc_main_argv = argv;
1152                if (!StartServiceCtrlDispatcher(service_table)) {
1153                        printf("%s: cannot dispatch service, Error=%ld\n"
1154                                "Option \"%s\" cannot be used to start %s as a service from console.\n"
1155                                "Use \"%s install ...\" to install the service\n"
1156                                "and \"net start %s\" to start it.\n",
1157                                ident, GetLastError(), svc_opts->cmd_opt, ident, ident, ident);
1158
1159#ifdef _DEBUG
1160                        if (debugging())
1161                                service_main(argc, argv);
1162#endif
1163                        return 100;
1164                }
1165                Sleep(1000);
1166                ExitThread(0); // Do not redo exit() processing
1167                /*NOTREACHED*/
1168                return 0;
1169        }
1170}
1171
1172
1173/////////////////////////////////////////////////////////////////////////////
1174// Test Program
1175
1176#ifdef TEST
1177
1178static volatile sig_atomic_t caughtsig = 0;
1179
1180static void sig_handler(int sig)
1181{
1182        caughtsig = sig;
1183}
1184
1185static void test_exit(void)
1186{
1187        printf("Main exit\n");
1188}
1189
1190int test_main(int argc, char **argv)
1191{
1192        int i;
1193        int debug = 0;
1194        char * cmd = 0;
1195
1196        printf("PID=%ld\n", GetCurrentProcessId());
1197        for (i = 0; i < argc; i++) {
1198                printf("%d: \"%s\"\n", i, argv[i]);
1199                if (!strcmp(argv[i],"-d"))
1200                        debug = 1;
1201        }
1202        if (argc > 1 && argv[argc-1][0] != '-')
1203                cmd = argv[argc-1];
1204
1205        daemon_signal(SIGINT, sig_handler);
1206        daemon_signal(SIGBREAK, sig_handler);
1207        daemon_signal(SIGTERM, sig_handler);
1208        daemon_signal(SIGHUP, sig_handler);
1209        daemon_signal(SIGUSR1, sig_handler);
1210        daemon_signal(SIGUSR2, sig_handler);
1211
1212        atexit(test_exit);
1213
1214        if (!debug) {
1215                printf("Preparing to detach...\n");
1216                Sleep(2000);
1217                daemon_detach("test");
1218                printf("Detached!\n");
1219        }
1220
1221        for (;;) {
1222                daemon_sleep(1);
1223                printf("."); fflush(stdout);
1224                if (caughtsig) {
1225                        if (caughtsig == SIGUSR2) {
1226                                debug ^= 1;
1227                                if (debug)
1228                                        daemon_enable_console("Daemon[Debug]");
1229                                else
1230                                        daemon_disable_console();
1231                        }
1232                        else if (caughtsig == SIGUSR1 && cmd) {
1233                                char inpbuf[200], outbuf[1000]; int rc;
1234                                strcpy(inpbuf, "Hello\nWorld!\n");
1235                                rc = daemon_spawn(cmd, inpbuf, strlen(inpbuf), outbuf, sizeof(outbuf));
1236                                if (!debug)
1237                                        daemon_enable_console("Command output");
1238                                printf("\"%s\" returns %d\n", cmd, rc);
1239                                if (rc >= 0)
1240                                        printf("output:\n%s.\n", outbuf);
1241                                fflush(stdout);
1242                                if (!debug) {
1243                                        Sleep(10000); daemon_disable_console();
1244                                }
1245                        }
1246                        printf("[PID=%ld: Signal=%d]", GetCurrentProcessId(), caughtsig); fflush(stdout);
1247                        if (caughtsig == SIGTERM || caughtsig == SIGBREAK)
1248                                break;
1249                        caughtsig = 0;
1250                }
1251        }
1252        printf("\nExiting on signal %d\n", caughtsig);
1253        return 0;
1254}
1255
1256
1257int main(int argc, char **argv)
1258{
1259        static const daemon_winsvc_options svc_opts = {
1260        "-s", "test", "Test Service", "Service to test daemon_win32.c Module"
1261        };
1262
1263        return daemon_main("testd", &svc_opts, test_main, argc, argv);
1264}
1265
1266#endif
Note: See TracBrowser for help on using the browser.