diff options
Diffstat (limited to 'os_win32')
-rw-r--r-- | os_win32/daemon_win32.cpp | 1107 | ||||
-rw-r--r-- | os_win32/daemon_win32.h | 55 | ||||
-rw-r--r-- | os_win32/default.manifest | 27 | ||||
-rw-r--r-- | os_win32/installer.nsi | 946 | ||||
-rw-r--r-- | os_win32/popen.h | 66 | ||||
-rw-r--r-- | os_win32/popen_win32.cpp | 348 | ||||
-rw-r--r-- | os_win32/runcmd.c | 76 | ||||
-rw-r--r-- | os_win32/smartd_mailer.conf.sample.ps1 | 31 | ||||
-rw-r--r-- | os_win32/smartd_mailer.ps1 | 90 | ||||
-rw-r--r-- | os_win32/smartd_warning.cmd | 210 | ||||
-rw-r--r-- | os_win32/syslog.h | 62 | ||||
-rw-r--r-- | os_win32/syslog_win32.cpp | 375 | ||||
-rw-r--r-- | os_win32/syslogevt.mc | 156 | ||||
-rw-r--r-- | os_win32/update-smart-drivedb.ps1.in | 841 | ||||
-rw-r--r-- | os_win32/versioninfo.rc.in | 34 | ||||
-rw-r--r-- | os_win32/wmiquery.cpp | 190 | ||||
-rw-r--r-- | os_win32/wmiquery.h | 180 | ||||
-rw-r--r-- | os_win32/wtssendmsg.c | 179 |
18 files changed, 4973 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 diff --git a/os_win32/daemon_win32.h b/os_win32/daemon_win32.h new file mode 100644 index 0000000..f0f4b21 --- /dev/null +++ b/os_win32/daemon_win32.h @@ -0,0 +1,55 @@ +/* + * os_win32/daemon_win32.h + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2004-18 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DAEMON_WIN32_H +#define DAEMON_WIN32_H + +#define DAEMON_WIN32_H_CVSID "$Id: daemon_win32.h 4818 2018-10-17 05:32:17Z chrfranke $" + +#include <signal.h> + +// Additional non-ANSI signals +#define SIGHUP (NSIG+1) +#define SIGUSR1 (NSIG+2) +#define SIGUSR2 (NSIG+3) + + +// Options for Windows service +typedef struct daemon_winsvc_options_s { + const char * cmd_opt; // argv[1] option for services + // For service "install" command only: + const char * svcname; // Service name + const char * dispname; // Service display name + const char * descript; // Service description +} daemon_winsvc_options; + + +// This function must be called from main() +int daemon_main(const char * ident, const daemon_winsvc_options * svc_opts, + int (*main_func)(int, char **), int argc, char **argv ); + +// exit(code) returned by a service +extern int daemon_winsvc_exitcode; + +// Simulate signal() +void (*daemon_signal(int sig, void (*func)(int)))(int); +const char * daemon_strsignal(int sig); + +// Simulate sleep() +void daemon_sleep(int seconds); + +// Disable/Enable console +void daemon_disable_console(void); +int daemon_enable_console(const char * title); + +// Detach from console +int daemon_detach(const char * ident); + +#endif // DAEMON_WIN32_H diff --git a/os_win32/default.manifest b/os_win32/default.manifest new file mode 100644 index 0000000..01379b9 --- /dev/null +++ b/os_win32/default.manifest @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel + level="asInvoker" + uiAccess="false" + /> + </requestedPrivileges> + </security> + </trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows Vista --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + <!-- Windows 7 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <!-- Windows 8 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <!-- Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <!-- Windows 10 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + </application> + </compatibility> +</assembly> diff --git a/os_win32/installer.nsi b/os_win32/installer.nsi new file mode 100644 index 0000000..d4690ec --- /dev/null +++ b/os_win32/installer.nsi @@ -0,0 +1,946 @@ +; +; os_win32/installer.nsi - smartmontools install NSIS script +; +; Home page of code is: https://www.smartmontools.org +; +; Copyright (C) 2006-23 Christian Franke +; +; SPDX-License-Identifier: GPL-2.0-or-later +; +; $Id: installer.nsi 5504 2023-07-16 15:44:41Z chrfranke $ +; + + +;-------------------------------------------------------------------- +; Command line arguments: +; makensis -DINPDIR=<input-dir> -DINPDIR64=<input-dir-64-bit> \ +; -DOUTFILE=<output-file> -DVERSTR=<version-string> -DYY=<year> \ +; installer.nsi + +!ifndef INPDIR + !define INPDIR "." +!endif + +!ifndef OUTFILE + !define OUTFILE "smartmontools.win32-setup.exe" +!endif + +;-------------------------------------------------------------------- +; General + +Name "smartmontools" +OutFile "${OUTFILE}" + +RequestExecutionLevel admin + +SetCompressor /solid lzma + +XPStyle on +InstallColors /windows + +; Set in .onInit +;InstallDir "$PROGRAMFILES\smartmontools" +;InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "InstallLocation" + +!ifdef VERSION + VIProductVersion "${VERSION}" + VIAddVersionKey /LANG=1033-English "CompanyName" "www.smartmontools.org" + VIAddVersionKey /LANG=1033-English "FileDescription" "SMART Monitoring Tools" + VIAddVersionKey /LANG=1033-English "FileVersion" "${VERSION}" + !ifdef YY + VIAddVersionKey /LANG=1033-English "LegalCopyright" "(C) 2002-20${YY}, Bruce Allen, Christian Franke, www.smartmontools.org" + !endif + VIAddVersionKey /LANG=1033-English "OriginalFilename" "${OUTFILE}" + VIAddVersionKey /LANG=1033-English "ProductName" "smartmontools" + VIAddVersionKey /LANG=1033-English "ProductVersion" "${VERSION}" +!endif + +Var EDITOR + +!ifdef INPDIR64 + Var X64 + Var INSTDIR32 + Var INSTDIR64 +!endif + +LicenseData "${INPDIR}\doc\COPYING.txt" + +!include "FileFunc.nsh" +!include "LogicLib.nsh" +!include "Sections.nsh" + + +;-------------------------------------------------------------------- +; Pages + +Page license +Page components +!ifdef INPDIR64 + Page directory CheckX64 +!else + Page directory +!endif +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +!ifdef INPDIR64 + InstType "Full (x86_64)" + InstType "Extract files only (x86_64)" + InstType "Drive menu (x86_64)" + InstType "Full (x86)" + InstType "Extract files only (x86)" + InstType "Drive menu (x86)" +!else + InstType "Full" + InstType "Extract files only" + InstType "Drive menu" +!endif + + +;-------------------------------------------------------------------- +; Sections + +!ifdef INPDIR64 + Section "64-bit version" X64_SECTION + SectionIn 1 2 3 + ; Handled in Function CheckX64 + SectionEnd + + !define FULL_TYPES "1 4" + !define EXTRACT_TYPES "2 5" + !define DRIVEMENU_TYPE "3 6" +!else + !define FULL_TYPES "1" + !define EXTRACT_TYPES "2" + !define DRIVEMENU_TYPE "3" +!endif + +SectionGroup "!Program files" + + !macro FileExe path option + !ifdef INPDIR64 + ; Use dummy SetOutPath to control archive location of executables + ${If} $X64 != "" + Goto +2 + SetOutPath "$INSTDIR\bin" + File ${option} '${INPDIR64}\${path}' + ${Else} + Goto +2 + SetOutPath "$INSTDIR\bin32" + File ${option} '${INPDIR}\${path}' + ${EndIf} + !else + File ${option} '${INPDIR}\${path}' + !endif + !macroend + + Section "smartctl" SMARTCTL_SECTION + + SectionIn ${FULL_TYPES} ${EXTRACT_TYPES} + + SetOutPath "$INSTDIR\bin" + !insertmacro FileExe "bin\smartctl.exe" "" + + SectionEnd + + Section "smartd" SMARTD_SECTION + + SectionIn ${FULL_TYPES} ${EXTRACT_TYPES} + + SetOutPath "$INSTDIR\bin" + + ; Stop service ? + StrCpy $1 "" + ${If} ${FileExists} "$INSTDIR\bin\smartd.exe" + ReadRegStr $0 HKLM "System\CurrentControlSet\Services\smartd" "ImagePath" + ${If} $0 != "" + ExecWait "net stop smartd" $1 + ${EndIf} + ${EndIf} + !insertmacro FileExe "bin\smartd.exe" "" + + SetOutPath "$INSTDIR\bin" + IfFileExists "$INSTDIR\bin\smartd.conf" 0 +2 + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Replace existing configuration file$\n$INSTDIR\bin\smartd.conf ?" /SD IDNO IDYES 0 IDNO +2 + File "${INPDIR}\doc\smartd.conf" + + File "${INPDIR}\bin\smartd_mailer.ps1" + File "${INPDIR}\bin\smartd_mailer.conf.sample.ps1" + File "${INPDIR}\bin\smartd_warning.cmd" + !insertmacro FileExe "bin\wtssendmsg.exe" "" + + ; Restart service ? + ${If} $1 == "0" + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Restart smartd service ?" /SD IDNO IDYES 0 IDNO +2 + ExecWait "net start smartd" + ${EndIf} + + SectionEnd + + Section "smartctl-nc (GSmartControl)" SMARTCTL_NC_SECTION + + SectionIn ${FULL_TYPES} ${EXTRACT_TYPES} + + SetOutPath "$INSTDIR\bin" + !insertmacro FileExe "bin\smartctl-nc.exe" "" + + SectionEnd + + Section "drivedb.h (Drive Database)" DRIVEDB_SECTION + + SectionIn ${FULL_TYPES} ${EXTRACT_TYPES} + + SetOutPath "$INSTDIR\bin" + File "${INPDIR}\bin\drivedb.h" + Delete "$INSTDIR\bin\update-smart-drivedb.exe" ; TODO: Remove after smartmontools 7.3 + File "${INPDIR}\bin\update-smart-drivedb.ps1" + + SectionEnd + +SectionGroupEnd + +Section "!Documentation" DOC_SECTION + + SectionIn ${FULL_TYPES} ${EXTRACT_TYPES} + + SetOutPath "$INSTDIR\doc" + File "${INPDIR}\doc\AUTHORS.txt" + File "${INPDIR}\doc\ChangeLog.txt" + File "${INPDIR}\doc\ChangeLog-6.0-7.0.txt" + File "${INPDIR}\doc\COPYING.txt" + File "${INPDIR}\doc\INSTALL.txt" + File "${INPDIR}\doc\NEWS.txt" + File "${INPDIR}\doc\README.txt" + File "${INPDIR}\doc\TODO.txt" +!ifdef INPDIR64 + ${If} $X64 != "" + File "${INPDIR64}\doc\checksums64.txt" + ${Else} + File "${INPDIR}\doc\checksums32.txt" + ${EndIf} +!else + File "${INPDIR}\doc\checksums??.txt" +!endif + File "${INPDIR}\doc\smartctl.8.html" + File "${INPDIR}\doc\smartctl.8.pdf" + File "${INPDIR}\doc\smartd.8.html" + File "${INPDIR}\doc\smartd.8.pdf" + File "${INPDIR}\doc\smartd.conf" + File "${INPDIR}\doc\smartd.conf.5.html" + File "${INPDIR}\doc\smartd.conf.5.pdf" + +SectionEnd + +Section "Uninstaller" UNINST_SECTION + + SectionIn ${FULL_TYPES} + AddSize 40 + + CreateDirectory "$INSTDIR" + + ; Write uninstall keys and program + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "DisplayName" "smartmontools" +!ifdef VERSTR + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "DisplayVersion" "${VERSTR}" +!endif + ; Important: GSmartControl (>= 1.0.0) reads "InstallLocation" to detect location of bin\smartctl-nc.exe + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "UninstallString" '"$INSTDIR\uninst-smartmontools.exe"' + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "Publisher" "smartmontools.org" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "URLInfoAbout" "https://www.smartmontools.org/" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "HelpLink" "https://www.smartmontools.org/wiki/Help" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "URLUpdateInfo" "https://builds.smartmontools.org/" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "NoRepair" 1 + + Goto +2 ; Use dummy SetOutPath to control archive location of uninstaller + SetOutPath "$INSTDIR" + WriteUninstaller "uninst-smartmontools.exe" + +SectionEnd + +Section "Start Menu Shortcuts" MENU_SECTION + + SectionIn ${FULL_TYPES} + + SetShellVarContext all + + CreateDirectory "$SMPROGRAMS\smartmontools" + + !macro CreateAdminShortCut link target args + CreateShortCut '${link}' '${target}' '${args}' + push '${link}' + Call ShellLinkSetRunAs + !macroend + + ; runcmdu + ${If} ${FileExists} "$INSTDIR\bin\smartctl.exe" + ${OrIf} ${FileExists} "$INSTDIR\bin\smartd.exe" + SetOutPath "$INSTDIR\bin" + !insertmacro FileExe "bin\runcmdu.exe" "" + ${EndIf} + + ; smartctl + ${If} ${FileExists} "$INSTDIR\bin\smartctl.exe" + SetOutPath "$INSTDIR\bin" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl (Admin CMD).lnk" "$WINDIR\system32\cmd.exe" '/k PATH=$INSTDIR\bin;%PATH%&cd /d "$INSTDIR\bin"' + CreateDirectory "$SMPROGRAMS\smartmontools\smartctl Examples" + FileOpen $0 "$SMPROGRAMS\smartmontools\smartctl Examples\!Read this first!.txt" "w" + FileWrite $0 "All the example commands in this directory$\r$\napply to the first drive (sda).$\r$\n" + FileClose $0 + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\All info (-x).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -x sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Identify drive (-i).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -i sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\SMART attributes (-A -f brief).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -A -f brief sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\SMART capabilities (-c).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -c sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\SMART health status (-H).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -H sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\SMART error log (-l error).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -l error sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\SMART selftest log (-l selftest).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -l selftest sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Start long selftest (-t long).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -t long sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Start offline test (-t offline).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -t offline sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Start short selftest (-t short).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -t short sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Stop(Abort) selftest (-X).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -X sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Turn SMART off (-s off).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -s off sda" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartctl Examples\Turn SMART on (-s on).lnk" "$INSTDIR\bin\runcmdu.exe" "smartctl -s on sda" + ${EndIf} + + ; smartd + ${If} ${FileExists} "$INSTDIR\bin\smartd.exe" + SetOutPath "$INSTDIR\bin" + CreateDirectory "$SMPROGRAMS\smartmontools\smartd Examples" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Daemon start, smartd.log.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd -l local0" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Daemon start, eventlog.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Daemon stop.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd stop" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Do all tests once (-q onecheck).lnk" "$INSTDIR\bin\runcmdu.exe" "smartd -q onecheck" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Debug mode (-d).lnk" "$INSTDIR\bin\runcmdu.exe" "smartd -d" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\smartd.conf (edit).lnk" "$EDITOR" '"$INSTDIR\bin\smartd.conf"' + CreateShortCut "$SMPROGRAMS\smartmontools\smartd Examples\smartd.conf (view).lnk" "$EDITOR" '"$INSTDIR\bin\smartd.conf"' + CreateShortCut "$SMPROGRAMS\smartmontools\smartd Examples\smartd.log (view).lnk" "$EDITOR" '"$INSTDIR\bin\smartd.log"' + CreateShortCut "$SMPROGRAMS\smartmontools\smartd Examples\smartd_mailer.conf.sample.ps1 (view).lnk" "$EDITOR" '"$INSTDIR\bin\smartd_mailer.conf.sample.ps1"' + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\smartd_mailer.conf.ps1 (create, edit).lnk" "$EDITOR" '"$INSTDIR\bin\smartd_mailer.conf.ps1"' + + ; smartd service + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Service install, eventlog, 30min.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd install" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Service install, smartd.log, 10min.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd install -l local0 -i 600" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Service install, smartd.log, 30min.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd install -l local0" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Service remove.lnk" "$INSTDIR\bin\runcmdu.exe" "smartd remove" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Service start.lnk" "$INSTDIR\bin\runcmdu.exe" "net start smartd" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\smartd Examples\Service stop.lnk" "$INSTDIR\bin\runcmdu.exe" "net stop smartd" + ${EndIf} + + ; Documentation + ${If} ${FileExists} "$INSTDIR\doc\README.TXT" + SetOutPath "$INSTDIR\doc" + CreateDirectory "$SMPROGRAMS\smartmontools\Documentation" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartctl manual page (html).lnk" "$INSTDIR\doc\smartctl.8.html" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartd manual page (html).lnk" "$INSTDIR\doc\smartd.8.html" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartd.conf manual page (html).lnk" "$INSTDIR\doc\smartd.conf.5.html" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartctl manual page (pdf).lnk" "$INSTDIR\doc\smartctl.8.pdf" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartd manual page (pdf).lnk" "$INSTDIR\doc\smartd.8.pdf" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartd.conf manual page (pdf).lnk" "$INSTDIR\doc\smartd.conf.5.pdf" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\smartd.conf sample.lnk" "$EDITOR" '"$INSTDIR\doc\smartd.conf"' + ${If} ${FileExists} "$INSTDIR\bin\drivedb.h" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\drivedb.h (view).lnk" "$EDITOR" '"$INSTDIR\bin\drivedb.h"' + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\Documentation\drivedb-add.h (create, edit).lnk" "$EDITOR" '"$INSTDIR\bin\drivedb-add.h"' + ${EndIf} + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\ChangeLog.lnk" "$INSTDIR\doc\ChangeLog.txt" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\COPYING.lnk" "$INSTDIR\doc\COPYING.txt" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\NEWS.lnk" "$INSTDIR\doc\NEWS.txt" + ${EndIf} + + ; Homepage + CreateShortCut "$SMPROGRAMS\smartmontools\smartmontools Home Page.lnk" "https://www.smartmontools.org/" + CreateShortCut "$SMPROGRAMS\smartmontools\smartmontools Daily Builds.lnk" "https://builds.smartmontools.org/" + + ; drivedb.h update + Delete "$SMPROGRAMS\smartmontools\drivedb.h update.lnk" ; TODO: Remove after smartmontools 7.3 + ${If} ${FileExists} "$INSTDIR\bin\update-smart-drivedb.ps1" + SetOutPath "$INSTDIR\bin" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\drivedb.h update (ps1).lnk" "$INSTDIR\bin\runcmdu.exe" "powershell -NoProfile -ExecutionPolicy Bypass .\update-smart-drivedb.ps1" + ${If} ${FileExists} "$INSTDIR\doc\README.TXT" + CreateShortCut "$SMPROGRAMS\smartmontools\Documentation\drivedb.h update help (ps1).lnk" "$INSTDIR\bin\runcmdu.exe" 'powershell -NoProfile -ExecutionPolicy Bypass "Get-Help .\update-smart-drivedb.ps1 -Detail | more"' + ${EndIf} + ${EndIf} + + ; Uninstall + ${If} ${FileExists} "$INSTDIR\uninst-smartmontools.exe" + SetOutPath "$INSTDIR" + !insertmacro CreateAdminShortCut "$SMPROGRAMS\smartmontools\Uninstall smartmontools.lnk" "$INSTDIR\uninst-smartmontools.exe" "" + ${EndIf} + +SectionEnd + +Section "Add install dir to PATH" PATH_SECTION + + SectionIn ${FULL_TYPES} + + Push "$INSTDIR\bin" + Call AddToPath + +SectionEnd + +SectionGroup "Add smartctl to drive menu" + +!macro DriveMenuRemove + DetailPrint "Remove drive menu entries" + DeleteRegKey HKCR "Drive\shell\smartctl0" + DeleteRegKey HKCR "Drive\shell\smartctl1" + DeleteRegKey HKCR "Drive\shell\smartctl2" + DeleteRegKey HKCR "Drive\shell\smartctl3" + DeleteRegKey HKCR "Drive\shell\smartctl4" + DeleteRegKey HKCR "Drive\shell\smartctl5" +!macroend + + Section "Remove existing entries first" DRIVE_REMOVE_SECTION + SectionIn ${DRIVEMENU_TYPE} + !insertmacro DriveMenuRemove + SectionEnd + +!macro DriveSection id name args + Section 'smartctl ${args} ...' DRIVE_${id}_SECTION + SectionIn ${DRIVEMENU_TYPE} + Call CheckRunCmdA + DetailPrint 'Add drive menu entry "${name}": smartctl ${args} ...' + WriteRegStr HKCR "Drive\shell\smartctl${id}" "" "${name}" + WriteRegStr HKCR "Drive\shell\smartctl${id}\command" "" '"$INSTDIR\bin\runcmda.exe" "$INSTDIR\bin\smartctl.exe" ${args} %L' + SectionEnd +!macroend + + !insertmacro DriveSection 0 "SMART all info" "-x" + !insertmacro DriveSection 1 "SMART status" "-Hc" + !insertmacro DriveSection 2 "SMART attributes" "-A -f brief" + !insertmacro DriveSection 3 "SMART short selftest" "-t short" + !insertmacro DriveSection 4 "SMART long selftest" "-t long" + !insertmacro DriveSection 5 "SMART continue selective selftest" '-t "selective,cont"' + +SectionGroupEnd + +;-------------------------------------------------------------------- + +Section "Uninstall" + + ; Stop & remove service + ${If} ${FileExists} "$INSTDIR\bin\smartd.exe" + ReadRegStr $0 HKLM "System\CurrentControlSet\Services\smartd" "ImagePath" + ${If} $0 != "" + ExecWait "net stop smartd" + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Remove smartd service ?" /SD IDNO IDYES 0 IDNO +2 + ExecWait "$INSTDIR\bin\smartd.exe remove" + ${EndIf} + ${EndIf} + + ; Remove installer registry key + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" + + ; Remove conf file ? + ${If} ${FileExists} "$INSTDIR\bin\smartd.conf" + ; Assume unchanged if timestamp is equal to sample file + GetFileTime "$INSTDIR\bin\smartd.conf" $0 $1 + GetFileTime "$INSTDIR\doc\smartd.conf" $2 $3 + StrCmp "$0:$1" "$2:$3" +2 0 + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Delete configuration file$\n$INSTDIR\bin\smartd.conf ?" /SD IDNO IDYES 0 IDNO +2 + Delete "$INSTDIR\bin\smartd.conf" + ${EndIf} + + ; Remove log file ? + ${If} ${FileExists} "$INSTDIR\bin\smartd.log" + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Delete log file$\n$INSTDIR\bin\smartd.log ?" /SD IDNO IDYES 0 IDNO +2 + Delete "$INSTDIR\bin\smartd.log" + ${EndIf} + + ; Remove drivedb-add file ? + ${If} ${FileExists} "$INSTDIR\bin\drivedb-add.h" + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Delete local drive database file$\n$INSTDIR\bin\drivedb-add.h ?" /SD IDNO IDYES 0 IDNO +2 + Delete "$INSTDIR\bin\drivedb-add.h" + ${EndIf} + + ; Remove smartd_mailer.conf.ps1 file ? + ${If} ${FileExists} "$INSTDIR\bin\smartd_mailer.conf.ps1" + MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Delete mailer configuration file$\n$INSTDIR\bin\smartd_mailer.conf.ps1 ?" /SD IDNO IDYES 0 IDNO +2 + Delete "$INSTDIR\bin\smartd_mailer.conf.ps1" + ${EndIf} + + ; Remove files + Delete "$INSTDIR\bin\smartctl.exe" + Delete "$INSTDIR\bin\smartctl-nc.exe" + Delete "$INSTDIR\bin\smartd.exe" + Delete "$INSTDIR\bin\smartd_mailer.ps1" + Delete "$INSTDIR\bin\smartd_mailer.conf.sample.ps1" + Delete "$INSTDIR\bin\smartd_warning.cmd" ; TODO: Check for modifications? + Delete "$INSTDIR\bin\drivedb.h" + Delete "$INSTDIR\bin\drivedb.h.error" + Delete "$INSTDIR\bin\drivedb.h.lastcheck" + Delete "$INSTDIR\bin\drivedb.h.old" + Delete "$INSTDIR\bin\update-smart-drivedb.exe" ; TODO: Remove after smartmontools 7.3 + Delete "$INSTDIR\bin\update-smart-drivedb.ps1" + Delete "$INSTDIR\bin\runcmda.exe" + Delete "$INSTDIR\bin\runcmdu.exe" + Delete "$INSTDIR\bin\wtssendmsg.exe" + Delete "$INSTDIR\doc\AUTHORS.txt" + Delete "$INSTDIR\doc\ChangeLog.txt" + Delete "$INSTDIR\doc\ChangeLog-6.0-7.0.txt" + Delete "$INSTDIR\doc\COPYING.txt" + Delete "$INSTDIR\doc\INSTALL.txt" + Delete "$INSTDIR\doc\NEWS.txt" + Delete "$INSTDIR\doc\README.txt" + Delete "$INSTDIR\doc\TODO.txt" + Delete "$INSTDIR\doc\checksums*.txt" + Delete "$INSTDIR\doc\smartctl.8.html" + Delete "$INSTDIR\doc\smartctl.8.pdf" + Delete "$INSTDIR\doc\smartd.8.html" + Delete "$INSTDIR\doc\smartd.8.pdf" + Delete "$INSTDIR\doc\smartd.conf" + Delete "$INSTDIR\doc\smartd.conf.5.html" + Delete "$INSTDIR\doc\smartd.conf.5.pdf" + Delete "$INSTDIR\uninst-smartmontools.exe" + + ; Remove shortcuts + SetShellVarContext all + Delete "$SMPROGRAMS\smartmontools\*.*" + Delete "$SMPROGRAMS\smartmontools\Documentation\*.*" + Delete "$SMPROGRAMS\smartmontools\smartctl Examples\*.*" + Delete "$SMPROGRAMS\smartmontools\smartd Examples\*.*" + + ; Remove folders + RMDir "$SMPROGRAMS\smartmontools\Documentation" + RMDir "$SMPROGRAMS\smartmontools\smartctl Examples" + RMDir "$SMPROGRAMS\smartmontools\smartd Examples" + RMDir "$SMPROGRAMS\smartmontools" + RMDir "$INSTDIR\bin" + RMDir "$INSTDIR\doc" + RMDir "$INSTDIR" + + ; Remove install dir from PATH + Push "$INSTDIR\bin" + Call un.RemoveFromPath + + ; Remove drive menu registry entries + !insertmacro DriveMenuRemove + + ; Check for still existing entries + ${If} ${FileExists} "$INSTDIR\bin\smartd.exe" + MessageBox MB_OK|MB_ICONEXCLAMATION "$INSTDIR\bin\smartd.exe could not be removed.$\nsmartd is possibly still running." /SD IDOK + ${ElseIf} ${FileExists} "$INSTDIR" + MessageBox MB_OK "Note: $INSTDIR could not be removed." /SD IDOK + ${EndIf} + + ${If} ${FileExists} "$SMPROGRAMS\smartmontools" + MessageBox MB_OK "Note: $SMPROGRAMS\smartmontools could not be removed." /SD IDOK + ${EndIf} + +SectionEnd + +;-------------------------------------------------------------------- +; Functions + +!macro AdjustSectionSize section + SectionGetSize ${section} $0 + IntOp $0 $0 / 2 + SectionSetSize ${section} $0 +!macroend + +Function .onInit + + ; Set default install directories + ${If} $INSTDIR == "" ; /D=PATH option not specified ? + ReadRegStr $INSTDIR HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\smartmontools" "InstallLocation" + ${If} $INSTDIR == "" ; Not already installed ? + StrCpy $INSTDIR "$PROGRAMFILES\smartmontools" +!ifdef INPDIR64 + StrCpy $INSTDIR32 $INSTDIR + StrCpy $INSTDIR64 "$PROGRAMFILES64\smartmontools" +!endif + ${EndIf} + ${EndIf} + +!ifdef INPDIR64 + ; Check for 64-bit unless already installed in 32-bit location + ${If} $INSTDIR64 != "" + ${OrIf} $INSTDIR != "$PROGRAMFILES\smartmontools" + ; $1 = IsWow64Process(GetCurrentProcess(), ($0=FALSE, &$0)) + System::Call "kernel32::GetCurrentProcess() i.s" + System::Call "kernel32::IsWow64Process(i s, *i 0 r0) i.r1" + ${If} "$0 $1" == "1 1" ; 64-bit Windows ? + !insertmacro SelectSection ${X64_SECTION} + ${EndIf} + ${EndIf} + + ; Sizes of binary sections include 32-bit and 64-bit executables + !insertmacro AdjustSectionSize ${SMARTCTL_SECTION} + !insertmacro AdjustSectionSize ${SMARTD_SECTION} + !insertmacro AdjustSectionSize ${SMARTCTL_NC_SECTION} +!endif + + ; Use 32-bit or 64-bit Notepad++ if installed + StrCpy $EDITOR "$PROGRAMFILES\Notepad++\notepad++.exe" + ${IfNot} ${FileExists} "$EDITOR" + StrCpy $EDITOR "$PROGRAMFILES64\Notepad++\notepad++.exe" + ${IfNot} ${FileExists} "$EDITOR" + StrCpy $EDITOR "notepad.exe" + ${EndIf} + ${EndIf} + + Call ParseCmdLine + +!ifdef INPDIR64 + Call CheckX64 +!endif +FunctionEnd + +; Check x64 section and update INSTDIR accordingly + +!ifdef INPDIR64 +Function CheckX64 + ${IfNot} ${SectionIsSelected} ${X64_SECTION} + StrCpy $X64 "" + ${If} $INSTDIR32 != "" + ${AndIf} $INSTDIR == $INSTDIR64 + StrCpy $INSTDIR $INSTDIR32 + ${EndIf} + ${Else} + StrCpy $X64 "t" + ${If} $INSTDIR64 != "" + ${AndIf} $INSTDIR == $INSTDIR32 + StrCpy $INSTDIR $INSTDIR64 + ${EndIf} + ${EndIf} +FunctionEnd +!endif + +; Command line parsing + +!macro GetCmdLineOption var name + Push ",$opts," + Push ",${name}," + Call StrStr + Pop ${var} + ${If} ${var} != "" + StrCpy $nomatch "" + ${EndIf} +!macroend + +!macro CheckCmdLineOption name section + StrCpy $allopts "$allopts,${name}" + !insertmacro GetCmdLineOption $0 ${name} + ${If} $0 == "" + !insertmacro UnselectSection ${section} + ${Else} + !insertmacro SelectSection ${section} + ${EndIf} +!macroend + +Function ParseCmdLine + ; get /SO option + Var /global opts + ${GetParameters} $R0 + ${GetOptions} $R0 "/SO" $opts + ${If} ${Errors} + Return + ${EndIf} + Var /global allopts + Var /global nomatch + StrCpy $nomatch "t" +!ifdef INPDIR64 + ; Change previous 64-bit setting + StrCpy $allopts ",x32|x64" + !insertmacro GetCmdLineOption $0 "x32" + ${If} $0 != "" + !insertmacro UnselectSection ${X64_SECTION} + ${EndIf} + !insertmacro GetCmdLineOption $0 "x64" + ${If} $0 != "" + !insertmacro SelectSection ${X64_SECTION} + ${EndIf} + ; Leave other sections unchanged if only "x32" or "x64" is specified + ${If} $opts == "x32" + ${OrIf} $opts == "x64" + Return + ${EndIf} +!endif + ; Turn sections on or off + !insertmacro CheckCmdLineOption "smartctl" ${SMARTCTL_SECTION} + !insertmacro CheckCmdLineOption "smartd" ${SMARTD_SECTION} + !insertmacro CheckCmdLineOption "smartctlnc" ${SMARTCTL_NC_SECTION} + !insertmacro CheckCmdLineOption "drivedb" ${DRIVEDB_SECTION} + !insertmacro CheckCmdLineOption "doc" ${DOC_SECTION} + !insertmacro CheckCmdLineOption "uninst" ${UNINST_SECTION} + !insertmacro CheckCmdLineOption "menu" ${MENU_SECTION} + !insertmacro CheckCmdLineOption "path" ${PATH_SECTION} + !insertmacro CheckCmdLineOption "driveremove" ${DRIVE_REMOVE_SECTION} + !insertmacro CheckCmdLineOption "drive0" ${DRIVE_0_SECTION} + !insertmacro CheckCmdLineOption "drive1" ${DRIVE_1_SECTION} + !insertmacro CheckCmdLineOption "drive2" ${DRIVE_2_SECTION} + !insertmacro CheckCmdLineOption "drive3" ${DRIVE_3_SECTION} + !insertmacro CheckCmdLineOption "drive4" ${DRIVE_4_SECTION} + !insertmacro CheckCmdLineOption "drive5" ${DRIVE_5_SECTION} + ${If} $opts != "-" + ${If} $nomatch != "" + StrCpy $0 "$allopts,-" "" 1 + MessageBox MB_OK "Usage: smartmontools-VERSION.win32-setup [/S] [/SO component,...] [/D=INSTDIR]$\n$\ncomponents:$\n $0" + Abort + ${EndIf} + ${EndIf} +FunctionEnd + +; Install runcmda.exe only once + +Function CheckRunCmdA + Var /global runcmda + ${If} $runcmda == "" + StrCpy $runcmda "t" + SetOutPath "$INSTDIR\bin" + !insertmacro FileExe "bin\runcmda.exe" "" + ${EndIf} +FunctionEnd + + +;-------------------------------------------------------------------- +; Path functions + +!include "WinMessages.nsh" + +; Registry Entry for environment +; All users: +;!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' +; Current user only: +!define Environ 'HKCU "Environment"' + + +; AddToPath - Appends dir to PATH +; +; Originally based on example from: +; https://nsis.sourceforge.io/Path_Manipulation +; Later reworked to fix the string overflow problem. +; This version is also provided here: +; https://nsis.sourceforge.io/AddToPath_safe +; +; Usage: +; Push "dir" +; Call AddToPath + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + + ; NSIS ReadRegStr returns empty string on string overflow + ; Native calls are used here to check actual length of PATH + + ; $4 = RegOpenKey(HKEY_CURRENT_USER, "Environment", &$3) + System::Call "advapi32::RegOpenKey(i 0x80000001, t'Environment', *i.r3) i.r4" + IntCmp $4 0 0 done done + ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2)) + ; RegCloseKey($3) + System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4" + System::Call "advapi32::RegCloseKey(i $3)" + + ${If} $4 = 234 ; ERROR_MORE_DATA + DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}" /SD IDOK + Goto done + ${EndIf} + + ${If} $4 <> 0 ; NO_ERROR + ${If} $4 <> 2 ; ERROR_FILE_NOT_FOUND + DetailPrint "AddToPath: unexpected error code $4" + Goto done + ${EndIf} + StrCpy $1 "" + ${EndIf} + + ; Check if already in PATH + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" 0 done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" 0 done + + ; Prevent NSIS string overflow + StrLen $2 $0 + StrLen $3 $1 + IntOp $2 $2 + $3 + IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";") + ${If} $2 > ${NSIS_MAX_STRLEN} + DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}." /SD IDOK + Goto done + ${EndIf} + + ; Append dir to PATH + DetailPrint "Add to PATH: $0" + StrCpy $2 $1 1 -1 + ${If} $2 == ";" + StrCpy $1 $1 -1 ; remove trailing ';' + ${EndIf} + ${If} $1 != "" ; no leading ';' + StrCpy $0 "$1;$0" + ${EndIf} + WriteRegExpandStr ${Environ} "PATH" $0 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Removes dir from PATH +; +; Based on example from: +; https://nsis.sourceforge.io/Path_Manipulation +; +; Usage: +; Push "dir" +; Call RemoveFromPath + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + ReadRegStr $1 ${Environ} "PATH" + StrCpy $5 $1 1 -1 + ${If} $5 != ";" + StrCpy $1 "$1;" ; ensure trailing ';' + ${EndIf} + Push $1 + Push "$0;" + Call un.StrStr + Pop $2 ; pos of our dir + StrCmp $2 "" done + + DetailPrint "Remove from PATH: $0" + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove + StrCpy $3 "$5$6" + StrCpy $5 $3 1 -1 + ${If} $5 == ";" + StrCpy $3 $3 -1 ; remove trailing ';' + ${EndIf} + WriteRegExpandStr ${Environ} "PATH" $3 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; StrStr - find substring in a string +; +; Based on example from: +; https://nsis.sourceforge.io/Path_Manipulation +; +; Usage: +; Push "this is some string" +; Push "some" +; Call StrStr +; Pop $0 ; "some string" + +!macro StrStr un +Function ${un}StrStr + Exch $R1 ; $R1=substring, stack=[old$R1,string,...] + Exch ; stack=[string,old$R1,...] + Exch $R2 ; $R2=string, stack=[old$R2,old$R1,...] + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=substring, $R2=string, $R3=strlen(substring) + ; $R4=count, $R5=tmp + ${Do} + StrCpy $R5 $R2 $R3 $R4 + ${IfThen} $R5 == $R1 ${|} ${ExitDo} ${|} + ${IfThen} $R5 == "" ${|} ${ExitDo} ${|} + IntOp $R4 $R4 + 1 + ${Loop} + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 ; $R1=old$R1, stack=[result,...] +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." + + +;-------------------------------------------------------------------- +; Set Run As Administrator flag in shortcut +; +; Based on example from: +; https://nsis.sourceforge.io/IShellLink_Set_RunAs_flag +; + +!define IPersistFile {0000010b-0000-0000-c000-000000000046} +!define CLSID_ShellLink {00021401-0000-0000-C000-000000000046} +!define IID_IShellLinkA {000214EE-0000-0000-C000-000000000046} +!define IID_IShellLinkW {000214F9-0000-0000-C000-000000000046} +!define IShellLinkDataList {45e2b4ae-b1c3-11d0-b92f-00a0c90312e1} +!ifdef NSIS_UNICODE + !define IID_IShellLink ${IID_IShellLinkW} +!else + !define IID_IShellLink ${IID_IShellLinkA} +!endif + +Function ShellLinkSetRunAs + ; Set archive location of $PLUGINSDIR + Goto +2 + SetOutPath "$INSTDIR" + + System::Store S ; push $0-$9, $R0-$R9 + pop $9 + ; $0 = CoCreateInstance(CLSID_ShellLink, 0, CLSCTX_INPROC_SERVER, IID_IShellLink, &$1) + System::Call "ole32::CoCreateInstance(g'${CLSID_ShellLink}',i0,i1,g'${IID_IShellLink}',*i.r1)i.r0" + ${If} $0 = 0 + System::Call "$1->0(g'${IPersistFile}',*i.r2)i.r0" ; $0 = $1->QueryInterface(IPersistFile, &$2) + ${If} $0 = 0 + System::Call "$2->5(w '$9',i 0)i.r0" ; $0 = $2->Load($9, STGM_READ) + ${If} $0 = 0 + System::Call "$1->0(g'${IShellLinkDataList}',*i.r3)i.r0" ; $0 = $1->QueryInterface(IShellLinkDataList, &$3) + ${If} $0 = 0 + System::Call "$3->6(*i.r4)i.r0"; $0 = $3->GetFlags(&$4) + ${If} $0 = 0 + System::Call "$3->7(i $4|0x2000)i.r0" ; $0 = $3->SetFlags($4|SLDF_RUNAS_USER) + ${If} $0 = 0 + System::Call "$2->6(w '$9',i1)i.r0" ; $2->Save($9, TRUE) + ${EndIf} + ${EndIf} + System::Call "$3->2()" ; $3->Release() + ${EndIf} + System::Call "$2->2()" ; $2->Release() + ${EndIf} + ${EndIf} + System::Call "$1->2()" ; $1->Release() + ${EndIf} + ${If} $0 <> 0 + DetailPrint "Set RunAsAdmin: $9 failed ($0)" + ${Else} + DetailPrint "Set RunAsAdmin: $9" + ${EndIf} + System::Store L ; pop $R9-$R0, $9-$0 +FunctionEnd diff --git a/os_win32/popen.h b/os_win32/popen.h new file mode 100644 index 0000000..0211587 --- /dev/null +++ b/os_win32/popen.h @@ -0,0 +1,66 @@ +/* + * os_win32/popen.h + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2018-21 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef POPEN_H +#define POPEN_H + +#define POPEN_H_CVSID "$Id: popen.h 5272 2021-12-18 15:55:35Z chrfranke $" + +#include <stdio.h> + +// MinGW <stdio.h> defines these to _popen/_pclose +#undef popen +#undef pclose + +#ifdef __cplusplus +extern "C" { +#endif + +// popen(3) reimplementation for Windows +// +// The _popen() from MSVCRT is not useful as it always opens a new +// console window if parent process has none. +// +// Differences to popen(3): +// - Only modes "r[bt]" are supported +// - stdin and stderr from parent are not inherited to child process +// but redirected to null device +// - Only one child process can be run at a time + +FILE * popen(const char * command, const char * mode); + +int pclose(FILE * f); + +#ifdef __cplusplus +} +#endif + +// Enhanced version of popen() with ability to modify the access token. +// If 'restricted' is set, the child process is run with a restricted access +// token. The local Administrator group and most privileges (all except +// SeChangeNotifyPrivilege) are removed. +FILE * popen_as_restr_user(const char * cmd, const char * mode, bool restricted); + +// Check whether the access token of the current user could be effectively +// restricted. +// Returns false if the current user is the local SYSTEM or Administrator account. +bool popen_as_restr_check(); + +// wait(3) macros from <sys/wait.h> +#ifndef WIFEXITED +#define WIFEXITED(status) (((status) & 0xff) == 0x00) +#define WIFSIGNALED(status) (((status) & 0xff) != 0x00) +#define WIFSTOPPED(status) (0) +#define WEXITSTATUS(status) ((status) >> 8) +#define WTERMSIG(status) ((status) & 0xff) +#define WSTOPSIG(status) (0) +#endif // WIFEXITED + +#endif // POPEN_H diff --git a/os_win32/popen_win32.cpp b/os_win32/popen_win32.cpp new file mode 100644 index 0000000..6243e50 --- /dev/null +++ b/os_win32/popen_win32.cpp @@ -0,0 +1,348 @@ +/* + * os_win32/popen_win32.cpp + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2018-21 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "popen.h" + +const char * popen_win32_cpp_cvsid = "$Id: popen_win32.cpp 5272 2021-12-18 15:55:35Z chrfranke $" + POPEN_H_CVSID; + +#include <errno.h> +#include <fcntl.h> +#include <io.h> // _open_osfhandle() +#include <signal.h> // SIGSEGV +#include <stdlib.h> +#include <string.h> + +#include <windows.h> + +static HANDLE create_restricted_token() +{ + // Create SIDs for SYSTEM and Local Adminstrator + union { + SID sid; + char sid_space[32]; // 16 + } adm, sys; // "S-1-5-18", "S-1-5-32-544" + DWORD adm_size = sizeof(adm), sys_size = sizeof(sys); + if (!( CreateWellKnownSid(WinBuiltinAdministratorsSid, (PSID)0, &adm.sid, &adm_size) + && CreateWellKnownSid(WinLocalSystemSid, (PSID)0, &sys.sid, &sys_size) )) { + errno = ENOMEM; + return (HANDLE)0; + } + + // Open token of current process + HANDLE proc_token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &proc_token)) { + errno = EPERM; + return (HANDLE)0; + } + + // Get Owner of current process: S-1-5-21-SYSTEM-GUID-USERID + union { + TOKEN_USER user; + char user_space[128]; // TODO: Max size? + } usr; + DWORD size = 0; + if (!GetTokenInformation(proc_token, TokenUser, &usr, sizeof(usr), &size)) { + CloseHandle(proc_token); + errno = EPERM; + return (HANDLE)0; + } + + // Restricting token from SYSTEM or local Administrator is not effective + if (EqualSid(usr.user.User.Sid, &sys.sid) || EqualSid(usr.user.User.Sid, &adm.sid)) { + CloseHandle(proc_token); + errno = EINVAL; + return (HANDLE)0; + } + + // The default DACL of an elevated process may not contain the user itself: + // D:(A;;GA;;;BA)(A;;GA;;;SY)[(A;;GXGR;;;S-1-5-5-0-LOGON_ID)] + // The restricted process then fails to start because the hidden + // console cannot be accessed. Use a standard default DACL instead: + // D:(A;;GA;;;S-1-5-21-SYSTEM-GUID-USERID)(A;;GA;;;BA)(A;;GA;;;SY) + union { + ACL acl; + char acl_space[256]; // 236 + } dacl; + if (!( InitializeAcl(&dacl.acl, sizeof(dacl), ACL_REVISION) + && AddAccessAllowedAce(&dacl.acl, ACL_REVISION, GENERIC_ALL, usr.user.User.Sid) + && AddAccessAllowedAce(&dacl.acl, ACL_REVISION, GENERIC_ALL, &adm.sid) + && AddAccessAllowedAce(&dacl.acl, ACL_REVISION, GENERIC_ALL, &sys.sid) )) { + CloseHandle(proc_token); + errno = ENOMEM; + return (HANDLE)0; + } + + // Create new token with local Administrator and most Privileges dropped + SID_AND_ATTRIBUTES sid_to_disable = {&adm.sid, 0}; + HANDLE restr_token; + BOOL ok = CreateRestrictedToken(proc_token, + DISABLE_MAX_PRIVILEGE, // Keep only "SeChangeNotifyPrivilege" + 1, &sid_to_disable, // Disable "S-1-5-32-544" (changes group to deny only) + 0, (LUID_AND_ATTRIBUTES *)0, // No further privileges + 0, (SID_AND_ATTRIBUTES *)0, // No restricted SIDs + &restr_token + ); + CloseHandle(proc_token); + + if (!ok) { + errno = EPERM; + return (HANDLE)0; + } + + // Set new Default DACL + TOKEN_DEFAULT_DACL tdacl = { &dacl.acl }; + if (!SetTokenInformation(restr_token, TokenDefaultDacl, &tdacl, sizeof(tdacl))) { + CloseHandle(restr_token); + errno = EPERM; + return (HANDLE)0; + } + + return restr_token; +} + +bool popen_as_restr_check() +{ + HANDLE restr_token = create_restricted_token(); + if (!restr_token) + return false; + CloseHandle(restr_token); + return true; +} + +static FILE * s_popen_file; +static HANDLE s_popen_process; + +FILE * popen_as_restr_user(const char * cmd, const char * mode, bool restricted) +{ + // Fail if previous run is still in progress + if (s_popen_file) { + errno = EEXIST; + return (FILE *)0; + } + + // mode "w" is not implemented + if (!(mode[0] == 'r' && (!mode[1] || !mode[2]))) { + errno = EINVAL; + return (FILE *)0; + } + + // Set flags for text or binary mode + // Note: _open_osfhandle() ignores _fmode and defaults to O_BINARY + int oflags; const char * fomode; + switch (mode[1]) { + case 0: + case 't': + oflags = O_RDONLY|O_TEXT; + fomode = "rt"; + break; + case 'b': + oflags = O_RDONLY|O_BINARY; + fomode = "rb"; + break; + default: + errno = EINVAL; + return (FILE *)0; + } + + // Create stdout pipe with inheritable write end + HANDLE pipe_out_r, pipe_out_w; + if (!CreatePipe(&pipe_out_r, &pipe_out_w, (SECURITY_ATTRIBUTES *)0, 1024)) { + errno = EMFILE; + return (FILE *)0; + } + if (!SetHandleInformation(pipe_out_w, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) { + CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); + errno = EMFILE; + return (FILE *)0; + } + + // Connect pipe read end to new FD + int fd = _open_osfhandle((intptr_t)pipe_out_r, oflags); + if (fd < 0) { + CloseHandle(pipe_out_r); CloseHandle(pipe_out_w); + return (FILE *)0; + } + + // Connect FD to new FILE + FILE * f = fdopen(fd, fomode); + if (!f) { + int err = errno; + close(fd); // CloseHandle(pipe_out_r) + CloseHandle(pipe_out_w); + errno = err; + return (FILE *)0; + } + + // Build command line "cmd /c COMMAND" + int cmdlen = strlen(cmd); + char * shellcmd = (char *)malloc(7 + cmdlen + 1); + if (!shellcmd) { + fclose(f); // CloseHandle(pipe_out_r) + CloseHandle(pipe_out_w); + errno = ENOMEM; + return (FILE *)0; + } + memcpy(shellcmd, "cmd /c ", 7); + memcpy(shellcmd + 7, cmd, cmdlen + 1); + + // Create a restricted token if requested + HANDLE restr_token = 0; + if (restricted) { + restr_token = create_restricted_token(); + if (!restr_token) { + int err = errno; + fclose(f); // CloseHandle(pipe_out_r) + CloseHandle(pipe_out_w); + errno = err; + return (FILE *)0; + } + } + + // Redirect stdin stderr to null device + // Don't inherit parent's stdin, script may hang if parent has no console. + SECURITY_ATTRIBUTES sa_inherit = { sizeof(sa_inherit), (SECURITY_DESCRIPTOR *)0, TRUE }; + HANDLE null_in = CreateFileA("nul", GENERIC_READ , 0, &sa_inherit, OPEN_EXISTING, 0, (HANDLE)0); + HANDLE null_err = CreateFileA("nul", GENERIC_WRITE, 0, &sa_inherit, OPEN_EXISTING, 0, (HANDLE)0); + + // Set stdio handles + STARTUPINFO si{}; si.cb = sizeof(si); + si.hStdInput = null_in; + si.hStdOutput = pipe_out_w; + si.hStdError = null_err; + si.dwFlags = STARTF_USESTDHANDLES; + + // Create process + PROCESS_INFORMATION pi; + BOOL ok; + const char * shell = getenv("COMSPEC"); + if (restr_token) { + ok = CreateProcessAsUserA( + restr_token, + shell, // "C:\Windows\System32\cmd.exe" or nullptr + shellcmd, // "cmd /c COMMAND" ("cmd" searched in PATH if COMSPEC not set) + (SECURITY_ATTRIBUTES *)0, (SECURITY_ATTRIBUTES *)0, + TRUE, // inherit + CREATE_NO_WINDOW, // DETACHED_PROCESS would open new console(s) + (void *)0, (char *)0, &si, &pi + ); + } + else { + ok = CreateProcessA( + shell, // "C:\Windows\System32\cmd.exe" or nullptr + shellcmd, // "cmd /c COMMAND" ("cmd" searched in PATH if COMSPEC not set) + (SECURITY_ATTRIBUTES *)0, (SECURITY_ATTRIBUTES *)0, + TRUE, // inherit + CREATE_NO_WINDOW, // DETACHED_PROCESS would open new console(s) + (void *)0, (char *)0, &si, &pi + ); + } + free(shellcmd); + + // Close inherited handles + CloseHandle(null_err); + CloseHandle(null_in); + if (restr_token) + CloseHandle(restr_token); + CloseHandle(pipe_out_w); + + if (!ok) { + fclose(f); // CloseHandle(pipe_out_r) + errno = ENOENT; + return (FILE *)0; + } + + // Store process and FILE for pclose() + CloseHandle(pi.hThread); + s_popen_process = pi.hProcess; + s_popen_file = f; + + return f; +} + +extern "C" +FILE * popen(const char * cmd, const char * mode) +{ + return popen_as_restr_user(cmd, mode, false); +} + +extern "C" +int pclose(FILE * f) +{ + if (f != s_popen_file) { + errno = EBADF; + return -1; + } + + fclose(f); + s_popen_file = 0; + + // Wait for process exitcode + DWORD exitcode = 42; + bool ok = ( WaitForSingleObject(s_popen_process, INFINITE) == WAIT_OBJECT_0 + && GetExitCodeProcess(s_popen_process, &exitcode)); + + CloseHandle(s_popen_process); + s_popen_process = 0; + + if (!ok) { + errno = ECHILD; + return -1; + } + + // Modify exitcode for wait(3) macros + if (exitcode >> 23) + return ((exitcode << 9) >> 1) | SIGSEGV; + else + return exitcode << 8; +} + +// Test program +#ifdef TEST + +int main(int argc, char **argv) +{ + bool restricted = false; + int ai = 1; + if (argc > 1 && !strcmp(argv[ai], "-r")) { + restricted = true; + ai++; + } + if (ai + 1 != argc) { + printf("Usage: %s [-r] \"COMMAND ARG...\"\n", argv[0]); + return 1; + } + const char * cmd = argv[ai]; + + printf("popen_as_restr_check() = %s\n", (popen_as_restr_check() ? "true" : "false")); + printf("popen_as_restr_user(\"%s\", \"r\", %s):\n", cmd, (restricted ? "true" : "false")); + FILE * f = popen_as_restr_user(cmd, "r", restricted); + if (!f) { + perror("popen_as_restr_user"); + return 1; + } + + int cnt, c; + for (cnt = 0; (c = getc(f)) != EOF; cnt++) + putchar(c); + printf("[EOF]\nread %d bytes\n", cnt); + + int status = pclose(f); + + if (status == -1) { + perror("pclose"); + return 1; + } + printf("pclose() = 0x%04x (exit = %d, sig = %d)\n", + status, WEXITSTATUS(status), WTERMSIG(status)); + return status; +} + +#endif diff --git a/os_win32/runcmd.c b/os_win32/runcmd.c new file mode 100644 index 0000000..0261717 --- /dev/null +++ b/os_win32/runcmd.c @@ -0,0 +1,76 @@ +/* + * Run console command and wait for user input + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2011 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +char svnid[] = "$Id: runcmd.c 4760 2018-08-19 18:45:53Z chrfranke $"; + +#include <stdio.h> +#include <windows.h> + +int main(int argc, char **argv) +{ + char * cmd = GetCommandLineA(); + DWORD exitcode; + STARTUPINFOA si = { sizeof(si), }; + PROCESS_INFORMATION pi; + int key; + + if (*cmd == '"') { + cmd++; + while (*cmd && !(*cmd == '"' && cmd[-1] != '\\')) + cmd++; + if (*cmd) + cmd++; + } + else { + while (*cmd && !(*cmd == ' ' || *cmd == '\t')) + cmd++; + } + + while (*cmd == ' ' || *cmd == '\t') + cmd++; + + if (*cmd) { + printf("%s\n\n", cmd); fflush(stdout); + } + + if (!*cmd) { + printf("Usage: %s COMMAND [ARG ...]\n", argv[0]); + exitcode = 1; + } + else if (!CreateProcessA((char *)0, cmd, + (SECURITY_ATTRIBUTES *)0, (SECURITY_ATTRIBUTES *)0, + TRUE/*inherit*/, 0/*no flags*/, (void *)0, (char *)0, &si, &pi) + ) { + DWORD err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) + printf("Command not found\n"); + else + printf("CreateProcess() failed with error=%u\n", err); + exitcode = 1; + } + else { + CloseHandle(pi.hThread); + + exitcode = 42; + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &exitcode); + CloseHandle(pi.hProcess); + + if (exitcode) + printf("\nExitcode: %u (0x%02x)", exitcode, exitcode); + } + + printf("\nType <return> to exit: "); fflush(stdout); + while (!((key = getc(stdin)) == EOF || key == '\n' || key == '\r')) + ; + printf("\n"); + + return exitcode; +} diff --git a/os_win32/smartd_mailer.conf.sample.ps1 b/os_win32/smartd_mailer.conf.sample.ps1 new file mode 100644 index 0000000..b82c9a3 --- /dev/null +++ b/os_win32/smartd_mailer.conf.sample.ps1 @@ -0,0 +1,31 @@ +# Sample file for smartd_mailer.conf.ps1 +# +# Home page of code is: http://www.smartmontools.org +# $Id: smartd_mailer.conf.sample.ps1 4338 2016-09-07 19:31:28Z chrfranke $ + +# SMTP Server +$smtpServer = "smtp.domain.local" + +# Optional settings [default values in square brackets] + +# Sender address ["smartd daemon <root@$hostname>"] +#$from = "Administrator <root@domain.local>" + +# SMTP Port [25] +#$port = 587 + +# Use STARTTLS [$false] +#$useSsl = $true + +# SMTP user name [] +#$username = "USER" + +# Plain text SMTP password [] +#$password = "PASSWORD" + +# Encrypted SMTP password [] +# (embedded newlines, tabs and spaces are ignored) +#$passwordEnc = " +# 0123456789abcdef... +# ... +#" diff --git a/os_win32/smartd_mailer.ps1 b/os_win32/smartd_mailer.ps1 new file mode 100644 index 0000000..c585c89 --- /dev/null +++ b/os_win32/smartd_mailer.ps1 @@ -0,0 +1,90 @@ +# +# smartd mailer script +# +# Home page of code is: http://www.smartmontools.org +# +# Copyright (C) 2016 Christian Franke +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# $Id: smartd_mailer.ps1 4760 2018-08-19 18:45:53Z chrfranke $ +# + +$ErrorActionPreference = "Stop" + +# Parse command line and check environment +$dryrun = $false +if (($args.Count -eq 1) -and ($args[0] -eq "--dryrun")) { + $dryrun = $true +} + +$toCsv = $env:SMARTD_ADDRCSV +$subject = $env:SMARTD_SUBJECT +$file = $env:SMARTD_FULLMSGFILE + +if (!((($args.Count -eq 0) -or $dryrun) -and $toCsv -and $subject -and $file)) { + echo ` +"smartd mailer script + +Usage: +set SMARTD_ADDRCSV='Comma separated mail addresses' +set SMARTD_SUBJECT='Mail Subject' +set SMARTD_FULLMSGFILE='X:\PATH\TO\Message.txt' + +.\$($MyInvocation.MyCommand.Name) [--dryrun] +" + exit 1 +} + +# Set default sender address +if ($env:COMPUTERNAME -match '^[-_A-Za-z0-9]+$') { + $hostname = $env:COMPUTERNAME.ToLower() +} else { + $hostname = "unknown" +} +if ($env:USERDNSDOMAIN -match '^[-._A-Za-z0-9]+$') { + $hostname += ".$($env:USERDNSDOMAIN.ToLower())" +} elseif ( ($env:USERDOMAIN -match '^[-_A-Za-z0-9]+$') ` + -and ($env:USERDOMAIN -ne $env:COMPUTERNAME) ) { + $hostname += ".$($env:USERDOMAIN.ToLower()).local" +} else { + $hostname += ".local" +} + +$from = "smartd daemon <root@$hostname>" + +# Read configuration +. .\smartd_mailer.conf.ps1 + +# Create parameters +$to = $toCsv.Split(",") +$body = Get-Content -Path $file | Out-String + +$parm = @{ + SmtpServer = $smtpServer; From = $from; To = $to + Subject = $subject; Body = $body +} +if ($port) { + $parm += @{ Port = $port } +} +if ($useSsl) { + $parm += @{ useSsl = $true } +} + +if ($username -and ($password -or $passwordEnc)) { + if (!$passwordEnc) { + $secureString = ConvertTo-SecureString -String $password -AsPlainText -Force + } else { + $passwordEnc = $passwordEnc -replace '[\r\n\t ]','' + $secureString = ConvertTo-SecureString -String $passwordEnc + } + $credential = New-Object -Typename System.Management.Automation.PSCredential -Argumentlist $username,$secureString + $parm += @{ Credential = $credential } +} + +# Send mail +if ($dryrun) { + echo "Send-MailMessage" @parm +} else { + Send-MailMessage @parm +} diff --git a/os_win32/smartd_warning.cmd b/os_win32/smartd_warning.cmd new file mode 100644 index 0000000..bfc459b --- /dev/null +++ b/os_win32/smartd_warning.cmd @@ -0,0 +1,210 @@ +@echo off +:: +:: smartd warning script +:: +:: Home page of code is: http://www.smartmontools.org +:: +:: Copyright (C) 2012-22 Christian Franke +:: +:: SPDX-License-Identifier: GPL-2.0-or-later +:: +:: $Id: smartd_warning.cmd 5428 2022-12-31 15:55:43Z chrfranke $ +:: + +verify other 2>nul +setlocal enableextensions enabledelayedexpansion +if errorlevel 1 goto UNSUPPORTED +set err= + +:: Change to script directory (not necessary if run from smartd service) +cd /d %~dp0 +if errorlevel 1 goto ERROR + +:: Detect accidental use of '-M exec /path/to/smartd_warning.cmd' +if not "!SMARTD_SUBJECT!" == "" ( + echo smartd_warning.cmd: SMARTD_SUBJECT is already set - possible recursion + goto ERROR +) + +:: Parse options +set dryrun= +if "%1" == "--dryrun" ( + set dryrun=--dryrun + shift +) +if not "!dryrun!" == "" echo cd /d !cd! + +if not "%1" == "" ( + echo smartd warning message script + echo. + echo Usage: + echo set SMARTD_MAILER='Path to external script, empty for "blat"' + echo set SMARTD_ADDRESS='Space separated mail addresses, empty if none' + echo set SMARTD_MESSAGE='Error Message' + echo set SMARTD_FAILTYPE='Type of failure, "EMailTest" for tests' + echo set SMARTD_TFIRST='Date of first message sent, empty if none' + echo :: set SMARTD_TFIRSTEPOCH='time_t format of above' + echo set SMARTD_PREVCNT='Number of previous messages, 0 if none' + echo set SMARTD_NEXTDAYS='Number of days until next message, empty if none' + echo set SMARTD_DEVICEINFO='Device identify information' + echo :: set SMARTD_DEVICE='Device name' + echo :: set SMARTD_DEVICESTRING='Annotated device name' + echo :: set SMARTD_DEVICETYPE='Device type from -d directive, "auto" if none' + + echo smartd_warning.cmd [--dryrun] + goto ERROR +) + +if "!SMARTD_ADDRESS!!SMARTD_MAILER!" == "" ( + echo smartd_warning.cmd: SMARTD_ADDRESS or SMARTD_MAILER must be set + goto ERROR +) + +:: USERDNSDOMAIN may be unset if running as service +if "!USERDNSDOMAIN!" == "" ( + for /f "delims== tokens=2 usebackq" %%d in (`wmic PATH Win32_Computersystem WHERE "PartOfDomain=TRUE" GET Domain /VALUE ^<nul 2^>nul`) do set USERDNSDOMAIN=%%~d +) +:: Remove possible trailing \r appended by above command (requires %...%) +set USERDNSDOMAIN=%USERDNSDOMAIN% + +:: Format subject +set SMARTD_SUBJECT=SMART error (!SMARTD_FAILTYPE!) detected on host: !COMPUTERNAME! + +:: Temp file for message +if not "!TMP!" == "" set SMARTD_FULLMSGFILE=!TMP!\smartd_warning-!RANDOM!.txt +if "!TMP!" == "" set SMARTD_FULLMSGFILE=smartd_warning-!RANDOM!.txt + +:: Format message +( + echo This message was generated by the smartd service running on: + echo. + echo. host name: !COMPUTERNAME! + if not "!USERDNSDOMAIN!" == "" echo. DNS domain: !USERDNSDOMAIN! + if "!USERDNSDOMAIN!" == "" echo. DNS domain: [Empty] + if not "!USERDOMAIN!" == "" echo. Win domain: !USERDOMAIN! + echo. + echo The following warning/error was logged by the smartd service: + echo. + if not "!SMARTD_MESSAGE!" == "" echo !SMARTD_MESSAGE! + if "!SMARTD_MESSAGE!" == "" echo [SMARTD_MESSAGE] + echo. + echo Device info: + if not "!SMARTD_DEVICEINFO!" == "" echo !SMARTD_DEVICEINFO! + if "!SMARTD_DEVICEINFO!" == "" echo [SMARTD_DEVICEINFO] + echo. + echo For details see the event log or log file of smartd. + if not "!SMARTD_FAILTYPE!" == "EmailTest" ( + echo. + echo You can also use the smartctl utility for further investigation. + if not "!SMARTD_PREVCNT!" == "0" echo The original message about this issue was sent at !SMARTD_TFIRST! + if "!SMARTD_NEXTDAYS!" == "" ( + echo No additional messages about this problem will be sent. + ) else ( if "!SMARTD_NEXTDAYS!" == "0" ( + echo Another message will be sent upon next check if the problem persists. + ) else ( if "!SMARTD_NEXTDAYS!" == "1" ( + echo Another message will be sent in 24 hours if the problem persists. + ) else ( + echo Another message will be sent in !SMARTD_NEXTDAYS! days if the problem persists. + ))) + ) +) > "!SMARTD_FULLMSGFILE!" +if errorlevel 1 goto ERROR + +if not "!dryrun!" == "" ( + echo !SMARTD_FULLMSGFILE!: + type "!SMARTD_FULLMSGFILE!" + echo --EOF-- +) + +:: Check first address +set first= +for /f "tokens=1*" %%a in ("!SMARTD_ADDRESS!") do (set first=%%a) +set wtssend= +if "!first!" == "console" set wtssend=-c +if "!first!" == "active" set wtssend=-a +if "!first!" == "connected" set wtssend=-s + +if not "!wtssend!" == "" ( + :: Show Message box(es) via WTSSendMessage() + if not "!dryrun!" == "" ( + echo call .\wtssendmsg !wtssend! "!SMARTD_SUBJECT!" - ^< "!SMARTD_FULLMSGFILE!" + ) else ( + call .\wtssendmsg !wtssend! "!SMARTD_SUBJECT!" - < "!SMARTD_FULLMSGFILE!" + if errorlevel 1 set err=t + ) + :: Remove first address + for /f "tokens=1*" %%a in ("!SMARTD_ADDRESS!") do (set SMARTD_ADDRESS=%%b) +) + +:: Make comma separated address list +set SMARTD_ADDRCSV= +if not "!SMARTD_ADDRESS!" == "" set SMARTD_ADDRCSV=!SMARTD_ADDRESS: =,! + +:: Default mailer is smartd_mailer.ps1 (if configured) or blat.exe +if not "!SMARTD_ADDRESS!" == "" if "!SMARTD_MAILER!" == "" ( + if not exist smartd_mailer.conf.ps1 set SMARTD_MAILER=blat +) + +:: Get mailer extension +set ext= +for /f "delims=" %%f in ("!SMARTD_MAILER!") do (set ext=%%~xf) + +:: Send mail or run command +if "!ext!" == ".ps1" ( + + :: Run PowerShell script + if not "!dryrun!" == "" ( + set esc=^^ + echo PowerShell -NoProfile -ExecutionPolicy Bypass -Command !esc!^& '!SMARTD_MAILER!' ^<nul + ) else ( + PowerShell -NoProfile -ExecutionPolicy Bypass -Command ^& '!SMARTD_MAILER!' <nul + if errorlevel 1 set err=t + ) + +) else ( if not "!SMARTD_ADDRCSV!" == "" ( + + :: Send mail + if "!SMARTD_MAILER!" == "" ( + + :: Use smartd_mailer.ps1 + if not "!dryrun!" == "" ( + echo PowerShell -NoProfile -ExecutionPolicy Bypass -Command .\smartd_mailer.ps1 ^<nul + echo ========== + ) + PowerShell -NoProfile -ExecutionPolicy Bypass -Command .\smartd_mailer.ps1 !dryrun! <nul + if errorlevel 1 set err=t + if not "!dryrun!" == "" echo ========== + + ) else ( + + :: Use blat mailer or compatible + if not "!dryrun!" == "" ( + echo call "!SMARTD_MAILER!" - -q -subject "!SMARTD_SUBJECT!" -to "!SMARTD_ADDRCSV!" ^< "!SMARTD_FULLMSGFILE!" + ) else ( + call "!SMARTD_MAILER!" - -q -subject "!SMARTD_SUBJECT!" -to "!SMARTD_ADDRCSV!" < "!SMARTD_FULLMSGFILE!" + if errorlevel 1 set err=t + ) + + ) + +) else ( if not "!SMARTD_MAILER!" == "" ( + + :: Run command + if not "!dryrun!" == "" ( + echo call "!SMARTD_MAILER!" ^<nul + ) else ( + call "!SMARTD_MAILER!" <nul + if errorlevel 1 set err=t + ) + +))) + +del "!SMARTD_FULLMSGFILE!" >nul 2>nul + +if not "!err!" == "" goto ERROR +endlocal +exit /b 0 + +:ERROR +endlocal +exit /b 1 diff --git a/os_win32/syslog.h b/os_win32/syslog.h new file mode 100644 index 0000000..00947b3 --- /dev/null +++ b/os_win32/syslog.h @@ -0,0 +1,62 @@ +/* + * os_win32/syslog.h + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2004-8 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SYSLOG_H +#define SYSLOG_H + +#define SYSLOG_H_CVSID "$Id: syslog.h 4760 2018-08-19 18:45:53Z chrfranke $\n" + +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* EVENTLOG_ERROR_TYPE: */ +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +/* EVENTLOG_WARNING_TYPE: */ +#define LOG_WARNING 4 +/* EVENTLOG_INFORMATION_TYPE: */ +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +/* event log: */ +#define LOG_DAEMON ( 3<<3) +/* ident.log: */ +#define LOG_LOCAL0 (16<<3) +/* ident1-7.log: */ +#define LOG_LOCAL1 (17<<3) +#define LOG_LOCAL2 (18<<3) +#define LOG_LOCAL3 (19<<3) +#define LOG_LOCAL4 (20<<3) +#define LOG_LOCAL5 (21<<3) +#define LOG_LOCAL6 (22<<3) +#define LOG_LOCAL7 (23<<3) + +#define LOG_FACMASK 0x03f8 +#define LOG_FAC(f) (((f) & LOG_FACMASK) >> 3) + +#define LOG_PID 0x01 + +void openlog(const char * ident, int option, int facility); + +void closelog(void); + +void vsyslog(int priority, const char * message, va_list args); + +#ifdef __cplusplus +} +#endif + +#endif /* SYSLOG_H */ diff --git a/os_win32/syslog_win32.cpp b/os_win32/syslog_win32.cpp new file mode 100644 index 0000000..8e0571a --- /dev/null +++ b/os_win32/syslog_win32.cpp @@ -0,0 +1,375 @@ +/* + * os_win32/syslog_win32.cpp + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2004-23 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +// Win32 Emulation of syslog() for smartd. +// Writes to windows event log by default. +// If facility is set to LOG_LOCAL[0-7], log is written to +// file "<ident>.log", stdout, stderr, "<ident>[1-5].log". + +#include "syslog.h" + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <errno.h> +#include <process.h> // getpid() + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> // RegisterEventSourceA(), ReportEventA(), ... + +const char *syslog_win32_cpp_cvsid = "$Id: syslog_win32.cpp 5499 2023-07-10 16:32:10Z chrfranke $" + SYSLOG_H_CVSID; + +#ifdef TESTEVT +// Redirect event log to stdout for testing + +static BOOL Test_ReportEventA(HANDLE h, WORD type, WORD cat, DWORD id, PSID usid, + WORD nstrings, WORD datasize, LPCTSTR * strings, LPVOID data) +{ + int i; + printf("%u %lu:%s", type, id, nstrings != 1?"\n":""); + for (i = 0; i < nstrings; i++) + printf(" \"%s\"\n", strings[i]); + fflush(stdout); + return TRUE; +} + +HANDLE Test_RegisterEventSourceA(LPCTSTR server, LPCTSTR source) +{ + return (HANDLE)42; +} + +#define ReportEventA Test_ReportEventA +#define RegisterEventSourceA Test_RegisterEventSourceA +#endif // TESTEVT + +// Event message ids, +// should be identical to MSG_SYSLOG* in "syslogevt.h" +// (generated by "mc" from "syslogevt.mc") +#define MSG_SYSLOG 0x00000000L +#define MSG_SYSLOG_01 0x00000001L +// ... +#define MSG_SYSLOG_10 0x0000000AL + +static char sl_ident[100]; +static char sl_logpath[sizeof(sl_ident) + sizeof("0.log")-1]; +static FILE * sl_logfile; +static char sl_pidstr[16]; +static HANDLE sl_hevtsrc; + +// Ring buffer for event log output via thread +#define MAXLINES 10 +#define LINELEN 200 + +static HANDLE evt_hthread; +static char evt_lines[MAXLINES][LINELEN+1]; +static int evt_priorities[MAXLINES]; +static volatile int evt_timeout; +static int evt_index_in, evt_index_out; +static HANDLE evt_wait_in, evt_wait_out; + +// Map syslog priority to event type +static WORD pri2evtype(int priority) +{ + switch (priority) { + default: + case LOG_EMERG: case LOG_ALERT: + case LOG_CRIT: case LOG_ERR: + return EVENTLOG_ERROR_TYPE; + case LOG_WARNING: + return EVENTLOG_WARNING_TYPE; + case LOG_NOTICE: case LOG_INFO: + case LOG_DEBUG: + return EVENTLOG_INFORMATION_TYPE; + } +} + +// Map syslog priority to string +static const char * pri2text(int priority) +{ + switch (priority) { + case LOG_EMERG: return "EMERG"; + case LOG_ALERT: return "ALERT"; + case LOG_CRIT: return "CRIT"; + default: + case LOG_ERR: return "ERROR"; + case LOG_WARNING: return "Warn"; + case LOG_NOTICE: return "Note"; + case LOG_INFO: return "Info"; + case LOG_DEBUG: return "Debug"; + } +} + +// Output cnt events from ring buffer +static void report_events(int cnt) +{ + if (cnt <= 0) + return; + if (cnt > MAXLINES) + cnt = MAXLINES; + + int pri = evt_priorities[evt_index_out]; + + const char * msgs[3+MAXLINES]; + msgs[0] = sl_ident; + msgs[1] = sl_pidstr; + msgs[2] = pri2text(pri); + for (int i = 0; i < cnt; i++) { + //assert(evt_priorities[evt_index_out] == pri); + msgs[3+i] = evt_lines[evt_index_out]; + if (++evt_index_out >= MAXLINES) + evt_index_out = 0; + } + ReportEventA(sl_hevtsrc, + pri2evtype(pri), // type + 0, MSG_SYSLOG+cnt, // category, message id + NULL, // no security id + (WORD)(3+cnt), 0, // 3+cnt strings, ... + msgs, NULL); // ... , no data +} + +// Thread to combine several syslog lines into one event log entry +static ULONG WINAPI event_logger_thread(LPVOID) +{ + int cnt = 0; + for (;;) { + // Wait for first line ... + if (cnt == 0) { + if (WaitForSingleObject(evt_wait_out, (evt_timeout? INFINITE : 0)) != WAIT_OBJECT_0) + break; + cnt = 1; + } + + // ... wait some time for more lines with same prior + int i = evt_index_out; + int prior = evt_priorities[i]; + int rest = 0; + while (cnt < MAXLINES) { + long timeout = + evt_timeout * ((1000L * (MAXLINES-cnt+1))/MAXLINES); + if (WaitForSingleObject(evt_wait_out, timeout) != WAIT_OBJECT_0) + break; + if (++i >= MAXLINES) + i = 0; + if (evt_priorities[i] != prior) { + rest = 1; + break; + } + cnt++; + } + + // Output all in one event log entry + report_events(cnt); + + // Signal space + if (!ReleaseSemaphore(evt_wait_in, cnt, NULL)) + break; + cnt = rest; + } + return 0; +} + +static void on_exit_event_logger(void) +{ + // Output lines immediate if exiting + evt_timeout = 0; + // Wait for thread to finish + WaitForSingleObject(evt_hthread, 1000L); + CloseHandle(evt_hthread); +#if 0 + if (sl_hevtsrc) { + DeregisterEventSource(sl_hevtsrc); sl_hevtsrc = 0; + } +#else + // Leave event message source open to prevent losing messages during shutdown +#endif +} + +static int start_event_logger() +{ + evt_timeout = 1; + if ( !(evt_wait_in = CreateSemaphore(NULL, MAXLINES, MAXLINES, NULL)) + || !(evt_wait_out = CreateSemaphore(NULL, 0, MAXLINES, NULL))) { + fprintf(stderr,"CreateSemaphore failed, Error=%ld\n", GetLastError()); + return -1; + } + DWORD tid; + if (!(evt_hthread = CreateThread(NULL, 0, event_logger_thread, NULL, 0, &tid))) { + fprintf(stderr,"CreateThread failed, Error=%ld\n", GetLastError()); + return -1; + } + atexit(on_exit_event_logger); + return 0; +} + +// Write lines to event log ring buffer +static void write_event_log(int priority, const char * lines) +{ + int cnt = 0; + for (int i = 0; lines[i]; i++) { + int len = 0; + while (lines[i+len] && lines[i+len] != '\n') + len++; + if (len > 0) { + // Wait for space + if (WaitForSingleObject(evt_wait_in, INFINITE) != WAIT_OBJECT_0) + return; + // Copy line + evt_priorities[evt_index_in] = priority; + memcpy(evt_lines[evt_index_in], lines+i, (len < LINELEN ? len : LINELEN)); + if (len < LINELEN) + evt_lines[evt_index_in][len] = 0; + if (++evt_index_in >= MAXLINES) + evt_index_in = 0; + // Signal avail if ring buffer full + if (++cnt >= MAXLINES) { + ReleaseSemaphore(evt_wait_out, cnt, NULL); + cnt = 0; + } + i += len; + } + if (!lines[i]) + break; + } + + // Signal avail + if (cnt > 0) + ReleaseSemaphore(evt_wait_out, cnt, NULL); + Sleep(1); +} + +// Write lines to logfile +static void write_logfile(FILE * f, int priority, const char * lines) +{ + char stamp[32]; + // 64-bit variant avoids conflict with the C11 variant of localtime_s() + __time64_t now = _time64(nullptr); + struct tm tmbuf; + if (!( !_localtime64_s(&tmbuf, &now) + && strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", &tmbuf))) { + stamp[0] = '?'; stamp[1] = 0; + } + + for (int i = 0; lines[i]; i++) { + int len = 0; + while (lines[i+len] && lines[i+len] != '\n') + len++; + if (len > 0) { + fprintf(f, "%s %s[%s]: %-5s: ", + stamp, sl_ident, sl_pidstr, pri2text(priority)); + fwrite(lines+i, len, 1, f); + fputc('\n', f); + i += len; + } + if (!lines[i]) + break; + } +} + +void openlog(const char *ident, int /* logopt */, int facility) +{ + if (sl_logpath[0] || sl_logfile || sl_hevtsrc) + return; // Already open + + strncpy(sl_ident, ident, sizeof(sl_ident)-1); + // logopt==LOG_PID assumed + int pid = getpid(); + if (snprintf(sl_pidstr, sizeof(sl_pidstr)-1, (pid >= 0 ? "%d" : "0x%X"), pid) < 0) + strcpy(sl_pidstr,"?"); + + if (facility == LOG_LOCAL0) // "ident.log" + strcat(strcpy(sl_logpath, sl_ident), ".log"); + else if (facility == LOG_LOCAL1) // stdout + sl_logfile = stdout; + else if (facility == LOG_LOCAL2) // stderr + sl_logfile = stderr; + else if (LOG_LOCAL2 < facility && facility <= LOG_LOCAL7) { // "ident[1-5].log" + snprintf(sl_logpath, sizeof(sl_logpath)-1, "%s%d.log", + sl_ident, LOG_FAC(facility)-LOG_FAC(LOG_LOCAL2)); + } + else // Assume LOG_DAEMON, use event log if possible, else "ident.log" + if (!(sl_hevtsrc = RegisterEventSourceA(NULL/*localhost*/, sl_ident))) { + // Cannot open => Use logfile + long err = GetLastError(); + strcat(strcpy(sl_logpath, sl_ident), ".log"); + fprintf(stderr, "%s: Cannot register event source (Error=%ld), writing to %s\n", + sl_ident, err, sl_logpath); + } + else { + // Start event log thread + start_event_logger(); + } + //assert(sl_logpath[0] || sl_logfile || sl_hevtsrc); + +} + +void closelog() +{ +} + +void vsyslog(int priority, const char * message, va_list args) +{ + // Translation of %m to error text not supported yet + if (strstr(message, "%m")) + message = "Internal error: \"%%m\" in log message"; + + // Format message + char buffer[1000]; + if (vsnprintf(buffer, sizeof(buffer)-1, message, args) < 0) + strcpy(buffer, "Internal Error: buffer overflow"); + + if (sl_hevtsrc) { + // Write to event log + write_event_log(priority, buffer); + } + else if (sl_logfile) { + // Write to stdout/err + write_logfile(sl_logfile, priority, buffer); + fflush(sl_logfile); + } + else if (sl_logpath[0]) { + // Append to logfile + FILE * f; + if (!(f = fopen(sl_logpath, "a"))) + return; + write_logfile(f, priority, buffer); + fclose(f); + } +} + +#ifdef TEST +// Test program + +void syslog(int priority, const char *message, ...) +{ + va_list args; + va_start(args, message); + vsyslog(priority, message, args); + va_end(args); +} + +int main(int argc, char* argv[]) +{ + openlog(argc < 2 ? "test" : argv[1], LOG_PID, (argc < 3 ? LOG_DAEMON : LOG_LOCAL1)); + syslog(LOG_INFO, "Info\n"); + syslog(LOG_WARNING, "Warning %d\n\n", 42); + syslog(LOG_ERR, "Error %s", "Fatal"); + for (int i = 0; i < 100; i++) { + char buf[LINELEN]; + if (i % 13 == 0) + Sleep(1000L); + sprintf(buf, "Log Line %d\n", i); + syslog((i % 17) ? LOG_INFO : LOG_ERR, buf); + } + closelog(); + return 0; +} + +#endif diff --git a/os_win32/syslogevt.mc b/os_win32/syslogevt.mc new file mode 100644 index 0000000..8266a61 --- /dev/null +++ b/os_win32/syslogevt.mc @@ -0,0 +1,156 @@ +;/* +; * os_win32/syslogevt.mc +; * +; * Home page of code is: http://www.smartmontools.org +; * +; * Copyright (C) 2004-10 Christian Franke +; * +; * SPDX-License-Identifier: GPL-2.0-or-later +; */ +; +;// $Id: syslogevt.mc 4760 2018-08-19 18:45:53Z chrfranke $ +; +;// Use message compiler "mc" or "windmc" to generate +;// syslogevt.rc, syslogevt.h, msg00001.bin +;// from this file. +;// MSG_SYSLOG in syslogmsg.h must be zero +;// MSG_SYSLOG_nn must be == nn +; +;// MS and binutils message compiler defaults for FacilityNames differ: +;// mc: Application = 0x000 +;// windmc: Application = 0xfff +FacilityNames = (Application = 0x000) + +MessageId=0x0 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG +Language=English +%1 +. +;// 1-10 Line SYSLOG Messages +;// %1=Ident, %2=PID, %3=Severity, %[4-13]=Line 1-10 +MessageId=0x1 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_01 +Language=English +%1[%2]:%3: %4 +. +MessageId=0x2 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_02 +Language=English +%1[%2]:%3%n +%4%n +%5 +. +MessageId=0x3 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_03 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6 +. +MessageId=0x4 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_04 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7 +. +MessageId=0x5 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_05 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7%n +%8 +. +MessageId=0x6 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_06 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7%n +%8%n +%9 +. +MessageId=0x7 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_07 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7%n +%8%n +%9%n +%10 +. +MessageId=0x8 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_08 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7%n +%8%n +%9%n +%10%n +%11 +. +MessageId=0x9 +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_09 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7%n +%8%n +%9%n +%10%n +%11%n +%12 +. +MessageId=0xa +Severity=Success +Facility=Application +SymbolicName=MSG_SYSLOG_10 +Language=English +%1[%2]:%3%n +%4%n +%5%n +%6%n +%7%n +%8%n +%9%n +%10%n +%11%n +%12%n +%13 +. diff --git a/os_win32/update-smart-drivedb.ps1.in b/os_win32/update-smart-drivedb.ps1.in new file mode 100644 index 0000000..ffec165 --- /dev/null +++ b/os_win32/update-smart-drivedb.ps1.in @@ -0,0 +1,841 @@ +# +# smartmontools drive database update script for Windows +# +# Home page of code is: https://www.smartmontools.org +# +# Copyright (C) 2022-23 Christian Franke +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# $Id: update-smart-drivedb.ps1.in 5515 2023-07-24 12:58:25Z chrfranke $ +# + +<# + .SYNOPSIS + Update smartmontools @VERSION@ drive database. + + .DESCRIPTION + update-smart-drivedb.ps1 updates SCRIPTDIR/drivedb.h from + branches/@DRIVEDB_BRANCH@ of smartmontools SVN repository. + + The downloaded file is verified with OpenPGP/GPG key ID 721042C5. + The public key block is included in the script. + + .PARAMETER Smartctl + Use this smartctl executable for syntax check ('-Smartctl -' to + disable). The default is smartctl.exe in script directory. + + .PARAMETER UrlOf + Use the URL of one of the following locations for download: + github (GitHub mirror of SVN repository) + sf (Sourceforge code browser) + svn (SVN repository) + svni (SVN repository via HTTP instead of HTTPS) + trac (Trac code browser) + The default is 'svn'. + + .PARAMETER Url + Download from this URL. A valid OpenPGP/GPG signature with '.raw.asc' + extension must also exist unless '-NoVerify' is also specified. + + .PARAMETER File + Copy from local file. A valid OpenPGP/GPG signature 'FILE.raw.asc' + must also exist unless '-NoVerify' is also specified. + + .PARAMETER Trunk + Download from SVN trunk. This requires '-NoVerify' because the trunk + versions are not signed. + + .PARAMETER Branch + If '-Branch X.Y' is specified, download from + branches/RELEASE_X_Y_DRIVEDB. This also selects the OpenPGP/GPG key + for older branches (5.40 to 6.6: Key ID DFD22559). + + .PARAMETER Insecure + Don't abort download if certificate verification fails. This option + is also required if a HTTP URL is selected with '-UrlOf' option. + If a HTTPS URL is selected, PowerShell >= 6.0 is required. + + .PARAMETER NoVerify + Don't verify signature with GnuPG. + + .PARAMETER Force + Allow downgrades. By default, the database is not replaced with an + older version of the same branch. + + .PARAMETER ExportKey + Print the OpenPGP/GPG public key block. + + .PARAMETER DryRun + Print download commands only. + + .PARAMETER Quiet + Suppress info messages. + + .PARAMETER Drivedb + Destination drive database file. + The default is drivedb.h in script directory. + + .EXAMPLE + update-smart-drivedb.ps1 + /INSTALLPATH/drivedb.h 7.2/5225 updated to 7.2/5237 + + (Regular update) + + .EXAMPLE + update-smart-drivedb.ps1 -Force -File /INSTALLPATH/drivedb.h.old + /INSTALLPATH/drivedb.h 7.2/5237 downgraded to 7.2/5225 + + (Revert to previous version) + + .EXAMPLE + update-smart-drivedb -Trunk -NoVerify -Smartctl - drivedb-trunk.h + drivedb-trunk.h 7.3/5254 newly installed (NOT VERIFIED) + + (Download the database from SVN trunk to current directory) + + .LINK + https://www.smartmontools.org/ +#> + +param ( + [string]$Smartctl, + [string]$UrlOf, + [string]$Url, + [string]$File, + [switch]$Trunk, + [string]$Branch, + [switch]$Insecure, + [switch]$NoVerify, + [switch]$Force, + [switch]$ExportKey, + [switch]$DryRun, + [switch]$Quiet, + #[switch]$Verbose, # Common parameter + [Parameter(Position=0)][string]$Drivedb +) + +$ErrorActionPreference = "Stop" + +# Set by config.status +# Default drivedb.h update branch +$default_branch="@DRIVEDB_BRANCH@" + +# GnuPG used to verify signature (disabled if empty) +$gpg = "@gnupg@" + +# Name and Directory of this script +$myname = $MyInvocation.MyCommand.Name +$mydir = Split-Path -Path $MyInvocation.MyCommand.Source +if (!$mydir) { + throw "Unknown script directory" +} + +# Default drivedb location +$default_drivedb = "$mydir\drivedb.h" + +# Default command used for syntax check +$default_smartctl = "$mydir\smartctl" + +function error($message) +{ + # Echo error messages to stdout because 'Write-Error' + # always writes a full 'ErrorRecord' to stderr. + echo "${myname}: $message" + exit 1 +} + +function warning($message) +{ + echo "${myname}: (Warning) $message" +} + +function iecho($message) +{ + if (!$script:Quiet) { + echo $message + } +} + +function vecho($message) +{ + $message | Write-Verbose +} + +function test_f($file) +{ + return Test-Path -PathType Leaf -LiteralPath $file +} + +function touch($file) +{ + if (Test-Path -PathType Leaf -LiteralPath $file) { + (Get-Item $file).LastWriteTime = Get-Date + } else { + New-Item -ItemType File -Path $file | Out-Null + } +} + +function cmp($file1, $file2) +{ + if (!( (Test-Path -PathType Leaf -LiteralPath $file1) ` + -and (Test-Path -PathType Leaf -LiteralPath $file2))) { + return $false + } + return (Get-FileHash $file1).hash -eq (Get-FileHash $file2).hash +} + +function rm_f # FILE... +{ + foreach ($file in $args) { + if (Test-Path -PathType Leaf -LiteralPath $file) { + Remove-Item $file + } + } +} + +function rm_rf($dir) +{ + if (Test-Path -PathType Container -LiteralPath $dir) { + Remove-Item -Recurse $dir + } +} + +function mv_f($oldfile, $newfile) +{ + rm_f $newfile + Rename-Item $oldfile $newfile +} + +function selecturl($url_of, [ref]$url) +{ + switch ($url_of) { + 'github' { + # https://github.com/smartmontools/smartmontools/raw/origin/$branch/smartmontools/drivedb.h + # https://github.com/smartmontools/smartmontools/raw/master/smartmontools/drivedb.h + # redirected to: + $u = 'https://raw.githubusercontent.com/smartmontools/smartmontools/master/smartmontools/drivedb.h' + } + 'sf' { $u = 'https://sourceforge.net/p/smartmontools/code/HEAD/tree/trunk/smartmontools/drivedb.h?format=raw' } + 'svn' { $u = 'https://svn.code.sf.net/p/smartmontools/code/trunk/smartmontools/drivedb.h' } + 'svni' { $u = 'http://svn.code.sf.net/p/smartmontools/code/trunk/smartmontools/drivedb.h' } + 'trac' { $u = 'https://www.smartmontools.org/export/HEAD/trunk/smartmontools/drivedb.h' } + default { error "${url_of}: is none of 'github sf svn svni trac'" } + } + $url.Value = $u +} + +function vcopy($src, $dest) +{ + if ($script:DryRun) { + echo "Copy-Item $src $dest" + } else { + vecho "Copy-Item $src $dest" + Copy-Item $src $dest + } +} + +function download($url, $file, [ref]$errmsg) +{ + $req = @{ Uri = $url; OutFile = $file; MaximumRedirection = 0 } + if ($script:Insecure) { + # #Requires -Version 6 + $req += @{ SkipCertificateCheck = $true } + } + + $errmsg.Value = "" + if ($script:DryRun) { + echo "Invoke-WebRequest $(echo @req)" + } else { + try { + Invoke-WebRequest @req + } catch { + if ($_.Exception.Message) { + $errmsg.Value = $_.Exception.Message + } else { + $errmsg.Value = "Unknown error" + } + } + } +} + +function check_file($file, $firstchar, $minsize, $maxsize) +{ + # Check file size + $size = (Get-Item -LiteralPath $file).Length + if ($size -lt $minsize) { + return "too small file size $size bytes" + } + if ($size -gt $maxsize) { + return "too large file size $size bytes" + } + + # Check first chars + switch ((Get-Content -LiteralPath $file -TotalCount 1).ToCharArray()[0]) { + "$firstchar" { } + "<" { return "HTML error message" } + default { return "unknown file contents" } + } + + return "" +} + +function unexpand_svn_id($source, $dest) +{ + # For -NoNewLine: + #Requires -Version 5 + (Get-Content -Raw -Path $source) -replace ` + ('\$'+'Id: drivedb\.h [0-9][0-9]* 2[-0-9]* [012][:0-9]*Z [a-z][a-z0-9]* \$'),('$'+'Id'+'$') ` + | Set-Content -NoNewLine -Path $dest +} + +function selectkey($branch, [ref]$key) +{ + switch -RegEx ($branch) { + '^RELEASE_(5_4[0-3]|6_[0-6])_DRIVEDB$' { +# Smartmontools Signing Key (ext. to 2024) <smartmontools-database@listi.jpberlin.de> +# Smartmontools Signing Key (through 2018) <smartmontools-database@listi.jpberlin.de> +# Smartmontools Signing Key (through 2018) <smartmontools-database@lists.sourceforge.net> +# Key ID DFD22559 + $key.Value = ` +'-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +1TC5jSVOIEqOE4TwzRz1Y/dfqSU= +=3Lcg +-----END PGP PUBLIC KEY BLOCK----- +' + } + + '^RELEASE_7_[023]_DRIVEDB$' { +# Smartmontools Signing Key (through 2025) <smartmontools-database@listi.jpberlin.de> +# Smartmontools Signing Key (through 2020) <smartmontools-database@listi.jpberlin.de> +# Key ID 721042C5 + $key.Value = ` +'-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFwmhpUBEADRoOZaXq13MrqyAmbGe6FlHi6P9ujsT/SJGhTiAoN3W1X56Dbm +KP21nO9ZAjdXnvA2OmzppfCUX7v5Q3/TG3vN3WwfyQIO/dgSaTrGa1E8odbHEGc7 +rhzYA8ekAn3TmxhOrEUTcRIogumW0zlQewHOlTe0OYsxat6/N8l3Cqn28HwZUpRH +MrJW3RgefFihQGEhXlnfzo+Tltl14IriURbwBZIDeZOk2AWLGweI0+zqTgYSbF5A +tI5rXO1QDeoyBYZhSX3MtnncwPdCnxoRasizU5w3KoZWYyKAc5bxJBJgUUp9HDOu +ATgNqekc8j28x/cUAWerXe183SBYQp0QkzMPbmE9TCGW3GjtW+Kk/NDbNe8ufj6O +hk0r7EbGyBO0qvgzHLzSsQiSsgaMCkLc5Xt4NzB4g2DvnReFU2WwgRh031lHOVLm +mvFqRtHzJb20dKufyjOmSMzNKRzURVmobECKARaBlGNP0wHYhq97n4OxM1o0eq7a +4ugaSp2q+6BSaAQhbZN8ULCF/oGA/376Sz7RNuoOmQwl9aFqnfl3YgopBIqKvnSP +h4j0QynN45rUFOe/VywTmpWKj+DonGCupxe9VvyZ87NKRgKiHprXGDrhdB0GcNXM +wV66WbjKBV7qlpSh/GH3oiHwlcYT8LNyZbxTJXcVF5ODtlZfc9zqRtUBWQARAQAB +tFNTbWFydG1vbnRvb2xzIFNpZ25pbmcgS2V5ICh0aHJvdWdoIDIwMjUpIDxzbWFy +dG1vbnRvb2xzLWRhdGFiYXNlQGxpc3RpLmpwYmVybGluLmRlPokCQQQTAQIAKwIb +AwUJDS6amwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AFAl/gnzECGQEACgkQ6nSr +JXIQQsW11g//UmnWOtIgozoqs6beK12NpZyubn/lecEd0yJPzed9cygKpObySBbT +5jz7e5IDGwFLDsTm9fE/2GoyvuVW/riyTsowxrYYleoKm4Pmv30crNruVM7mC7c8 ++rbwmx5ZlmHC1tMsM/BdIxK0gqHyAXxWmzyB/YDGElkWnq2/+wjEoARbROUoKQYL +qG6q6bv/DQvv4tq/Yw+fsaLZsR4Cou87hB3wAwR3rv3p3GC7N+if86fbkS8rQh5b +j3qwTHnf3ugyYz9iEy2pjrHqgnDMV227tP2UiC2ECy3u1Z7eQvMeN2r0x8EIB79D +G7ny7ML3QXsJG9Pamg4VHlMh+Sb23GE6rRQuv9m265PeS4/6CsbuHdGer+UaG78V +N4bfFhMWpE4sjDZlQZBcm6VLbExhuS89GI7+9zYMtLoXE6Z5Mz0XFjSKlzEK94UT +RPcDdcQUHW59NvhG77SvTKN5PHGbcs+0uQkUkvaOxoovio2vWcYANG4eIPC/YvPZ +9q7f/bhMDbKid7eIvtCgvijSiYKQLjt1FtJJZRYF/EESdWWNJTs2OgSFMgSDBE3K +Da5alJyx3+IlYFwvF/khtQnGeTB1XRIGL8G7UMaNzpvJQOAEbqEiznyqoo5cNpz+ +03wTOw9IGVJ2fcvg2g+j7ffKQfs+GDYWAqicSKHDYpW2csBAW/1QE62JARwEEwEC +AAYFAl/gnzoACgkQL83sC9OvGqvE0Af/XXZ4GWMf4rEB0G3lXr9L9bvX4a/tVWz0 +hag57D6By9R6cWNDpRtKx5R0Y1Fv+O+sPHptM3P6LUsWI0d7dEf307n34FxkI/vh +4W1g8ITvhYfJWmJTzA1kNAief45uNPx0QWhGlVf4nQzhe41XnuBdFhYfOkHGf6k8 +9SJ9qWRitzE657h6mVO0EKqvjTld8w6lR2rA+oHPQnc9iDmXcZLfSTHP/NapQXPl +qtXiR1z0BkswBBaKCnJxVPpzjQA0W8jSyhQ4qPheMjOmVaFoQxZ4CbEaFI67EmVl +kwgwf+c6BlKr3DoOca/KmHYT/9dqUv1gfoYYTCm+ATN76vYCG794EokCHAQTAQIA +BgUCX+CfRQAKCRC/CzOcZLyqiwQWD/9eNQNnKWxkYL3qjSRt0DwUUaCcFDoj40rb +fRxWdU+LZKL7KjAWoRhdfaH7T30wZ9NFenrQXaU/QzuYioz1sHRwIIRYyUp2s0Jc +VHAIuOPjk6Q3TDVnbEm0AO0Er32gdxC0DYk4RfGp95n1Aw1kd2BSvKPJuZSRJrIV +f8iU3Im1KT4Avl7Fw7FEojQMMvn/qZzeo2pk/QdrrK3KnHkQwy2edx/szY82o2a5 +g5WarFFRcxVS2H/xrvNMGUL4TsWcGd3Z2oHoZ0u5A20/PpT2xG1LGXGEwBAqtMS2 +6iRAzbQFkkLhcdETTvOSqkDWkzr7NqJ6adhLOEVXsHXNLx23p1Tn+Li/ezpQ6/eQ +QDPclU19BjARmfInDq0w5V1q0RNET1J2Xu+Adxtq+Dl8TyhCmJMzO8e4htYnIRZu +90iSgZdt5cZgoH04weXCMwDugn/+Q3rzKvRUTrEfSOivJYg65D/mhbz6HoUTs4JD +SstTYa9qNCwKQGRSeis4PAgu0hCpnDAhZuN3Ja5AFC2Wi2szQ7R+Zx/JucIBm5S4 +U30W66MtsyUHeulSJ3AV3HrbFfnqu6zfQM4XLw7MpAtQUNJceS/lWfGIquAp3tY/ +IjZIHwgZqKB3czWDhM83wBzCWgAmxyzIrpb4MBYJ5PGuCyC7R/YTdtPJXxsPQl2l +znsX/9ssa4kBHAQTAQIABgUCX+CfSAAKCRDzh2PO39IlWVcuB/9UkLaPtGY4sDDV +/A7qjSvSy93mv8gkaIj9dhqoZw+r7cLiEtX04Cz9PqocOFgCYJXKrufHNNkHke2A +jE9EJfRKiPU/bkeWmrACvtrOd/DZbdmXfxTOekOr516D2ip/U8GBPw6zxfCQVot6 +htpBpB6zzMDtzMOeLnkOxoxR4EMu5K6eJ48bHvG/lbGBByyfRzhtqPh6AAA9G1CC +IdhNkaA5W1qums3N1mCXrTBnWyjaFhdnttGQfrMdHvTQ77HeL0c2axT2y5PYfrXY +2ZfZowYLEtFXRSTpDaJfgG+qem3N+pMv6SMOG/4CvlH4/3Hq0aCNvKcY5KUXfIgT +xmc3/n/wtFNTbWFydG1vbnRvb2xzIFNpZ25pbmcgS2V5ICh0aHJvdWdoIDIwMjAp +IDxzbWFydG1vbnRvb2xzLWRhdGFiYXNlQGxpc3RpLmpwYmVybGluLmRlPokCPgQT +AQIAKAIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AFAl/gnzAFCQ0umpsACgkQ +6nSrJXIQQsVK7RAAqbZfT3wZEfJkw8MK2JlvgGWH76fHKn5ZoH5i0mA4AvN4QLbU +5Q20HmqHnO9mfAZQ6u4Tn/aFcYT7nlSsEsEmFX+s5QU2y6m2Tx9ThDbZ03ezREOS +0wNf0FOQunV9ZVPT/7cKIgWJa5mZy+LClor9OHllyGUfs9tKNzwxaHh1zBrCNJow +Fi/1bkWy3iMc7vZhWHASwPSp64KHjB4UdMz2hV4pROiUhWi7BY0exIHyZrkcANMP +Hhl9lP32ZvNbOy8osBdPUgXyK3HePD+ftcwJMkoc4mFQXYi9UY7NQpk7STRO10cx +Kq/CgDDvYxbnViRjQoJ0sfwKCaOfsnY/gea7I0aCx8uNISYpHO9iMidd/tJ7+lgx +NiKZTI0EppHYvkyMY15/NGb0gTJbYjuVYdbqDS9mnLuLQAjAX43+n9ND2NjX1o0q +Z9bBqV2VFioNmnxKqGphhRFX9jEzTklieOjhpRrd8v9ljprT6vLFNpYpeLkel8om +VFXrHxrfzKtVFcto5wqHVOcyZyE2zm1QmsS8qvWOTrNfY6p2q9MA2rysqdfgfvN7 +pNDaXutK6ooQi6YlyyTA2ANnHFKa0ncRH+dg+5OF9rhNvM7RyaBXgxF7+5gnU5Gb +VQRKbJ+LOtSKkj0pApR5AKSwyGslZ2bNVlKsADWhk5xj8QlHVlNWiht+i/6JARwE +EwECAAYFAlwmhpwACgkQL83sC9OvGqsVOggAqLB5eQrUv8E9ikD6kJCito827bzD +WF29yD7PvfhjXaz5in54jOVpwg3o9CsqIjjRW0/1bBVswC8ZL0sAdZ+GDSDMw5F2 +IpkD77gjnFY79M/e6C9xYyxYzHC7emDPSz9IroOvdkkEgrB+OABKkaOCcS18P4Lk +3WNHaPw5c7aI0z1iJP52EmSfvB8r86mtUFJB+f15eD/4vaRfkZLFjF9FQ3kgEK1U ++rV4s1O2bCFfP3WPDcc83NgwRUvtXmcSOSOIoXnemJzyJr+JnqCWVET4XWF6i20m +RFXVEpWtf5AkJYgR3z/jW0djELbBWA/35bAnpXy5pDHv9NbZsTkBZxK/kokBHAQT +AQIABgUCXCaGnQAKCRAY7NpGy/a6xn4lB/90tXTnZsgmoftol9uivfQrPdR88WmO +ZLYmUeQAd1rqSFMxe+KzO/qLuU8s6OF4nznwL2cPfbGZxezM4PiYmAmbbEU/3gTO +NwjVBBA0Gfimy/fITEezFtCigo1thkaJ195g/dqY+zE3Vt4rzC03j1vx8mUHRPU6 +kkvKj8cP0j+XHX2xQDsTXTstfnom29wBmGnvSZ9HgcdL71e1VXJXwikmnO3P4J/1 +C2LeCOlWrGqWZ2c0WBLKdJnsYUx7Dm/OvkkB4lF+zWp98zS8jS/5h+1apVgEzrdT +MvT8ydTkUr7ObKGkIhK+L+Xo5BD+V9Qf6xKGYPwhhdj/E5/kyjULrm10iQEcBBMB +AgAGBQJcJoadAAoJEPOHY87f0iVZfiUH/3yKS5wGvTeRInse8+W1WzKuto3XzqXL +ngb9QXWw7nCwqmNS7PbzDnufQi2ThKrMfcK14WgNYABNZPU75I+6bcb0oCB5tloo +IUEV/2Ut/5Hl/83zFFoNA/kQKVz8kIDqgRcxC+zY2VJ4eTKHyQDvXygVk8wnKTBa +e3gX+CIZqJHPXiiygHlbl31Mi3G1Iaxu57dP6ocV0vX1dytKSwd4Rbviwwb4L76o +/tVT9t3GwFM15uK1SqtnAaiaktEdMi3XI4d01H3VUVz/iR0XQbf13RZoEM6CJWms +Q/qvYlwkbKOdlahjoHrFlkhADSBaO9N1OZp3OYDjziIujMdt2IPKnmM= +=7MQk +-----END PGP PUBLIC KEY BLOCK----- +' + } + + default { + error "No known public key for branches/${branch}" + } + } +} + +function run_join_out_err($cmd) # $arg1, $arg2, ... +{ + $cmdobj = Get-Command -CommandType Application -Name $cmd + # Don't prepend $input with BOM + $enc = [Console]::InputEncoding + [Console]::InputEncoding = [System.Text.UTF8Encoding]::new() + $ErrorActionPreference = "Continue" # Don't abort command on first stderr output + $LASTEXITCODE = 42 + # Run command and convert ErrorRecords from stderr to plain Strings + $($input | & $cmdobj @args 2>&1) | %{ $_.ToString() } + $ErrorActionPreference = "Stop" + [Console]::InputEncoding = $enc +} + +function gpg_verify($ascfile, $file, [ref]$ok) +{ + # Create temp home dir + if (!$env:TEMP) { error "Environment variable TEMP is not set" } + $gnupgtmp = Join-Path $env:TEMP "gnupg.$(New-Guid)" + rm_rf $gnupgtmp + New-Item -Type Directory -Path $gnupgtmp | Out-Null + + # Import public key + $env:LC_MESSAGES = "C" + $out = ($public_key | run_join_out_err $gpg "--batch" "--no-tty" "--homedir=$gnupgtmp" "--import") + if ($LASTEXITCODE -ne 0) { + echo $out + exit 1 + } + vecho $out + + # Verify + $out = run_join_out_err $gpg "--batch" "--no-tty" "--homedir=$gnupgtmp" "--verify" $ascfile $file + if ($LASTEXITCODE -eq 0) { + vecho $out + $ok.Value = $true + } + else { + # print gpg error always + echo $out + $ok.Value = $false + } + + # Stop the gpg-agent possibly started by gpg + if ($gpgconf) { + $out = run_join_out_err $gpgconf "--homedir=$gnupgtmp" "--kill" "gpg-agent" + if ($LASTEXITCODE -ne 0) { + echo $out + } + } + + # Remove temp home dir + try { + rm_rf "$gnupgtmp" + } catch { + warning "$_" + } +} + +function get_db_version($file) +{ + $x = (Select-String -CaseSensitive -Pattern '^[ {]*"VERSION: *[^"]*"' -Path $file).Line ` + -replace '^[ {]*"VERSION: ([1-9][./0-9]* [^"]*)".*$','$1' + $v = $x -replace ' .*$','' + if ($v -match '^[1-9][.0-9]*$') { # trunk: get rev from expanded SVN-Id + $r = $x -replace '^[^$]*\$Id: drivedb\.h ([1-9][0-9]*) .*$','$1' + if (!($r -match '^[1-9][0-9]*$')) { + $r = "?" + } + $v = "$v/$r" + } elseif (!($v -match '^[1-9][./0-9]*$')) { + return "" + } + return $v +} + +function mv_all($prefix, $old, $new) +{ + mv_f "${prefix}${old}" "${prefix}${new}" + mv_f "${prefix}${old}.raw" "${prefix}${new}.raw" + if (test_f "${prefix}${old}.raw.asc") { + mv_f "${prefix}${old}.raw.asc" "${prefix}${new}.raw.asc" + } else { + rm_f "${prefix}${new}.raw.asc" + } +} + +# Set defaults +if (!$Smartctl) { + $Smartctl = $default_smartctl +} + +if ($Branch) { + $brname = $Branch -replace '^([567])\.([0-9][0-9]*)$','RELEASE_$1_$2_DRIVEDB' + if ($brname -eq $Branch) { + error "invalid branch version '${Branch}'" + } +} else { + $brname = $default_branch +} + +if (!$Drivedb) { + $Drivedb = $default_drivedb +} + +if ($ExportKey) { + $key = "" + selectkey $brname ([ref]$key) + echo $key + exit 0 +} + +# Check selected source +if (!$Url -and !$File) { + if (!$UrlOf) { + $UrlOf = "svn" + } + $Url = "" + selecturl $UrlOf ([ref]$Url) + if (!$Trunk) { + $Url = $Url -replace "/trunk/","/branches/$brname/" + $Url = $Url -replace "/master/","/origin/$brname/" + } elseif (!$NoVerify) { + error "'-Trunk' requires '-NoVerify'" + } +} elseif (!$UrlOf -and $Url -and !$File) { + if (!($Url -match '^[a-z][a-z0-9]*:[^ ][^ ]*$')) { + error "${Url}: Invalid URL" + } +} elseif (!$UrlOf -and !$Url -and $File) { +} else { + error "only one of '-UrlOf', '-Url', '-File' is allowed" +} + +# Determine path of signature file +$FileAsc = "" +$UrlAsc = "" +if (!$NoVerify) { + if (!$Url) { + $FileAsc = "${File}.raw.asc" + } elseif ($Url -match '\?') { + $UrlAsc = $Url -replace '\?','.raw.asc?' + } else { + $UrlAsc = "${Url}.raw.asc" + } +} + +# Check option compatibility +if ($UrlOf -eq "svni") { + if ($Insecure) { + $Insecure = $false + } + else { + error "'-UrlOf svni' requires '-Insecure'" + } +} + +# Check for smartctl +if ($Smartctl -ne "-") { + if (!(Get-Command -Type Application -Name $Smartctl -ErrorAction Ignore)) { + error "${Smartctl}: not found ('-Smartctl -' to ignore)" + } +} + +# Check for GnuPG +$gpgconf = "" +if (!$NoVerify) { + $gpgobj = $null + if ($gpg) { + $gpgobj = Get-Command -Type Application -Name $gpg -ErrorAction Ignore + } + if (!$gpgobj) { + error "GnuPG is not available ('-NoVerify' to ignore)" + } + $public_key = "" + selectkey $brname ([ref]$public_key) + # Check for gpgconf in same directory + $gpgconf = "gpgconf" + $gpgconfobj = Get-Command -Type Application -Name $gpgconf -ErrorAction Ignore; + if (!($gpgconfobj -and ((Split-Path $gpgobj.Source) -eq (Split-Path $gpgconfobj.Source)))) { + $gpgconf = "" + } +} + +# Remove possible garbage from last download +if (!$DryRun) { + rm_f "${Drivedb}.new" "${Drivedb}.new.raw" "${Drivedb}.new.raw.asc" +} + +if ($Url) { + # Download + vecho "Download drivedb.h" + $errmsg = "" + download $Url "${Drivedb}.new" ([ref]$errmsg) + if ($errmsg) { + rm_f "${Drivedb}.new" + error "drivedb.h: download failed: $errmsg" + } + + if ($UrlAsc) { + vecho "Download drivedb.h.raw.asc" + download $UrlAsc "${Drivedb}.new.raw.asc" ([ref]$errmsg) + if ($errmsg) { + rm_f "${Drivedb}.new" "${Drivedb}.new.raw.asc" + error "drivedb.h.raw.asc: download failed: $errmsg ('-NoVerify' to ignore)" + } + } +} else { + # Copy from local file + if (!(test_f $File)) { + error "${File}: file not found" + } + if ($FileAsc -and !(test_f $FileAsc)) { + error "${FileAsc}: file not found ('-NoVerify' to ignore)" + } + + vcopy $File "${Drivedb}.new" + if ($FileAsc) { + vcopy $FileAsc "${Drivedb}.new.raw.asc" + } +} + +if ($DryRun) { + exit 0 +} + +# Check files and adjust timestamps +$errmsg = check_file "${Drivedb}.new" '/' 10000 1000000 +if ($errmsg) { + rm_f "${Drivedb}.new.raw.asc" + mv_f "${Drivedb}.new" "${Drivedb}.error" + error "${Drivedb}.error: $errmsg" +} +touch "${Drivedb}.new" + +if (test_f "${Drivedb}.new.raw.asc") { + $errmsg = check_file "${Drivedb}.new.raw.asc" '-' 200 2000 + if ($errmsg) { + rm_f "${Drivedb}.new" + mv_f "${Drivedb}.new.raw.asc" "${Drivedb}.error.raw.asc" + error "${Drivedb}.error.raw.asc: $errmsg" + } + touch "${Drivedb}.new.raw.asc" +} + +# Create raw file with unexpanded SVN Id +# (This assumes newlines are LF and not CR/LF) +unexpand_svn_id "${Drivedb}.new" "${Drivedb}.new.raw" + +# Check whether installed file is identical +$equal = $false +if (test_f $Drivedb) { + if (!(test_f "${Drivedb}.raw")) { + # Create missing raw file + unexpand_svn_id "${Drivedb}" "${Drivedb}.raw" + } + # Ignore missing Id keyword expansion in new file + if ( (cmp "${Drivedb}.raw" "${Drivedb}.new.raw") ` + -and ( (cmp "${Drivedb}" "${Drivedb}.new") ` + -or (cmp "${Drivedb}.raw" "${Drivedb}.new"))) { + $equal = $true + } +} + +if (!$NoVerify) { + # Verify raw file + $ok = $false + gpg_verify "${Drivedb}.new.raw.asc" "${Drivedb}.new.raw" ([ref]$ok) + if (!$ok) { + mv_all $Drivedb ".new" ".error" + if ($equal) { + warning "${Drivedb}: *** installed file is identical to broken new file ***" + } + error "${Drivedb}.error.raw: *** BAD signature or outdated key ***" + } +} + +# Get version +$newver = get_db_version "${Drivedb}.new" +if (!$newver) { + if (!$Force) { + mv_all $Drivedb ".new" ".error" + error "${Drivedb}.error: no VERSION information found ('-Force' to ignore)" + } + $newver = "?/?" +} elseif ($newver -match '/\?$') { + if (!$Trunk) { + mv_all $Drivedb ".new" ".error" + error "${Drivedb}.error: VERSION information is incomplete ('-Trunk' to ignore)" + } +} + +if ($Smartctl -ne "-") { + # Check syntax + run_join_out_err $Smartctl -B "${Drivedb}.new" -P showall | Out-Null + if (!$?) { + mv_all $Drivedb ".new" ".error" + error "${Drivedb}.error: rejected by $Smartctl, probably no longer compatible" + } + vecho "${Smartctl}: syntax OK" +} + +# Always install if missing +rm_f "${Drivedb}.lastcheck" +if (!(Test-Path -PathType Leaf -Path $Drivedb)) { + mv_all $Drivedb ".new" "" + iecho "$Drivedb $newver newly installed$(if ($NoVerify) {" (NOT VERIFIED)"})" + exit 0 +} + +# Keep old file if identical +if ($equal) { + if (test_f "${Drivedb}.new.raw.asc") { + if (!(cmp "${Drivedb}.new.raw.asc" "${Drivedb}.raw.asc")) { + mv_f "${Drivedb}.new.raw.asc" "${Drivedb}.raw.asc" + iecho "${Drivedb}.raw.asc $newver updated" + } + } + rm_f "${Drivedb}.new" "${Drivedb}.new.raw" "${Drivedb}.new.raw.asc" + touch "${Drivedb}.lastcheck" + iecho "$Drivedb $newver is already up to date$(if ($NoVerify) {" (NOT VERIFIED)"})" + exit 0 +} + +# Check branch and file version +$oldver = $(get_db_version "${Drivedb}") +if (!$oldver) { + $oldver = "?/?" +} + +if ( ($newver -match '/\?$') ` + -or ($oldver -match '/\?$') ` + -or (($newver -replace '/.*$','') -ne ($oldver -replace '/.*$',''))) { + # Always install from trunk or other branch + $updmsg = "replaced with" +} elseif ((($newver -replace '^.*/','') - ($oldver -replace '^.*/','')) -lt 0) { + # Install older file only if '-Force' is used + if (!$Force) { + rm_f "${Drivedb}.new" "${Drivedb}.new.raw" "${Drivedb}.new.raw.asc" + iecho "$Drivedb $oldver not downgraded to $newver ('-Force' to override)" + exit 0 + } + $updmsg = "downgraded to" +} else { + $updmsg = "updated to" +} + +mv_all $Drivedb "" ".old" +mv_all $Drivedb ".new" "" +iecho "$Drivedb $oldver $updmsg $newver$(if ($NoVerify) {" (NOT VERIFIED)"})" +exit 0 diff --git a/os_win32/versioninfo.rc.in b/os_win32/versioninfo.rc.in new file mode 100644 index 0000000..99388e9 --- /dev/null +++ b/os_win32/versioninfo.rc.in @@ -0,0 +1,34 @@ +// +// os_win32/versioninfo.rc.in +// +// $Id: versioninfo.rc.in 4519 2017-10-08 15:41:54Z chrfranke $ +// + +1 VERSIONINFO + FILEVERSION @BINARY_VERSION@ + PRODUCTVERSION @BINARY_VERSION@ + FILEFLAGSMASK 0x0 + FILEFLAGS 0x0 + FILEOS 0x4 // VOS__WINDOWS32 + FILETYPE 0x1 // VFT_APP + FILESUBTYPE 0x0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "04090000" + BEGIN + VALUE "CompanyName", "www.smartmontools.org" + VALUE "FileDescription", "@DESC@" + VALUE "FileVersion", "@TEXT_VERSION@" + VALUE "InternalName", "@NAME@" + VALUE "LegalCopyright", "(C) 2002-@YY@, Bruce Allen, Christian Franke, www.smartmontools.org" + VALUE "OriginalFilename", "@NAME@.exe" + VALUE "ProductName", "smartmontools" + VALUE "ProductVersion", "@TEXT_VERSION@" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0x0000 + END +END diff --git a/os_win32/wmiquery.cpp b/os_win32/wmiquery.cpp new file mode 100644 index 0000000..929b22a --- /dev/null +++ b/os_win32/wmiquery.cpp @@ -0,0 +1,190 @@ +/* + * os_win32/wmiquery.cpp + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2011-13 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WINVER 0x0400 +#define _WIN32_WINNT WINVER + +#include "wmiquery.h" + +#include <stdio.h> + +const char * wmiquery_cpp_cvsid = "$Id: wmiquery.cpp 4760 2018-08-19 18:45:53Z chrfranke $" + WMIQUERY_H_CVSID; + + +///////////////////////////////////////////////////////////////////////////// +// com_bstr + +com_bstr::com_bstr(const char * str) +: m_bstr(0) +{ + int sz = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, (LPWSTR)0, 0); + if (sz <= 0) + return; + m_bstr = SysAllocStringLen((OLECHAR*)0, sz-1); + if (!m_bstr) + return; // throw std::bad_alloc + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, m_bstr, sz); +} + +bool com_bstr::to_str(const BSTR & bstr, std::string & str) +{ + if (!bstr) + return false; + int sz = WideCharToMultiByte(CP_ACP, 0, bstr, -1, (LPSTR)0, 0, (LPCSTR)0, (LPBOOL)0); + if (sz <= 0) + return false; + char * buf = new char[sz]; + WideCharToMultiByte(CP_ACP, 0, bstr, -1, buf, sz, (LPCSTR)0, (LPBOOL)0); + str = buf; + delete [] buf; + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +// wbem_object + +std::string wbem_object::get_str(const char * name) /*const*/ +{ + std::string s; + if (!m_intf) + return s; + + VARIANT var; VariantInit(&var); + if (m_intf->Get(com_bstr(name), 0L, &var, (CIMTYPE*)0, (LPLONG)0) /* != WBEM_S_NO_ERROR */) + return s; + + if (var.vt == VT_BSTR) + com_bstr::to_str(var.bstrVal, s); + VariantClear(&var); + return s; +} + + +///////////////////////////////////////////////////////////////////////////// +// wbem_enumerator + +bool wbem_enumerator::next(wbem_object & obj) +{ + if (!m_intf) + return false; + + ULONG n = 0; + HRESULT rc = m_intf->Next(5000 /*5s*/, 1 /*count*/, obj.m_intf.replace(), &n); + if (FAILED(rc) || n != 1) + return false; + return true; +} + + +///////////////////////////////////////////////////////////////////////////// +// wbem_services + +const CLSID xCLSID_WbemLocator = {0x4590f811, 0x1d3a, 0x11d0, {0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24}}; +const IID xIID_IWbemLocator = {0xdc12a687, 0x737f, 0x11cf, {0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24}}; + +bool wbem_services::connect() +{ + // Init COM during first call. + static HRESULT init_rc = -1; + static bool init_tried = false; + if (!init_tried) { + init_tried = true; + init_rc = CoInitialize((LPVOID)0); + } + if (!(init_rc == S_OK || init_rc == S_FALSE)) + return false; + + /// Create locator. + com_intf_ptr<IWbemLocator> locator; + HRESULT rc = CoCreateInstance(xCLSID_WbemLocator, (LPUNKNOWN)0, + CLSCTX_INPROC_SERVER, xIID_IWbemLocator, (LPVOID*)locator.replace()); + if (FAILED(rc)) + return false; + + // Set timeout flag if supported. + long flags = 0; + OSVERSIONINFOA ver; ver.dwOSVersionInfoSize = sizeof(ver); + if (GetVersionExA(&ver) && ver.dwPlatformId == VER_PLATFORM_WIN32_NT + && ( ver.dwMajorVersion >= 6 // Vista + || (ver.dwMajorVersion == 5 && ver.dwMinorVersion >= 1))) // XP + flags = WBEM_FLAG_CONNECT_USE_MAX_WAIT; // return in 2min or less + + // Connect to local server. + rc = locator->ConnectServer(com_bstr("\\\\.\\root\\cimv2"), + (BSTR)0, (BSTR)0, (BSTR)0, // User, Password, Locale + flags, (BSTR)0, (IWbemContext*)0, m_intf.replace()); + if (FAILED(rc)) + return false; + + // Set authentication information, + rc = CoSetProxyBlanket(m_intf.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + (OLECHAR*)0, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, + (RPC_AUTH_IDENTITY_HANDLE*)0, EOAC_NONE); + if (FAILED(rc)) { + m_intf.reset(); + return false; + } + + return true; +} + +bool wbem_services::vquery(wbem_enumerator & result, const char * qstr, va_list args) /*const*/ +{ + if (!m_intf) + return false; + + char qline[1024]; + vsnprintf(qline, sizeof(qline), qstr, args); + qline[sizeof(qline)-1] = 0; + + HRESULT rc = m_intf->ExecQuery( + com_bstr("WQL"), com_bstr(qline), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + (IWbemContext*)0, result.m_intf.replace()); + if (FAILED(rc)) + return false; + + return true; +} + +bool wbem_services::vquery1(wbem_object & obj, const char * qstr, va_list args) /*const*/ +{ + wbem_enumerator result; + if (!vquery(result, qstr, args)) + return false; + + if (!result.next(obj)) + return false; + + wbem_object peek; + if (result.next(peek)) + return false; + + return true; +} + +bool wbem_services::query(wbem_enumerator & result, const char * qstr, ...) /*const*/ +{ + va_list args; va_start(args, qstr); + bool ok = vquery(result, qstr, args); + va_end(args); + return ok; +} + +bool wbem_services::query1(wbem_object & obj, const char * qstr, ...) /*const*/ +{ + va_list args; va_start(args, qstr); + bool ok = vquery1(obj, qstr, args); + va_end(args); + return ok; +} diff --git a/os_win32/wmiquery.h b/os_win32/wmiquery.h new file mode 100644 index 0000000..47b6f37 --- /dev/null +++ b/os_win32/wmiquery.h @@ -0,0 +1,180 @@ +/* + * os_win32/wmiquery.h + * + * Home page of code is: http://www.smartmontools.org + * + * Copyright (C) 2011-18 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef WMIQUERY_H +#define WMIQUERY_H + +#define WMIQUERY_H_CVSID "$Id: wmiquery.h 4760 2018-08-19 18:45:53Z chrfranke $" + +#include <wbemcli.h> + +#include <string> + +#ifndef __GNUC__ +#define __attribute_format_printf(x, y) /**/ +#elif defined(__MINGW32__) && __USE_MINGW_ANSI_STDIO +// Check format of __mingw_*printf() instead of MSVCRT.DLL:*printf() +#define __attribute_format_printf(x, y) __attribute__((format (gnu_printf, x, y))) +#else +#define __attribute_format_printf(x, y) __attribute__((format (printf, x, y))) +#endif + +///////////////////////////////////////////////////////////////////////////// +// com_bstr + +/// Wrapper class for COM BSTR +class com_bstr +{ +public: + /// Construct from string. + explicit com_bstr(const char * str); + + /// Destructor frees BSTR. + ~com_bstr() + { SysFreeString(m_bstr); } + + /// Implicit conversion to BSTR. + operator BSTR() + { return m_bstr; } + + /// Convert BSTR back to std::string. + static bool to_str(const BSTR & bstr, std::string & str); + +private: + BSTR m_bstr; + + com_bstr(const com_bstr &); + void operator=(const com_bstr &); +}; + + +///////////////////////////////////////////////////////////////////////////// +// com_intf_ptr + +/// Wrapper class for COM Interface pointer +template <class T> +class com_intf_ptr +{ +public: + /// Construct empty object + com_intf_ptr() + : m_ptr(0) { } + + /// Destructor releases the interface. + ~com_intf_ptr() + { reset(); } + + /// Release interface and clear the pointer. + void reset() + { + if (m_ptr) { + m_ptr->Release(); m_ptr = 0; + } + } + + /// Return the pointer. + T * get() + { return m_ptr; } + + /// Pointer dereferencing. + T * operator->() + { return m_ptr; } + + /// Return address of pointer for replacement. + T * * replace() + { reset(); return &m_ptr; } + + /// For (ptr != 0) check. + operator bool() const + { return !!m_ptr; } + + /// For (ptr == 0) check. + bool operator!() const + { return !m_ptr; } + +private: + T * m_ptr; + + com_intf_ptr(const com_intf_ptr &); + void operator=(const com_intf_ptr &); +}; + + +///////////////////////////////////////////////////////////////////////////// +// wbem_object + +class wbem_enumerator; + +/// Wrapper class for IWbemClassObject +class wbem_object +{ +public: + /// Get string representation. + std::string get_str(const char * name) /*const*/; + +private: + /// Contents is set by wbem_enumerator. + friend class wbem_enumerator; + com_intf_ptr<IWbemClassObject> m_intf; +}; + + +///////////////////////////////////////////////////////////////////////////// +// wbem_enumerator + +class wbem_services; + +/// Wrapper class for IEnumWbemClassObject +class wbem_enumerator +{ +public: + /// Get next object, return false if none or error. + bool next(wbem_object & obj); + +private: + /// Contents is set by wbem_services. + friend class wbem_services; + com_intf_ptr<IEnumWbemClassObject> m_intf; +}; + + +///////////////////////////////////////////////////////////////////////////// +// wbem_services + +/// Wrapper class for IWbemServices +class wbem_services +{ +public: + /// Connect to service, return false on error. + bool connect(); + + /// Execute query, get result list. + /// Return false on error. + bool vquery(wbem_enumerator & result, const char * qstr, va_list args) /*const*/ + __attribute_format_printf(3, 0); + + /// Execute query, get single result object. + /// Return false on error or result size != 1. + bool vquery1(wbem_object & obj, const char * qstr, va_list args) /*const*/ + __attribute_format_printf(3, 0); + + /// Version of vquery() with printf() formatting. + bool query(wbem_enumerator & result, const char * qstr, ...) /*const*/ + __attribute_format_printf(3, 4); + + /// Version of vquery1() with printf() formatting. + bool query1(wbem_object & obj, const char * qstr, ...) /*const*/ + __attribute_format_printf(3, 4); + +private: + com_intf_ptr<IWbemServices> m_intf; +}; + +#endif // WMIQUERY_H diff --git a/os_win32/wtssendmsg.c b/os_win32/wtssendmsg.c new file mode 100644 index 0000000..4ee0d3c --- /dev/null +++ b/os_win32/wtssendmsg.c @@ -0,0 +1,179 @@ +/* + * WTSSendMessage() command line tool + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2012-19 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define WINVER 0x0501 +#define _WIN32_WINNT WINVER + +char svnid[] = "$Id: wtssendmsg.c 4941 2019-08-08 18:56:36Z chrfranke $"; + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <wtsapi32.h> + + +static int usage() +{ + printf("wtssendmsg $Revision: 4941 $ - Display a message box on client desktops\n" + "Copyright (C) 2012-19 Christian Franke, www.smartmontools.org\n\n" + "Usage: wtssendmsg [-cas] [-t TIMEOUT] [-w 0..5] [-v] [\"Caption\"] \"Message\"|-\n" + " wtssendmsg -v\n\n" + " -c Console session [default]\n" + " -a Active sessions\n" + " -s Connected sessions\n" + " -t Remove message box after TIMEOUT seconds\n" + " -w Select buttons and wait for response or timeout\n" + " -v List sessions\n" + ); + return 1; +} + +static int getnum(const char * s) +{ + char * endp; + int n = strtol(s, &endp, 10); + if (*endp) + return -1; + return n; +} + +int main(int argc, const char **argv) +{ + int mode = 0, timeout = 0, buttons = -1, verbose = 0, i; + + for (i = 1; i < argc && argv[i][0] == '-' && argv[i][1]; i++) { + int j; + for (j = 1; argv[i][j]; j++) { + switch (argv[i][j]) { + case 'c': mode = 0; continue; + case 'a': mode = 1; continue; + case 's': mode = 2; continue; + case 't': + if (argv[i][j+1] || ++i >= argc) + return usage(); + timeout = getnum(argv[i]); + if (timeout < 0) + return usage(); + break; + case 'w': + if (argv[i][j+1] || ++i >= argc) + return usage(); + buttons = getnum(argv[i]); + if (!(MB_OK <= buttons && buttons <= MB_RETRYCANCEL)) // 0..5 + return usage(); + break; + case 'v': verbose = 1; continue; + default: return usage(); + } + break; + } + } + + const char * message = 0, * caption = ""; + char msgbuf[1024]; + if (i < argc) { + if (i+1 < argc) + caption = argv[i++]; + + message = argv[i++]; + if (i < argc) + return usage(); + + if (!strcmp(message, "-")) { + // Read message from stdin + // The message is also written to a Windows event log entry, so + // don't convert '\r\n' to '\n' (the MessageBox works with both) + i = 0; + DWORD size = 0; + do + if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), + msgbuf+i, sizeof(msgbuf)-1-i, &size, (OVERLAPPED*)0)) + break; // May fail with ERROR_BROKEN_PIPE instead of EOF + while (size > 0 && (i += size) < (int)sizeof(msgbuf)-1); + msgbuf[i] = 0; + message = msgbuf; + } + } + else { + if (!verbose) + return usage(); + } + + // Get session list + WTS_SESSION_INFOA * sessions; DWORD count; + if (!WTSEnumerateSessionsA(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessions, &count)) { + fprintf(stderr, "WTSEnumerateSessions() failed\n"); + return 1; + } + + int status = 0; + for (i = 0; i < (int)count; i++) { + + if (verbose) { + printf("Session %d (\"%s\", State=%d)%s", + i, sessions[i].pWinStationName, sessions[i].State, + (!message ? "\n" : ": ")); + if (!message) + continue; // List sessions only + fflush(stdout); + } + + // Check session state + if (!( !strcmpi(sessions[i].pWinStationName, "Console") + || (mode >= 1 && sessions[i].State == WTSActive) + || (mode >= 2 && sessions[i].State == WTSConnected))) { + if (verbose) + printf("ignored\n"); + continue; + } + + // Send Message + DWORD response = ~0; + if (!WTSSendMessageA(WTS_CURRENT_SERVER_HANDLE, sessions[i].SessionId, + (char *)caption, strlen(caption), + (char *)message, strlen(message), + (buttons <= MB_OK ? MB_OK|MB_ICONEXCLAMATION + : buttons|MB_DEFBUTTON2|MB_ICONQUESTION ), + timeout, &response, (buttons >= MB_OK) /*Wait?*/ )) { + status |= 0x01; + if (verbose) + printf("WTSSendMessage() failed with error=%d\n", (int)GetLastError()); + else + fprintf(stderr, "Session %d (\"%s\", State=%d): WTSSendMessage() failed with error=%d\n", + i, sessions[i].pWinStationName, sessions[i].State, (int)GetLastError()); + continue; + } + + if (buttons >= MB_OK) { + switch (response) { + case IDOK: + case IDYES: case IDABORT: status |= 0x02; break; + case IDNO: case IDRETRY: status |= 0x04; break; + case IDCANCEL: case IDIGNORE: status |= 0x08; break; + case IDTIMEOUT: status |= 0x10; break; + default: status |= 0x01; break; + } + if (verbose) + printf("response = %d, status = 0x%02x\n", (int)response, status); + } + else { + // response == IDASYNC + if (verbose) + printf("message sent\n"); + } + } + + WTSFreeMemory(sessions); + + return status; +} |