/* * os_win32/daemon_win32.cpp * * Home page of code is: http://smartmontools.sourceforge.net * * Copyright (C) 2004-13 Christian Franke * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * You should have received a copy of the GNU General Public License * (for example COPYING); If not, see . * */ #define WINVER 0x0600 #define _WIN32_WINNT WINVER #include "daemon_win32.h" const char * daemon_win32_cpp_cvsid = "$Id: daemon_win32.cpp,v 1.1.1.3 2013/10/14 07:54:04 misho Exp $" DAEMON_WIN32_H_CVSID; #include #include #include #include #define WIN32_LEAN_AND_MEAN #include #ifdef _DEBUG #include #endif #ifndef SERVICE_CONFIG_DELAYED_AUTO_START_INFO // Missing in older MinGW headers #define SERVICE_CONFIG_DELAYED_AUTO_START_INFO 3 #endif ///////////////////////////////////////////////////////////////////////////// // Prevent spawning of child process if debugging #ifdef _DEBUG #define debugging() IsDebuggerPresent() #else #define debugging() FALSE #endif #define EVT_NAME_LEN 260 // Internal events (must be > SIGUSRn) #define EVT_RUNNING 100 // Exists when running, signaled on creation #define EVT_DETACHED 101 // Signaled when child detaches from console #define EVT_RESTART 102 // Signaled when child should restart static void make_name(char * name, int sig) { int i; if (!GetModuleFileNameA(NULL, name, EVT_NAME_LEN-10)) strcpy(name, "DaemonEvent"); for (i = 0; name[i]; i++) { char c = name[i]; if (!( ('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) name[i] = '_'; } sprintf(name+strlen(name), "-%d", sig); } static HANDLE create_event(int sig, BOOL initial, BOOL errmsg, BOOL * exists) { char name[EVT_NAME_LEN]; HANDLE h; if (sig >= 0) make_name(name, sig); else name[0] = 0; if (exists) *exists = FALSE; if (!(h = CreateEventA(NULL, FALSE, initial, (name[0] ? name : NULL)))) { if (errmsg) fprintf(stderr, "CreateEvent(.,\"%s\"): Error=%ld\n", name, GetLastError()); return 0; } if (GetLastError() == ERROR_ALREADY_EXISTS) { if (!exists) { if (errmsg) fprintf(stderr, "CreateEvent(.,\"%s\"): Exists\n", name); CloseHandle(h); return 0; } *exists = TRUE; } return h; } static HANDLE open_event(int sig) { char name[EVT_NAME_LEN]; make_name(name, sig); return OpenEventA(EVENT_MODIFY_STATE, FALSE, name); } static int event_exists(int sig) { char name[EVT_NAME_LEN]; HANDLE h; make_name(name, sig); if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name))) return 0; CloseHandle(h); return 1; } static int sig_event(int sig) { char name[EVT_NAME_LEN]; HANDLE h; make_name(name, sig); if (!(h = OpenEventA(EVENT_MODIFY_STATE, FALSE, name))) { make_name(name, EVT_RUNNING); if (!(h = OpenEvent(EVENT_MODIFY_STATE, FALSE, name))) return -1; CloseHandle(h); return 0; } SetEvent(h); CloseHandle(h); return 1; } static void daemon_help(FILE * f, const char * ident, const char * message) { fprintf(f, "%s: %s.\n" "Use \"%s status|stop|reload|restart|sigusr1|sigusr2\" to control daemon.\n", ident, message, ident); fflush(f); } ///////////////////////////////////////////////////////////////////////////// // Parent Process static BOOL WINAPI parent_console_handler(DWORD event) { switch (event) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: return TRUE; // Ignore } return FALSE; // continue with next handler ... } static int parent_main(HANDLE rev) { HANDLE dev; HANDLE ht[2]; char * cmdline; STARTUPINFO si; PROCESS_INFORMATION pi; DWORD rc, exitcode; // Ignore ^C, ^BREAK in parent SetConsoleCtrlHandler(parent_console_handler, TRUE/*add*/); // Create event used by child to signal daemon_detach() if (!(dev = create_event(EVT_DETACHED, FALSE/*not signaled*/, TRUE, NULL/*must not exist*/))) { CloseHandle(rev); return 101; } // Restart process with same args cmdline = GetCommandLineA(); memset(&si, 0, sizeof(si)); si.cb = sizeof(si); if (!CreateProcessA( NULL, cmdline, NULL, NULL, TRUE/*inherit*/, 0, NULL, NULL, &si, &pi)) { fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError()); CloseHandle(rev); CloseHandle(dev); return 101; } CloseHandle(pi.hThread); // Wait for daemon_detach() or exit() ht[0] = dev; ht[1] = pi.hProcess; rc = WaitForMultipleObjects(2, ht, FALSE/*or*/, INFINITE); if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+2)) { fprintf(stderr, "WaitForMultipleObjects returns %lX\n", rc); TerminateProcess(pi.hProcess, 200); } CloseHandle(rev); CloseHandle(dev); // Get exit code if (!GetExitCodeProcess(pi.hProcess, &exitcode)) exitcode = 201; else if (exitcode == STILL_ACTIVE) // detach()ed, assume OK exitcode = 0; CloseHandle(pi.hProcess); return exitcode; } ///////////////////////////////////////////////////////////////////////////// // Child Process static int svc_mode; // Running as service? static int svc_paused; // Service paused? static void service_report_status(int state, int waithint); // Tables of signal handler and corresponding events typedef void (*sigfunc_t)(int); #define MAX_SIG_HANDLERS 8 static int num_sig_handlers = 0; static sigfunc_t sig_handlers[MAX_SIG_HANDLERS]; static int sig_numbers[MAX_SIG_HANDLERS]; static HANDLE sig_events[MAX_SIG_HANDLERS]; static HANDLE sighup_handle, sigint_handle, sigbreak_handle; static HANDLE sigterm_handle, sigusr1_handle; static HANDLE running_event; static int reopen_stdin, reopen_stdout, reopen_stderr; // Handler for windows console events static BOOL WINAPI child_console_handler(DWORD event) { // Caution: runs in a new thread // TODO: Guard with a mutex HANDLE h = 0; switch (event) { case CTRL_C_EVENT: // (SIGINT) h = sigint_handle; break; case CTRL_BREAK_EVENT: // (SIGBREAK/SIGQUIT) case CTRL_CLOSE_EVENT: // User closed console or abort via task manager h = sigbreak_handle; break; case CTRL_LOGOFF_EVENT: // Logout/Shutdown (SIGTERM) case CTRL_SHUTDOWN_EVENT: h = sigterm_handle; break; } if (!h) return FALSE; // continue with next handler // Signal event if (!SetEvent(h)) return FALSE; return TRUE; } static void child_exit(void) { int i; char * cmdline; HANDLE rst; STARTUPINFO si; PROCESS_INFORMATION pi; for (i = 0; i < num_sig_handlers; i++) CloseHandle(sig_events[i]); num_sig_handlers = 0; CloseHandle(running_event); running_event = 0; // Restart? if (!(rst = open_event(EVT_RESTART))) return; // No => normal exit // Yes => Signal exit and restart process Sleep(500); SetEvent(rst); CloseHandle(rst); Sleep(500); cmdline = GetCommandLineA(); memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; if (!CreateProcessA( NULL, cmdline, NULL, NULL, TRUE/*inherit*/, 0, NULL, NULL, &si, &pi)) { fprintf(stderr, "CreateProcess(.,\"%s\",.) failed, Error=%ld\n", cmdline, GetLastError()); } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } static int child_main(HANDLE hev,int (*main_func)(int, char **), int argc, char **argv) { // Keep EVT_RUNNING open until exit running_event = hev; // Install console handler SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/); // Install restart handler atexit(child_exit); // Continue in main_func() to do the real work return main_func(argc, argv); } // Simulate signal() sigfunc_t daemon_signal(int sig, sigfunc_t func) { int i; HANDLE h; if (func == SIG_DFL || func == SIG_IGN) return func; // TODO for (i = 0; i < num_sig_handlers; i++) { if (sig_numbers[i] == sig) { sigfunc_t old = sig_handlers[i]; sig_handlers[i] = func; return old; } } if (num_sig_handlers >= MAX_SIG_HANDLERS) return SIG_ERR; if (!(h = create_event((!svc_mode ? sig : -1), FALSE, TRUE, NULL))) return SIG_ERR; sig_events[num_sig_handlers] = h; sig_numbers[num_sig_handlers] = sig; sig_handlers[num_sig_handlers] = func; switch (sig) { case SIGHUP: sighup_handle = h; break; case SIGINT: sigint_handle = h; break; case SIGTERM: sigterm_handle = h; break; case SIGBREAK: sigbreak_handle = h; break; case SIGUSR1: sigusr1_handle = h; break; } num_sig_handlers++; return SIG_DFL; } // strsignal() const char * daemon_strsignal(int sig) { switch (sig) { case SIGHUP: return "SIGHUP"; case SIGINT: return "SIGINT"; case SIGTERM: return "SIGTERM"; case SIGBREAK:return "SIGBREAK"; case SIGUSR1: return "SIGUSR1"; case SIGUSR2: return "SIGUSR2"; default: return "*UNKNOWN*"; } } // Simulate sleep() void daemon_sleep(int seconds) { do { if (num_sig_handlers <= 0) { Sleep(seconds*1000L); } else { // Wait for any signal or timeout DWORD rc = WaitForMultipleObjects(num_sig_handlers, sig_events, FALSE/*OR*/, seconds*1000L); if (rc != WAIT_TIMEOUT) { if (!(/*WAIT_OBJECT_0(0) <= rc && */ rc < WAIT_OBJECT_0+(unsigned)num_sig_handlers)) { fprintf(stderr,"WaitForMultipleObjects returns %lu\n", rc); Sleep(seconds*1000L); return; } // Call Handler sig_handlers[rc-WAIT_OBJECT_0](sig_numbers[rc-WAIT_OBJECT_0]); break; } } } while (svc_paused); } // Disable/Enable console void daemon_disable_console() { SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/); reopen_stdin = reopen_stdout = reopen_stderr = 0; if (isatty(fileno(stdin))) { fclose(stdin); reopen_stdin = 1; } if (isatty(fileno(stdout))) { fclose(stdout); reopen_stdout = 1; } if (isatty(fileno(stderr))) { fclose(stderr); reopen_stderr = 1; } FreeConsole(); SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/); } int daemon_enable_console(const char * title) { BOOL ok; SetConsoleCtrlHandler(child_console_handler, FALSE/*remove*/); ok = AllocConsole(); SetConsoleCtrlHandler(child_console_handler, TRUE/*add*/); if (!ok) return -1; if (title) SetConsoleTitleA(title); if (reopen_stdin) freopen("conin$", "r", stdin); if (reopen_stdout) freopen("conout$", "w", stdout); if (reopen_stderr) freopen("conout$", "w", stderr); reopen_stdin = reopen_stdout = reopen_stderr = 0; return 0; } // Detach daemon from console & parent int daemon_detach(const char * ident) { if (!svc_mode) { if (ident) { // Print help FILE * f = ( isatty(fileno(stdout)) ? stdout : isatty(fileno(stderr)) ? stderr : NULL); if (f) daemon_help(f, ident, "now detaches from console into background mode"); } // Signal detach to parent if (sig_event(EVT_DETACHED) != 1) { if (!debugging()) return -1; } daemon_disable_console(); } else { // Signal end of initialization to service control manager service_report_status(SERVICE_RUNNING, 0); reopen_stdin = reopen_stdout = reopen_stderr = 1; } return 0; } ///////////////////////////////////////////////////////////////////////////// // Spawn a command and redirect outbuf // return command's exitcode or -1 on error int daemon_spawn(const char * cmd, const char * inpbuf, int inpsize, char * outbuf, int outsize ) { HANDLE self = GetCurrentProcess(); // Create stdin pipe with inheritable read side SECURITY_ATTRIBUTES sa; memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; HANDLE pipe_inp_r, pipe_inp_w, h; if (!CreatePipe(&pipe_inp_r, &h, &sa/*inherit*/, inpsize*2+13)) return -1; if (!DuplicateHandle(self, h, self, &pipe_inp_w, 0, FALSE/*!inherit*/, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE)) { CloseHandle(pipe_inp_r); return -1; } // Create stdout pipe with inheritable write side memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; HANDLE pipe_out_w; if (!CreatePipe(&h, &pipe_out_w, &sa/*inherit*/, outsize)) { CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w); return -1; } HANDLE pipe_out_r; if (!DuplicateHandle(self, h, self, &pipe_out_r, GENERIC_READ, FALSE/*!inherit*/, DUPLICATE_CLOSE_SOURCE)) { CloseHandle(pipe_out_w); CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w); return -1; } // Create stderr handle as dup of stdout write side HANDLE pipe_err_w; if (!DuplicateHandle(self, pipe_out_w, self, &pipe_err_w, 0, TRUE/*inherit*/, DUPLICATE_SAME_ACCESS)) { CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w); return -1; } // Create process with pipes as stdio STARTUPINFO si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.hStdInput = pipe_inp_r; si.hStdOutput = pipe_out_w; si.hStdError = pipe_err_w; si.dwFlags = STARTF_USESTDHANDLES; PROCESS_INFORMATION pi; if (!CreateProcessA( NULL, (char*)cmd, NULL, NULL, TRUE/*inherit*/, CREATE_NO_WINDOW, // DETACHED_PROCESS does not work NULL, NULL, &si, &pi)) { CloseHandle(pipe_err_w); CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); CloseHandle(pipe_inp_r); CloseHandle(pipe_inp_w); return -1; } CloseHandle(pi.hThread); // Close inherited handles CloseHandle(pipe_inp_r); CloseHandle(pipe_out_w); CloseHandle(pipe_err_w); // Copy inpbuf to stdin // convert \n => \r\n DWORD num_io; int i; for (i = 0; i < inpsize; ) { int len = 0; while (i+len < inpsize && inpbuf[i+len] != '\n') len++; if (len > 0) WriteFile(pipe_inp_w, inpbuf+i, len, &num_io, NULL); i += len; if (i < inpsize) { WriteFile(pipe_inp_w, "\r\n", 2, &num_io, NULL); i++; } } CloseHandle(pipe_inp_w); // Copy stdout to output buffer until full, rest to /dev/null // convert \r\n => \n for (i = 0; ; ) { char buf[256]; if (!ReadFile(pipe_out_r, buf, sizeof(buf), &num_io, NULL) || num_io == 0) break; for (int j = 0; i < outsize-1 && j < (int)num_io; j++) { if (buf[j] != '\r') outbuf[i++] = buf[j]; } } outbuf[i] = 0; CloseHandle(pipe_out_r); // Wait for process exitcode DWORD exitcode = 42; WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &exitcode); CloseHandle(pi.hProcess); return exitcode; } ///////////////////////////////////////////////////////////////////////////// // Initd Functions static int wait_signaled(HANDLE h, int seconds) { int i; for (i = 0; ; ) { if (WaitForSingleObject(h, 1000L) == WAIT_OBJECT_0) return 0; if (++i >= seconds) return -1; fputchar('.'); fflush(stdout); } } static int wait_evt_running(int seconds, int exists) { int i; if (event_exists(EVT_RUNNING) == exists) return 0; for (i = 0; ; ) { Sleep(1000); if (event_exists(EVT_RUNNING) == exists) return 0; if (++i >= seconds) return -1; fputchar('.'); fflush(stdout); } } static int is_initd_command(char * s) { if (!strcmp(s, "status")) return EVT_RUNNING; if (!strcmp(s, "stop")) return SIGTERM; if (!strcmp(s, "reload")) return SIGHUP; if (!strcmp(s, "sigusr1")) return SIGUSR1; if (!strcmp(s, "sigusr2")) return SIGUSR2; if (!strcmp(s, "restart")) return EVT_RESTART; return -1; } static int initd_main(const char * ident, int argc, char **argv) { int rc; if (argc < 2) return -1; if ((rc = is_initd_command(argv[1])) < 0) return -1; if (argc != 2) { printf("%s: no arguments allowed for command %s\n", ident, argv[1]); return 1; } switch (rc) { default: case EVT_RUNNING: printf("Checking for %s:", ident); fflush(stdout); rc = event_exists(EVT_RUNNING); puts(rc ? " running" : " not running"); return (rc ? 0 : 1); case SIGTERM: printf("Stopping %s:", ident); fflush(stdout); rc = sig_event(SIGTERM); if (rc <= 0) { puts(rc < 0 ? " not running" : " error"); return (rc < 0 ? 0 : 1); } rc = wait_evt_running(10, 0); puts(!rc ? " done" : " timeout"); return (!rc ? 0 : 1); case SIGHUP: printf("Reloading %s:", ident); fflush(stdout); rc = sig_event(SIGHUP); puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running"); return (rc > 0 ? 0 : 1); case SIGUSR1: case SIGUSR2: printf("Sending SIGUSR%d to %s:", (rc-SIGUSR1+1), ident); fflush(stdout); rc = sig_event(rc); puts(rc > 0 ? " done" : rc == 0 ? " error" : " not running"); return (rc > 0 ? 0 : 1); case EVT_RESTART: { HANDLE rst; printf("Stopping %s:", ident); fflush(stdout); if (event_exists(EVT_DETACHED)) { puts(" not detached, cannot restart"); return 1; } if (!(rst = create_event(EVT_RESTART, FALSE, FALSE, NULL))) { puts(" error"); return 1; } rc = sig_event(SIGTERM); if (rc <= 0) { puts(rc < 0 ? " not running" : " error"); CloseHandle(rst); return 1; } rc = wait_signaled(rst, 10); CloseHandle(rst); if (rc) { puts(" timeout"); return 1; } puts(" done"); Sleep(100); printf("Starting %s:", ident); fflush(stdout); rc = wait_evt_running(10, 1); puts(!rc ? " done" : " error"); return (!rc ? 0 : 1); } } } ///////////////////////////////////////////////////////////////////////////// // Windows Service Functions int daemon_winsvc_exitcode; // Set by app to exit(code) static SERVICE_STATUS_HANDLE svc_handle; static SERVICE_STATUS svc_status; // Report status to SCM static void service_report_status(int state, int seconds) { // TODO: Avoid race static DWORD checkpoint = 1; svc_status.dwCurrentState = state; svc_status.dwWaitHint = seconds*1000; switch (state) { default: svc_status.dwCheckPoint = checkpoint++; break; case SERVICE_RUNNING: case SERVICE_STOPPED: svc_status.dwCheckPoint = 0; } switch (state) { case SERVICE_START_PENDING: case SERVICE_STOP_PENDING: svc_status.dwControlsAccepted = 0; break; default: svc_status.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN| SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_PARAMCHANGE; break; } SetServiceStatus(svc_handle, &svc_status); } // Control the service, called by SCM static void WINAPI service_control(DWORD ctrlcode) { switch (ctrlcode) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: service_report_status(SERVICE_STOP_PENDING, 30); svc_paused = 0; SetEvent(sigterm_handle); break; case SERVICE_CONTROL_PARAMCHANGE: // Win2000/XP service_report_status(svc_status.dwCurrentState, 0); svc_paused = 0; SetEvent(sighup_handle); // reload break; case SERVICE_CONTROL_PAUSE: service_report_status(SERVICE_PAUSED, 0); svc_paused = 1; break; case SERVICE_CONTROL_CONTINUE: service_report_status(SERVICE_RUNNING, 0); { int was_paused = svc_paused; svc_paused = 0; SetEvent(was_paused ? sighup_handle : sigusr1_handle); // reload:recheck } break; case SERVICE_CONTROL_INTERROGATE: default: // unknown service_report_status(svc_status.dwCurrentState, 0); break; } } // Exit handler for service static void service_exit(void) { // Close signal events int i; for (i = 0; i < num_sig_handlers; i++) CloseHandle(sig_events[i]); num_sig_handlers = 0; // Set exitcode if (daemon_winsvc_exitcode) { svc_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; svc_status.dwServiceSpecificExitCode = daemon_winsvc_exitcode; } // Report stopped service_report_status(SERVICE_STOPPED, 0); } // Variables for passing main(argc, argv) from daemon_main to service_main() static int (*svc_main_func)(int, char **); static int svc_main_argc; static char ** svc_main_argv; // Main function for service, called by service dispatcher static void WINAPI service_main(DWORD /*argc*/, LPSTR * argv) { char path[MAX_PATH], *p; // Register control handler svc_handle = RegisterServiceCtrlHandler(argv[0], service_control); // Init service status svc_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; service_report_status(SERVICE_START_PENDING, 10); // Service started in \windows\system32, change to .exe directory if (GetModuleFileNameA(NULL, path, sizeof(path)) && (p = strrchr(path, '\\'))) { *p = 0; SetCurrentDirectoryA(path); } // Install exit handler atexit(service_exit); // Do the real work, service status later updated by daemon_detach() daemon_winsvc_exitcode = svc_main_func(svc_main_argc, svc_main_argv); exit(daemon_winsvc_exitcode); // ... continued in service_exit() } ///////////////////////////////////////////////////////////////////////////// // Windows Service Admin Functions // Make registry key name for event message file static bool make_evtkey(char * buf, unsigned size, const char * ident) { static const char prefix[] = "SYSTEM\\CurrentControlSet\\Services\\Eventlog\\Application\\"; const unsigned pfxlen = sizeof(prefix)-1; unsigned idlen = strlen(ident); if (pfxlen + idlen >= size) { printf(" Buffer overflow\n"); return false; } memcpy(buf, prefix, pfxlen); memcpy(buf+pfxlen, ident, idlen+1); return true; } // Install this exe as event message file static void inst_evtmsg(const char * ident) { printf("Installing event message file for %s:", ident); fflush(stdout); char mypath[MAX_PATH]; if (!GetModuleFileNameA((HMODULE)0, mypath, sizeof(mypath))) { printf(" unknown program path, Error=%ld\n", GetLastError()); return; } char subkey[MAX_PATH]; if (!make_evtkey(subkey, sizeof(subkey), ident)) return; HKEY hk; LONG err = RegCreateKeyExA(HKEY_LOCAL_MACHINE, subkey, 0, (char *)0, 0, KEY_ALL_ACCESS, (SECURITY_ATTRIBUTES *)0, &hk, (DWORD *)0); if (err != ERROR_SUCCESS) { printf(" RegCreateKeyEx failed, error=%ld\n", err); return; } err = RegSetValueExA(hk, "EventMessageFile", 0, REG_SZ, (const BYTE *)mypath, strlen(mypath)+1); if (err == ERROR_SUCCESS) { DWORD val = EVENTLOG_INFORMATION_TYPE |EVENTLOG_WARNING_TYPE |EVENTLOG_ERROR_TYPE; err = RegSetValueExA(hk, "TypesSupported", 0, REG_DWORD, (const BYTE *)&val, sizeof(val)); } if (err != ERROR_SUCCESS) printf(" RegSetValueEx failed, error=%ld\n", err); RegCloseKey(hk); puts(" done"); } // Uninstall event message file static void uninst_evtmsg(const char * ident) { printf("Removing event message file for %s:", ident); fflush(stdout); char subkey[MAX_PATH]; if (!make_evtkey(subkey, sizeof(subkey), ident)) return; LONG err = RegDeleteKeyA(HKEY_LOCAL_MACHINE, subkey); if (err != ERROR_SUCCESS && err != ERROR_FILE_NOT_FOUND) { printf(" RegDeleteKey failed, error=%ld\n", err); return; } puts(" done"); } // Service install/remove commands static int svcadm_main(const char * ident, const daemon_winsvc_options * svc_opts, int argc, char **argv ) { int remove; long err; SC_HANDLE hm, hs; if (argc < 2) return -1; if (!strcmp(argv[1], "install")) remove = 0; else if (!strcmp(argv[1], "remove")) { if (argc != 2) { printf("%s: no arguments allowed for command remove\n", ident); return 1; } remove = 1; } else return -1; printf("%s service %s:", (!remove?"Installing":"Removing"), ident); fflush(stdout); // Open SCM if (!(hm = OpenSCManager(NULL/*local*/, NULL/*default*/, SC_MANAGER_ALL_ACCESS))) { if ((err = GetLastError()) == ERROR_ACCESS_DENIED) puts(" access to SCManager denied"); else printf(" cannot open SCManager, Error=%ld\n", err); return 1; } if (!remove) { char path[MAX_PATH+100]; int i; // Get program path if (!GetModuleFileNameA(NULL, path, MAX_PATH)) { printf(" unknown program path, Error=%ld\n", GetLastError()); CloseServiceHandle(hm); return 1; } // Add quotes if necessary if (strchr(path, ' ')) { i = strlen(path); path[i+1] = '"'; path[i+2] = 0; while (--i >= 0) path[i+1] = path[i]; path[0] = '"'; } // Append options strcat(path, " "); strcat(path, svc_opts->cmd_opt); for (i = 2; i < argc; i++) { const char * s = argv[i]; if (strlen(path)+1+1+strlen(s)+1 >= sizeof(path)) break; // Add quotes if necessary if (strchr(s, ' ') && !strchr(s, '"')) { strcat(path, " \""); strcat(path, s); strcat(path, "\""); } else { strcat(path, " "); strcat(path, s); } } // Create if (!(hs = CreateService(hm, svc_opts->svcname, svc_opts->dispname, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path, NULL/*no load ordering*/, NULL/*no tag id*/, ""/*no depedencies*/, NULL/*local system account*/, NULL/*no pw*/))) { if ((err = GetLastError()) == ERROR_SERVICE_EXISTS) puts(" the service is already installed"); else if (err == ERROR_SERVICE_MARKED_FOR_DELETE) puts(" service is still running and marked for deletion\n" "Stop the service and retry install"); else printf(" failed, Error=%ld\n", err); CloseServiceHandle(hm); return 1; } // Set optional description if (svc_opts->descript) { SERVICE_DESCRIPTIONA sd = { const_cast(svc_opts->descript) }; ChangeServiceConfig2A(hs, SERVICE_CONFIG_DESCRIPTION, &sd); } // Enable delayed auto start if supported OSVERSIONINFOA ver; ver.dwOSVersionInfoSize = sizeof(ver); if ( GetVersionExA(&ver) && ver.dwPlatformId == VER_PLATFORM_WIN32_NT && ver.dwMajorVersion >= 6 /* Vista */ ) { SERVICE_DELAYED_AUTO_START_INFO sdasi = { TRUE }; ChangeServiceConfig2A(hs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &sdasi); } } else { // Open if (!(hs = OpenService(hm, svc_opts->svcname, SERVICE_ALL_ACCESS))) { puts(" not found"); CloseServiceHandle(hm); return 1; } // TODO: Stop service if running // Remove if (!DeleteService(hs)) { if ((err = GetLastError()) == ERROR_SERVICE_MARKED_FOR_DELETE) puts(" service is still running and marked for deletion\n" "Stop the service to remove it"); else printf(" failed, Error=%ld\n", err); CloseServiceHandle(hs); CloseServiceHandle(hm); return 1; } } puts(" done"); CloseServiceHandle(hs); CloseServiceHandle(hm); // Install/Remove event message file registry entry if (!remove) { inst_evtmsg(ident); } else { uninst_evtmsg(ident); } return 0; } ///////////////////////////////////////////////////////////////////////////// // Main Function // This function must be called from main() // main_func is the function doing the real work int daemon_main(const char * ident, const daemon_winsvc_options * svc_opts, int (*main_func)(int, char **), int argc, char **argv ) { int rc; #ifdef _DEBUG // Enable Debug heap checks _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) |_CRTDBG_ALLOC_MEM_DF|_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_LEAK_CHECK_DF); #endif // Check for [status|stop|reload|restart|sigusr1|sigusr2] parameters if ((rc = initd_main(ident, argc, argv)) >= 0) return rc; // Check for [install|remove] parameters if (svc_opts && (rc = svcadm_main(ident, svc_opts, argc, argv)) >= 0) return rc; // Run as service if svc_opts.cmd_opt is given as first(!) argument svc_mode = (svc_opts && argc >= 2 && !strcmp(argv[1], svc_opts->cmd_opt)); if (!svc_mode) { // Daemon: Try to simulate a Unix-like daemon HANDLE rev; BOOL exists; // Create main event to detect process type: // 1. new: parent process => start child and wait for detach() or exit() of child. // 2. exists && signaled: child process => do the real work, signal detach() to parent // 3. exists && !signaled: already running => exit() if (!(rev = create_event(EVT_RUNNING, TRUE/*signaled*/, TRUE, &exists))) return 100; if (!exists && !debugging()) { // Event new => parent process return parent_main(rev); } if (WaitForSingleObject(rev, 0) == WAIT_OBJECT_0) { // Event was signaled => In child process return child_main(rev, main_func, argc, argv); } // Event no longer signaled => Already running! daemon_help(stdout, ident, "already running"); CloseHandle(rev); return 1; } else { // Service: Start service_main() via SCM SERVICE_TABLE_ENTRY service_table[] = { { (char*)svc_opts->svcname, service_main }, { NULL, NULL } }; svc_main_func = main_func; svc_main_argc = argc; svc_main_argv = argv; if (!StartServiceCtrlDispatcher(service_table)) { printf("%s: cannot dispatch service, Error=%ld\n" "Option \"%s\" cannot be used to start %s as a service from console.\n" "Use \"%s install ...\" to install the service\n" "and \"net start %s\" to start it.\n", ident, GetLastError(), svc_opts->cmd_opt, ident, ident, ident); #ifdef _DEBUG if (debugging()) service_main(argc, argv); #endif return 100; } Sleep(1000); ExitThread(0); // Do not redo exit() processing /*NOTREACHED*/ return 0; } } ///////////////////////////////////////////////////////////////////////////// // Test Program #ifdef TEST static volatile sig_atomic_t caughtsig = 0; static void sig_handler(int sig) { caughtsig = sig; } static void test_exit(void) { printf("Main exit\n"); } int test_main(int argc, char **argv) { int i; int debug = 0; char * cmd = 0; printf("PID=%ld\n", GetCurrentProcessId()); for (i = 0; i < argc; i++) { printf("%d: \"%s\"\n", i, argv[i]); if (!strcmp(argv[i],"-d")) debug = 1; } if (argc > 1 && argv[argc-1][0] != '-') cmd = argv[argc-1]; daemon_signal(SIGINT, sig_handler); daemon_signal(SIGBREAK, sig_handler); daemon_signal(SIGTERM, sig_handler); daemon_signal(SIGHUP, sig_handler); daemon_signal(SIGUSR1, sig_handler); daemon_signal(SIGUSR2, sig_handler); atexit(test_exit); if (!debug) { printf("Preparing to detach...\n"); Sleep(2000); daemon_detach("test"); printf("Detached!\n"); } for (;;) { daemon_sleep(1); printf("."); fflush(stdout); if (caughtsig) { if (caughtsig == SIGUSR2) { debug ^= 1; if (debug) daemon_enable_console("Daemon[Debug]"); else daemon_disable_console(); } else if (caughtsig == SIGUSR1 && cmd) { char inpbuf[200], outbuf[1000]; int rc; strcpy(inpbuf, "Hello\nWorld!\n"); rc = daemon_spawn(cmd, inpbuf, strlen(inpbuf), outbuf, sizeof(outbuf)); if (!debug) daemon_enable_console("Command output"); printf("\"%s\" returns %d\n", cmd, rc); if (rc >= 0) printf("output:\n%s.\n", outbuf); fflush(stdout); if (!debug) { Sleep(10000); daemon_disable_console(); } } printf("[PID=%ld: Signal=%d]", GetCurrentProcessId(), caughtsig); fflush(stdout); if (caughtsig == SIGTERM || caughtsig == SIGBREAK) break; caughtsig = 0; } } printf("\nExiting on signal %d\n", caughtsig); return 0; } int main(int argc, char **argv) { static const daemon_winsvc_options svc_opts = { "-s", "test", "Test Service", "Service to test daemon_win32.c Module" }; return daemon_main("testd", &svc_opts, test_main, argc, argv); } #endif