summaryrefslogtreecommitdiffstats
path: root/os_win32
diff options
context:
space:
mode:
Diffstat (limited to 'os_win32')
-rw-r--r--os_win32/daemon_win32.cpp1107
-rw-r--r--os_win32/daemon_win32.h55
-rw-r--r--os_win32/default.manifest27
-rw-r--r--os_win32/installer.nsi946
-rw-r--r--os_win32/popen.h66
-rw-r--r--os_win32/popen_win32.cpp348
-rw-r--r--os_win32/runcmd.c76
-rw-r--r--os_win32/smartd_mailer.conf.sample.ps131
-rw-r--r--os_win32/smartd_mailer.ps190
-rw-r--r--os_win32/smartd_warning.cmd210
-rw-r--r--os_win32/syslog.h62
-rw-r--r--os_win32/syslog_win32.cpp375
-rw-r--r--os_win32/syslogevt.mc156
-rw-r--r--os_win32/update-smart-drivedb.ps1.in841
-rw-r--r--os_win32/versioninfo.rc.in34
-rw-r--r--os_win32/wmiquery.cpp190
-rw-r--r--os_win32/wmiquery.h180
-rw-r--r--os_win32/wtssendmsg.c179
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-----
+
+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
+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;
+}