diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:14:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:14:45 +0000 |
commit | 43e8530e93493bb978c446a2023134bdd4277e50 (patch) | |
tree | e8c0d3c0c394b17381f48fb2d288f166b4f22440 /os_win32/daemon_win32.cpp | |
parent | Initial commit. (diff) | |
download | smartmontools-43e8530e93493bb978c446a2023134bdd4277e50.tar.xz smartmontools-43e8530e93493bb978c446a2023134bdd4277e50.zip |
Adding upstream version 7.4.upstream/7.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'os_win32/daemon_win32.cpp')
-rw-r--r-- | os_win32/daemon_win32.cpp | 1107 |
1 files changed, 1107 insertions, 0 deletions
diff --git a/os_win32/daemon_win32.cpp b/os_win32/daemon_win32.cpp new file mode 100644 index 0000000..66e022a --- /dev/null +++ b/os_win32/daemon_win32.cpp @@ -0,0 +1,1107 @@ +/* + * os_win32/daemon_win32.cpp + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2004-18 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define WINVER 0x0600 +#define _WIN32_WINNT WINVER + +#include "daemon_win32.h" + +const char * daemon_win32_cpp_cvsid = "$Id: daemon_win32.cpp 4842 2018-12-02 16:07:26Z chrfranke $" + DAEMON_WIN32_H_CVSID; + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <io.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#ifdef _DEBUG +#include <crtdbg.h> +#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: // <CONTROL-C> (SIGINT) + h = sigint_handle; break; + case CTRL_BREAK_EVENT: // <CONTROL-Break> (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; +} + + +///////////////////////////////////////////////////////////////////////////// +// 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 dependencies*/, 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<char *>(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_{,CONFIG_}DELAYED_AUTO_START_INFO are missing in older MinGW headers + struct /* SERVICE_DELAYED_AUTO_START_INFO */ { + BOOL fDelayedAutostart; + } sdasi = { TRUE }; + // typedef char ASSERT_sizeof_sdasi[sizeof(sdasi) == sizeof(SERVICE_DELAYED_AUTO_START_INFO) ? 1 : -1]; + // typedef char ASSERT_const_scdasi[SERVICE_CONFIG_DELAYED_AUTO_START_INFO == 3 ? 1 : -1]; + ChangeServiceConfig2A(hs, 3 /* 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 |