summaryrefslogtreecommitdiffstats
path: root/os_win32
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--os_win32.cpp4857
-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
19 files changed, 9830 insertions, 0 deletions
diff --git a/os_win32.cpp b/os_win32.cpp
new file mode 100644
index 0000000..8f04b4c
--- /dev/null
+++ b/os_win32.cpp
@@ -0,0 +1,4857 @@
+/*
+ * os_win32.cpp
+ *
+ * Home page of code is: https://www.smartmontools.org
+ *
+ * Copyright (C) 2004-22 Christian Franke
+ *
+ * Original AACRaid code:
+ * Copyright (C) 2015 Nidhi Malhotra <nidhi.malhotra@pmcs.com>
+ *
+ * Original Areca code:
+ * Copyright (C) 2012 Hank Wu <hank@areca.com.tw>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WINVER 0x0502
+#define _WIN32_WINNT WINVER
+
+#include "atacmds.h"
+#include "scsicmds.h"
+#include "nvmecmds.h"
+#include "utility.h"
+
+#include "dev_interface.h"
+#include "dev_ata_cmd_set.h"
+#include "dev_areca.h"
+
+#include "os_win32/wmiquery.h"
+#include "os_win32/popen.h"
+
+// TODO: Move from smartctl.h to other include file
+extern unsigned char failuretest_permissive;
+
+#include <errno.h>
+
+#ifdef _DEBUG
+#include <assert.h>
+#else
+#undef assert
+#define assert(x) /* */
+#endif
+
+#include <stddef.h> // offsetof()
+
+#include <windows.h>
+#include <ntddscsi.h> // IOCTL_ATA_PASS_THROUGH, IOCTL_SCSI_PASS_THROUGH, ...
+// #include <nvme.h> // NVME_COMMAND, missing in older versions of Mingw-w64
+
+#ifndef _WIN32
+// csmisas.h and aacraid.h require _WIN32 but w32api-headers no longer define it on Cygwin
+// (aacraid.h also checks for _WIN64 which is also set on Cygwin x64)
+#define _WIN32
+#endif
+
+// CSMI support
+#include "csmisas.h"
+
+// aacraid support
+#include "aacraid.h"
+
+#ifndef _WIN64
+#define SELECT_WIN_32_64(x32, x64) (x32)
+#else
+#define SELECT_WIN_32_64(x32, x64) (x64)
+#endif
+
+// Cygwin does no longer provide strn?icmp() compatibility macros
+// MSVCRT does not provide strn?casecmp()
+#if defined(__CYGWIN__) && !defined(stricmp)
+#define stricmp strcasecmp
+#define strnicmp strncasecmp
+#endif
+
+const char * os_win32_cpp_cvsid = "$Id: os_win32.cpp 5419 2022-11-22 17:30:56Z chrfranke $";
+
+/////////////////////////////////////////////////////////////////////////////
+// Windows I/O-controls, some declarations are missing in the include files
+
+extern "C" {
+
+// SMART_* IOCTLs, also known as DFP_* (Disk Fault Protection)
+
+STATIC_ASSERT(SMART_GET_VERSION == 0x074080);
+STATIC_ASSERT(SMART_SEND_DRIVE_COMMAND == 0x07c084);
+STATIC_ASSERT(SMART_RCV_DRIVE_DATA == 0x07c088);
+STATIC_ASSERT(sizeof(GETVERSIONINPARAMS) == 24);
+STATIC_ASSERT(sizeof(SENDCMDINPARAMS) == 32+1);
+STATIC_ASSERT(sizeof(SENDCMDOUTPARAMS) == 16+1);
+
+
+// IDE PASS THROUGH (2000, XP, undocumented)
+
+#ifndef IOCTL_IDE_PASS_THROUGH
+
+#define IOCTL_IDE_PASS_THROUGH \
+ CTL_CODE(IOCTL_SCSI_BASE, 0x040A, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+#endif // IOCTL_IDE_PASS_THROUGH
+
+#pragma pack(1)
+
+typedef struct {
+ IDEREGS IdeReg;
+ ULONG DataBufferSize;
+ UCHAR DataBuffer[1];
+} ATA_PASS_THROUGH;
+
+#pragma pack()
+
+STATIC_ASSERT(IOCTL_IDE_PASS_THROUGH == 0x04d028);
+STATIC_ASSERT(sizeof(ATA_PASS_THROUGH) == 12+1);
+
+
+// ATA PASS THROUGH (Win2003, XP SP2)
+
+STATIC_ASSERT(IOCTL_ATA_PASS_THROUGH == 0x04d02c);
+STATIC_ASSERT(sizeof(ATA_PASS_THROUGH_EX) == SELECT_WIN_32_64(40, 48));
+
+
+// IOCTL_SCSI_PASS_THROUGH[_DIRECT]
+
+STATIC_ASSERT(IOCTL_SCSI_PASS_THROUGH == 0x04d004);
+STATIC_ASSERT(IOCTL_SCSI_PASS_THROUGH_DIRECT == 0x04d014);
+STATIC_ASSERT(sizeof(SCSI_PASS_THROUGH) == SELECT_WIN_32_64(44, 56));
+STATIC_ASSERT(sizeof(SCSI_PASS_THROUGH_DIRECT) == SELECT_WIN_32_64(44, 56));
+
+
+// SMART IOCTL via SCSI MINIPORT ioctl
+
+#ifndef FILE_DEVICE_SCSI
+#define FILE_DEVICE_SCSI 0x001b
+#endif
+
+#ifndef IOCTL_SCSI_MINIPORT_SMART_VERSION
+
+#define IOCTL_SCSI_MINIPORT_SMART_VERSION ((FILE_DEVICE_SCSI << 16) + 0x0500)
+#define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI << 16) + 0x0501)
+#define IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS ((FILE_DEVICE_SCSI << 16) + 0x0502)
+#define IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS ((FILE_DEVICE_SCSI << 16) + 0x0503)
+#define IOCTL_SCSI_MINIPORT_ENABLE_SMART ((FILE_DEVICE_SCSI << 16) + 0x0504)
+#define IOCTL_SCSI_MINIPORT_DISABLE_SMART ((FILE_DEVICE_SCSI << 16) + 0x0505)
+#define IOCTL_SCSI_MINIPORT_RETURN_STATUS ((FILE_DEVICE_SCSI << 16) + 0x0506)
+#define IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE ((FILE_DEVICE_SCSI << 16) + 0x0507)
+#define IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES ((FILE_DEVICE_SCSI << 16) + 0x0508)
+#define IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS ((FILE_DEVICE_SCSI << 16) + 0x0509)
+#define IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE ((FILE_DEVICE_SCSI << 16) + 0x050a)
+#define IOCTL_SCSI_MINIPORT_READ_SMART_LOG ((FILE_DEVICE_SCSI << 16) + 0x050b)
+#define IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG ((FILE_DEVICE_SCSI << 16) + 0x050c)
+
+#endif // IOCTL_SCSI_MINIPORT_SMART_VERSION
+
+STATIC_ASSERT(IOCTL_SCSI_MINIPORT == 0x04d008);
+STATIC_ASSERT(IOCTL_SCSI_MINIPORT_SMART_VERSION == 0x1b0500);
+STATIC_ASSERT(sizeof(SRB_IO_CONTROL) == 28);
+
+
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+STATIC_ASSERT(IOCTL_STORAGE_QUERY_PROPERTY == 0x002d1400);
+STATIC_ASSERT(sizeof(STORAGE_DEVICE_DESCRIPTOR) == 36+1+3);
+STATIC_ASSERT(sizeof(STORAGE_PROPERTY_QUERY) == 8+1+3);
+
+
+// IOCTL_STORAGE_QUERY_PROPERTY: Windows 10 enhancements
+
+namespace win10 {
+
+ // enum STORAGE_PROPERTY_ID: new values
+ const STORAGE_PROPERTY_ID StorageAdapterProtocolSpecificProperty = (STORAGE_PROPERTY_ID)49;
+ const STORAGE_PROPERTY_ID StorageDeviceProtocolSpecificProperty = (STORAGE_PROPERTY_ID)50;
+
+ typedef enum _STORAGE_PROTOCOL_TYPE {
+ ProtocolTypeUnknown = 0,
+ ProtocolTypeScsi,
+ ProtocolTypeAta,
+ ProtocolTypeNvme,
+ ProtocolTypeSd
+ } STORAGE_PROTOCOL_TYPE;
+
+ typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE {
+ NVMeDataTypeUnknown = 0,
+ NVMeDataTypeIdentify,
+ NVMeDataTypeLogPage,
+ NVMeDataTypeFeature
+ } STORAGE_PROTOCOL_NVME_DATA_TYPE;
+
+ typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {
+ STORAGE_PROTOCOL_TYPE ProtocolType;
+ ULONG DataType;
+ ULONG ProtocolDataRequestValue;
+ ULONG ProtocolDataRequestSubValue;
+ ULONG ProtocolDataOffset;
+ ULONG ProtocolDataLength;
+ ULONG FixedProtocolReturnData;
+ ULONG Reserved[3];
+ } STORAGE_PROTOCOL_SPECIFIC_DATA;
+
+ STATIC_ASSERT(sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) == 40);
+
+} // namespace win10
+
+
+// IOCTL_STORAGE_PREDICT_FAILURE
+
+STATIC_ASSERT(IOCTL_STORAGE_PREDICT_FAILURE == 0x002d1100);
+STATIC_ASSERT(sizeof(STORAGE_PREDICT_FAILURE) == 4+512);
+
+// IOCTL_STORAGE_PROTOCOL_COMMAND
+
+#ifndef IOCTL_STORAGE_PROTOCOL_COMMAND
+
+#define IOCTL_STORAGE_PROTOCOL_COMMAND \
+ CTL_CODE(IOCTL_STORAGE_BASE, 0x04f0, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+#endif // IOCTL_STORAGE_PROTOCOL_COMMAND
+
+#ifndef STORAGE_PROTOCOL_STRUCTURE_VERSION
+
+#define STORAGE_PROTOCOL_STRUCTURE_VERSION 1
+
+typedef struct _STORAGE_PROTOCOL_COMMAND {
+ DWORD Version;
+ DWORD Length;
+ win10::STORAGE_PROTOCOL_TYPE ProtocolType;
+ DWORD Flags;
+ DWORD ReturnStatus;
+ DWORD ErrorCode;
+ DWORD CommandLength;
+ DWORD ErrorInfoLength;
+ DWORD DataToDeviceTransferLength;
+ DWORD DataFromDeviceTransferLength;
+ DWORD TimeOutValue;
+ DWORD ErrorInfoOffset;
+ DWORD DataToDeviceBufferOffset;
+ DWORD DataFromDeviceBufferOffset;
+ DWORD CommandSpecific;
+ DWORD Reserved0;
+ DWORD FixedProtocolReturnData;
+ DWORD Reserved1[3];
+ BYTE Command[1];
+} STORAGE_PROTOCOL_COMMAND;
+
+#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST 0x80000000
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND 0x01
+#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME 0x40
+
+#endif // STORAGE_PROTOCOL_STRUCTURE_VERSION
+
+STATIC_ASSERT(IOCTL_STORAGE_PROTOCOL_COMMAND == 0x002dd3c0);
+STATIC_ASSERT(offsetof(STORAGE_PROTOCOL_COMMAND, Command) == 80);
+STATIC_ASSERT(sizeof(STORAGE_PROTOCOL_COMMAND) == 84);
+
+// NVME_COMMAND from <nvme.h>
+
+#ifndef NVME_NAMESPACE_ALL
+
+typedef union {
+ struct {
+ ULONG OPC : 8;
+ ULONG _unused : 24;
+ };
+ ULONG AsUlong;
+} NVME_COMMAND_DWORD0;
+
+typedef struct {
+ NVME_COMMAND_DWORD0 CDW0;
+ ULONG NSID;
+ ULONGLONG _unused[4];
+ union {
+ struct {
+ ULONG CDW10;
+ ULONG CDW11;
+ ULONG CDW12;
+ ULONG CDW13;
+ ULONG CDW14;
+ ULONG CDW15;
+ } GENERAL;
+ // Others: Not used
+ } u;
+} NVME_COMMAND;
+
+#endif
+
+STATIC_ASSERT(sizeof(NVME_COMMAND) == STORAGE_PROTOCOL_COMMAND_LENGTH_NVME);
+STATIC_ASSERT(offsetof(NVME_COMMAND, u.GENERAL.CDW10) == 40);
+
+// 3ware specific versions of SMART ioctl structs
+
+#define SMART_VENDOR_3WARE 0x13C1 // identifies 3ware specific parameters
+
+#pragma pack(1)
+
+typedef struct _GETVERSIONINPARAMS_EX {
+ BYTE bVersion;
+ BYTE bRevision;
+ BYTE bReserved;
+ BYTE bIDEDeviceMap;
+ DWORD fCapabilities;
+ DWORD dwDeviceMapEx; // 3ware specific: RAID drive bit map
+ WORD wIdentifier; // Vendor specific identifier
+ WORD wControllerId; // 3ware specific: Controller ID (0,1,...)
+ ULONG dwReserved[2];
+} GETVERSIONINPARAMS_EX;
+
+typedef struct _SENDCMDINPARAMS_EX {
+ DWORD cBufferSize;
+ IDEREGS irDriveRegs;
+ BYTE bDriveNumber;
+ BYTE bPortNumber; // 3ware specific: port number
+ WORD wIdentifier; // Vendor specific identifier
+ DWORD dwReserved[4];
+ BYTE bBuffer[1];
+} SENDCMDINPARAMS_EX;
+
+#pragma pack()
+
+STATIC_ASSERT(sizeof(GETVERSIONINPARAMS_EX) == sizeof(GETVERSIONINPARAMS));
+STATIC_ASSERT(sizeof(SENDCMDINPARAMS_EX) == sizeof(SENDCMDINPARAMS));
+
+
+// NVME_PASS_THROUGH
+
+#ifndef NVME_PASS_THROUGH_SRB_IO_CODE
+
+#define NVME_SIG_STR "NvmeMini"
+#define NVME_STORPORT_DRIVER 0xe000
+
+#define NVME_PASS_THROUGH_SRB_IO_CODE \
+ CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#pragma pack(1)
+typedef struct _NVME_PASS_THROUGH_IOCTL
+{
+ SRB_IO_CONTROL SrbIoCtrl;
+ ULONG VendorSpecific[6];
+ ULONG NVMeCmd[16]; // Command DW[0...15]
+ ULONG CplEntry[4]; // Completion DW[0...3]
+ ULONG Direction; // 0=No, 1=Out, 2=In, 3=I/O
+ ULONG QueueId; // 0=AdminQ
+ ULONG DataBufferLen; // sizeof(DataBuffer) if Data In
+ ULONG MetaDataLen;
+ ULONG ReturnBufferLen; // offsetof(DataBuffer), plus sizeof(DataBuffer) if Data Out
+ UCHAR DataBuffer[1];
+} NVME_PASS_THROUGH_IOCTL;
+#pragma pack()
+
+#endif // NVME_PASS_THROUGH_SRB_IO_CODE
+
+STATIC_ASSERT(NVME_PASS_THROUGH_SRB_IO_CODE == (int)0xe0002000);
+STATIC_ASSERT(sizeof(NVME_PASS_THROUGH_IOCTL) == 152+1);
+STATIC_ASSERT(sizeof(NVME_PASS_THROUGH_IOCTL) == offsetof(NVME_PASS_THROUGH_IOCTL, DataBuffer)+1);
+
+
+// CSMI structs
+
+STATIC_ASSERT(sizeof(IOCTL_HEADER) == sizeof(SRB_IO_CONTROL));
+STATIC_ASSERT(sizeof(CSMI_SAS_DRIVER_INFO_BUFFER) == 204);
+STATIC_ASSERT(sizeof(CSMI_SAS_PHY_INFO_BUFFER) == 2080);
+STATIC_ASSERT(sizeof(CSMI_SAS_STP_PASSTHRU_BUFFER) == 168);
+
+// aacraid struct
+
+STATIC_ASSERT(sizeof(SCSI_REQUEST_BLOCK) == SELECT_WIN_32_64(64, 88));
+
+} // extern "C"
+
+/////////////////////////////////////////////////////////////////////////////
+
+namespace os_win32 { // no need to publish anything, name provided for Doxygen
+
+#ifdef _MSC_VER
+#pragma warning(disable:4250)
+#endif
+
+static int is_permissive()
+{
+ if (!failuretest_permissive) {
+ pout("To continue, add one or more '-T permissive' options.\n");
+ return 0;
+ }
+ failuretest_permissive--;
+ return 1;
+}
+
+// return number for drive letter, -1 on error
+// "[A-Za-z]:([/\\][.]?)?" => 0-25
+// Accepts trailing '"' to fix broken "X:\" parameter passing from .bat files
+static int drive_letter(const char * s)
+{
+ return ( (('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z'))
+ && s[1] == ':'
+ && (!s[2] || ( strchr("/\\\"", s[2])
+ && (!s[3] || (s[3] == '.' && !s[4]))) ) ?
+ (s[0] & 0x1f) - 1 : -1);
+}
+
+// Skip trailing "/dev/", do not allow "/dev/X:"
+static const char * skipdev(const char * s)
+{
+ return (!strncmp(s, "/dev/", 5) && drive_letter(s+5) < 0 ? s+5 : s);
+}
+
+// "sd[a-z]" -> 0-25, "sd[a-z][a-z]" -> 26-701
+static int sdxy_to_phydrive(const char (& xy)[2+1])
+{
+ int phydrive = xy[0] - 'a';
+ if (xy[1])
+ phydrive = (phydrive + 1) * ('z' - 'a' + 1) + (xy[1] - 'a');
+ return phydrive;
+}
+
+static void copy_swapped(unsigned char * dest, const char * src, int destsize)
+{
+ int srclen = strcspn(src, "\r\n");
+ int i;
+ for (i = 0; i < destsize-1 && i < srclen-1; i+=2) {
+ dest[i] = src[i+1]; dest[i+1] = src[i];
+ }
+ if (i < destsize-1 && i < srclen)
+ dest[i+1] = src[i];
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_smart_device
+
+class win_smart_device
+: virtual public /*implements*/ smart_device
+{
+public:
+ win_smart_device()
+ : smart_device(never_called),
+ m_fh(INVALID_HANDLE_VALUE)
+ { }
+
+ virtual ~win_smart_device();
+
+ virtual bool is_open() const;
+
+ virtual bool close();
+
+protected:
+ /// Set handle for open() in derived classes.
+ void set_fh(HANDLE fh)
+ { m_fh = fh; }
+
+ /// Return handle for derived classes.
+ HANDLE get_fh() const
+ { return m_fh; }
+
+private:
+ HANDLE m_fh; ///< File handle
+};
+
+
+// Common routines for devices with HANDLEs
+
+win_smart_device::~win_smart_device()
+{
+ if (m_fh != INVALID_HANDLE_VALUE)
+ ::CloseHandle(m_fh);
+}
+
+bool win_smart_device::is_open() const
+{
+ return (m_fh != INVALID_HANDLE_VALUE);
+}
+
+bool win_smart_device::close()
+{
+ if (m_fh == INVALID_HANDLE_VALUE)
+ return true;
+ BOOL rc = ::CloseHandle(m_fh);
+ m_fh = INVALID_HANDLE_VALUE;
+ return !!rc;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define SMART_CYL_LOW 0x4F
+#define SMART_CYL_HI 0xC2
+
+static void print_ide_regs(const IDEREGS * r, int out)
+{
+ pout("%s=0x%02x,%s=0x%02x, SC=0x%02x, SN=0x%02x, CL=0x%02x, CH=0x%02x, SEL=0x%02x\n",
+ (out?"STS":"CMD"), r->bCommandReg, (out?"ERR":" FR"), r->bFeaturesReg,
+ r->bSectorCountReg, r->bSectorNumberReg, r->bCylLowReg, r->bCylHighReg, r->bDriveHeadReg);
+}
+
+static void print_ide_regs_io(const IDEREGS * ri, const IDEREGS * ro)
+{
+ pout(" Input : "); print_ide_regs(ri, 0);
+ if (ro) {
+ pout(" Output: "); print_ide_regs(ro, 1);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+// call SMART_GET_VERSION, return device map or -1 on error
+
+static int smart_get_version(HANDLE hdevice, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
+{
+ GETVERSIONINPARAMS vers; memset(&vers, 0, sizeof(vers));
+ const GETVERSIONINPARAMS_EX & vers_ex = (const GETVERSIONINPARAMS_EX &)vers;
+ DWORD num_out;
+
+ if (!DeviceIoControl(hdevice, SMART_GET_VERSION,
+ NULL, 0, &vers, sizeof(vers), &num_out, NULL)) {
+ if (ata_debugmode)
+ pout(" SMART_GET_VERSION failed, Error=%u\n", (unsigned)GetLastError());
+ errno = ENOSYS;
+ return -1;
+ }
+ assert(num_out == sizeof(GETVERSIONINPARAMS));
+
+ if (ata_debugmode > 1) {
+ pout(" SMART_GET_VERSION succeeded, bytes returned: %u\n"
+ " Vers = %d.%d, Caps = 0x%x, DeviceMap = 0x%02x\n",
+ (unsigned)num_out, vers.bVersion, vers.bRevision,
+ (unsigned)vers.fCapabilities, vers.bIDEDeviceMap);
+ if (vers_ex.wIdentifier == SMART_VENDOR_3WARE)
+ pout(" Identifier = %04x(3WARE), ControllerId=%u, DeviceMapEx = 0x%08x\n",
+ vers_ex.wIdentifier, vers_ex.wControllerId, (unsigned)vers_ex.dwDeviceMapEx);
+ }
+
+ if (ata_version_ex)
+ *ata_version_ex = vers_ex;
+
+ // TODO: Check vers.fCapabilities here?
+ return vers.bIDEDeviceMap;
+}
+
+
+// call SMART_* ioctl
+
+static int smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize, int port)
+{
+ SENDCMDINPARAMS inpar;
+ SENDCMDINPARAMS_EX & inpar_ex = (SENDCMDINPARAMS_EX &)inpar;
+
+ unsigned char outbuf[sizeof(SENDCMDOUTPARAMS)-1 + 512];
+ const SENDCMDOUTPARAMS * outpar;
+ DWORD code, num_out;
+ unsigned int size_out;
+ const char * name;
+
+ memset(&inpar, 0, sizeof(inpar));
+ inpar.irDriveRegs = *regs;
+
+ // Older drivers may require bits 5 and 7 set
+ // ATA-3: bits shall be set, ATA-4 and later: bits are obsolete
+ inpar.irDriveRegs.bDriveHeadReg |= 0xa0;
+
+ // Drive number 0-3 was required on Win9x/ME only
+ //inpar.irDriveRegs.bDriveHeadReg |= (drive & 1) << 4;
+ //inpar.bDriveNumber = drive;
+
+ if (port >= 0) {
+ // Set RAID port
+ inpar_ex.wIdentifier = SMART_VENDOR_3WARE;
+ inpar_ex.bPortNumber = port;
+ }
+
+ if (datasize == 512) {
+ code = SMART_RCV_DRIVE_DATA; name = "SMART_RCV_DRIVE_DATA";
+ inpar.cBufferSize = size_out = 512;
+ }
+ else if (datasize == 0) {
+ code = SMART_SEND_DRIVE_COMMAND; name = "SMART_SEND_DRIVE_COMMAND";
+ if (regs->bFeaturesReg == ATA_SMART_STATUS)
+ size_out = sizeof(IDEREGS); // ioctl returns new IDEREGS as data
+ // Note: cBufferSize must be 0 on Win9x
+ else
+ size_out = 0;
+ }
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(&outbuf, 0, sizeof(outbuf));
+
+ if (!DeviceIoControl(hdevice, code, &inpar, sizeof(SENDCMDINPARAMS)-1,
+ outbuf, sizeof(SENDCMDOUTPARAMS)-1 + size_out, &num_out, NULL)) {
+ // CAUTION: DO NOT change "regs" Parameter in this case, see win_ata_device::ata_pass_through()
+ long err = GetLastError();
+ if (ata_debugmode && (err != ERROR_INVALID_PARAMETER || ata_debugmode > 1)) {
+ pout(" %s failed, Error=%ld\n", name, err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = ( err == ERROR_INVALID_FUNCTION/*9x*/
+ || err == ERROR_INVALID_PARAMETER/*NT/2K/XP*/
+ || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+ // NOTE: On Win9x, inpar.irDriveRegs now contains the returned regs
+
+ outpar = (const SENDCMDOUTPARAMS *)outbuf;
+
+ if (outpar->DriverStatus.bDriverError) {
+ if (ata_debugmode) {
+ pout(" %s failed, DriverError=0x%02x, IDEError=0x%02x\n", name,
+ outpar->DriverStatus.bDriverError, outpar->DriverStatus.bIDEError);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (!outpar->DriverStatus.bIDEError ? ENOSYS : EIO);
+ return -1;
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" %s succeeded, bytes returned: %u (buffer %u)\n", name,
+ (unsigned)num_out, (unsigned)outpar->cBufferSize);
+ print_ide_regs_io(regs, (regs->bFeaturesReg == ATA_SMART_STATUS ?
+ (const IDEREGS *)(outpar->bBuffer) : NULL));
+ }
+
+ if (datasize)
+ memcpy(data, outpar->bBuffer, 512);
+ else if (regs->bFeaturesReg == ATA_SMART_STATUS) {
+ if (nonempty(outpar->bBuffer, sizeof(IDEREGS)))
+ memcpy(regs, outpar->bBuffer, sizeof(IDEREGS));
+ else { // Workaround for driver not returning regs
+ if (ata_debugmode)
+ pout(" WARNING: driver does not return ATA registers in output buffer!\n");
+ *regs = inpar.irDriveRegs;
+ }
+ }
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// IDE PASS THROUGH (2000, XP, undocumented)
+//
+// Based on WinATA.cpp, 2002 c't/Matthias Withopf
+// ftp://ftp.heise.de/pub/ct/listings/0207-218.zip
+
+static int ide_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, unsigned datasize)
+{
+ if (datasize > 512) {
+ errno = EINVAL;
+ return -1;
+ }
+ unsigned int size = sizeof(ATA_PASS_THROUGH)-1 + datasize;
+ ATA_PASS_THROUGH * buf = (ATA_PASS_THROUGH *)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
+ DWORD num_out;
+ const unsigned char magic = 0xcf;
+
+ if (!buf) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ buf->IdeReg = *regs;
+ buf->DataBufferSize = datasize;
+ if (datasize)
+ buf->DataBuffer[0] = magic;
+
+ if (!DeviceIoControl(hdevice, IOCTL_IDE_PASS_THROUGH,
+ buf, size, buf, size, &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" IOCTL_IDE_PASS_THROUGH failed, Error=%ld\n", err);
+ print_ide_regs_io(regs, NULL);
+ }
+ VirtualFree(buf, 0, MEM_RELEASE);
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+
+ // Check ATA status
+ if (buf->IdeReg.bCommandReg/*Status*/ & 0x01) {
+ if (ata_debugmode) {
+ pout(" IOCTL_IDE_PASS_THROUGH command failed:\n");
+ print_ide_regs_io(regs, &buf->IdeReg);
+ }
+ VirtualFree(buf, 0, MEM_RELEASE);
+ errno = EIO;
+ return -1;
+ }
+
+ // Check and copy data
+ if (datasize) {
+ if ( num_out != size
+ || (buf->DataBuffer[0] == magic && !nonempty(buf->DataBuffer+1, datasize-1))) {
+ if (ata_debugmode) {
+ pout(" IOCTL_IDE_PASS_THROUGH output data missing (%u, %u)\n",
+ (unsigned)num_out, (unsigned)buf->DataBufferSize);
+ print_ide_regs_io(regs, &buf->IdeReg);
+ }
+ VirtualFree(buf, 0, MEM_RELEASE);
+ errno = EIO;
+ return -1;
+ }
+ memcpy(data, buf->DataBuffer, datasize);
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_IDE_PASS_THROUGH succeeded, bytes returned: %u (buffer %u)\n",
+ (unsigned)num_out, (unsigned)buf->DataBufferSize);
+ print_ide_regs_io(regs, &buf->IdeReg);
+ }
+ *regs = buf->IdeReg;
+
+ // Caution: VirtualFree() fails if parameter "dwSize" is nonzero
+ VirtualFree(buf, 0, MEM_RELEASE);
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// ATA PASS THROUGH (Win2003, XP SP2)
+
+// Warning:
+// IOCTL_ATA_PASS_THROUGH[_DIRECT] can only handle one interrupt/DRQ data
+// transfer per command. Therefore, multi-sector transfers are only supported
+// for the READ/WRITE MULTIPLE [EXT] commands. Other commands like READ/WRITE SECTORS
+// or READ/WRITE LOG EXT work only with single sector transfers.
+// The latter are supported on Vista (only) through new ATA_FLAGS_NO_MULTIPLE.
+// See:
+// http://social.msdn.microsoft.com/Forums/en-US/storageplatformata/thread/eb408507-f221-455b-9bbb-d1069b29c4da
+
+static int ata_pass_through_ioctl(HANDLE hdevice, IDEREGS * regs, IDEREGS * prev_regs, char * data, int datasize)
+{
+ const int max_sectors = 32; // TODO: Allocate dynamic buffer
+
+ typedef struct {
+ ATA_PASS_THROUGH_EX apt;
+ ULONG Filler;
+ UCHAR ucDataBuf[max_sectors * 512];
+ } ATA_PASS_THROUGH_EX_WITH_BUFFERS;
+
+ const unsigned char magic = 0xcf;
+
+ ATA_PASS_THROUGH_EX_WITH_BUFFERS ab; memset(&ab, 0, sizeof(ab));
+ ab.apt.Length = sizeof(ATA_PASS_THROUGH_EX);
+ //ab.apt.PathId = 0;
+ //ab.apt.TargetId = 0;
+ //ab.apt.Lun = 0;
+ ab.apt.TimeOutValue = 60; // seconds
+ unsigned size = offsetof(ATA_PASS_THROUGH_EX_WITH_BUFFERS, ucDataBuf);
+ ab.apt.DataBufferOffset = size;
+
+ if (datasize > 0) {
+ if (datasize > (int)sizeof(ab.ucDataBuf)) {
+ errno = EINVAL;
+ return -1;
+ }
+ ab.apt.AtaFlags = ATA_FLAGS_DATA_IN;
+ ab.apt.DataTransferLength = datasize;
+ size += datasize;
+ ab.ucDataBuf[0] = magic;
+ }
+ else if (datasize < 0) {
+ if (-datasize > (int)sizeof(ab.ucDataBuf)) {
+ errno = EINVAL;
+ return -1;
+ }
+ ab.apt.AtaFlags = ATA_FLAGS_DATA_OUT;
+ ab.apt.DataTransferLength = -datasize;
+ size += -datasize;
+ memcpy(ab.ucDataBuf, data, -datasize);
+ }
+ else {
+ assert(ab.apt.AtaFlags == 0);
+ assert(ab.apt.DataTransferLength == 0);
+ }
+
+ assert(sizeof(ab.apt.CurrentTaskFile) == sizeof(IDEREGS));
+ IDEREGS * ctfregs = (IDEREGS *)ab.apt.CurrentTaskFile;
+ IDEREGS * ptfregs = (IDEREGS *)ab.apt.PreviousTaskFile;
+ *ctfregs = *regs;
+
+ if (prev_regs) {
+ *ptfregs = *prev_regs;
+ ab.apt.AtaFlags |= ATA_FLAGS_48BIT_COMMAND;
+ }
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_ATA_PASS_THROUGH,
+ &ab, size, &ab, size, &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" IOCTL_ATA_PASS_THROUGH failed, Error=%ld\n", err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+
+ // Check ATA status
+ if (ctfregs->bCommandReg/*Status*/ & (0x01/*Err*/|0x08/*DRQ*/)) {
+ if (ata_debugmode) {
+ pout(" IOCTL_ATA_PASS_THROUGH command failed:\n");
+ print_ide_regs_io(regs, ctfregs);
+ }
+ errno = EIO;
+ return -1;
+ }
+
+ // Check and copy data
+ if (datasize > 0) {
+ if ( num_out != size
+ || (ab.ucDataBuf[0] == magic && !nonempty(ab.ucDataBuf+1, datasize-1))) {
+ if (ata_debugmode) {
+ pout(" IOCTL_ATA_PASS_THROUGH output data missing (%u)\n", (unsigned)num_out);
+ print_ide_regs_io(regs, ctfregs);
+ }
+ errno = EIO;
+ return -1;
+ }
+ memcpy(data, ab.ucDataBuf, datasize);
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_ATA_PASS_THROUGH succeeded, bytes returned: %u\n", (unsigned)num_out);
+ print_ide_regs_io(regs, ctfregs);
+ }
+ *regs = *ctfregs;
+ if (prev_regs)
+ *prev_regs = *ptfregs;
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// SMART IOCTL via SCSI MINIPORT ioctl
+
+// This function is handled by ATAPI port driver (atapi.sys) or by SCSI
+// miniport driver (via SCSI port driver scsiport.sys).
+// It can be used to skip the missing or broken handling of some SMART
+// command codes (e.g. READ_LOG) in the disk class driver (disk.sys)
+
+static int ata_via_scsi_miniport_smart_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize)
+{
+ // Select code
+ DWORD code = 0; const char * name = 0;
+ if (regs->bCommandReg == ATA_IDENTIFY_DEVICE) {
+ code = IOCTL_SCSI_MINIPORT_IDENTIFY; name = "IDENTIFY";
+ }
+ else if (regs->bCommandReg == ATA_SMART_CMD) switch (regs->bFeaturesReg) {
+ case ATA_SMART_READ_VALUES:
+ code = IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS; name = "READ_SMART_ATTRIBS"; break;
+ case ATA_SMART_READ_THRESHOLDS:
+ code = IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS; name = "READ_SMART_THRESHOLDS"; break;
+ case ATA_SMART_ENABLE:
+ code = IOCTL_SCSI_MINIPORT_ENABLE_SMART; name = "ENABLE_SMART"; break;
+ case ATA_SMART_DISABLE:
+ code = IOCTL_SCSI_MINIPORT_DISABLE_SMART; name = "DISABLE_SMART"; break;
+ case ATA_SMART_STATUS:
+ code = IOCTL_SCSI_MINIPORT_RETURN_STATUS; name = "RETURN_STATUS"; break;
+ case ATA_SMART_AUTOSAVE:
+ code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE; name = "ENABLE_DISABLE_AUTOSAVE"; break;
+ //case ATA_SMART_SAVE: // obsolete since ATA-6, not used by smartmontools
+ // code = IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES; name = "SAVE_ATTRIBUTE_VALUES"; break;
+ case ATA_SMART_IMMEDIATE_OFFLINE:
+ code = IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS; name = "EXECUTE_OFFLINE_DIAGS"; break;
+ case ATA_SMART_AUTO_OFFLINE:
+ code = IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE; name = "ENABLE_DISABLE_AUTO_OFFLINE"; break;
+ case ATA_SMART_READ_LOG_SECTOR:
+ code = IOCTL_SCSI_MINIPORT_READ_SMART_LOG; name = "READ_SMART_LOG"; break;
+ case ATA_SMART_WRITE_LOG_SECTOR:
+ code = IOCTL_SCSI_MINIPORT_WRITE_SMART_LOG; name = "WRITE_SMART_LOG"; break;
+ }
+ if (!code) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ // Set SRB
+ struct {
+ SRB_IO_CONTROL srbc;
+ union {
+ SENDCMDINPARAMS in;
+ SENDCMDOUTPARAMS out;
+ } params;
+ char space[512-1];
+ } sb;
+ STATIC_ASSERT(sizeof(sb) == sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDINPARAMS)-1+512);
+ memset(&sb, 0, sizeof(sb));
+
+ unsigned size;
+ if (datasize > 0) {
+ if (datasize > (int)sizeof(sb.space)+1) {
+ errno = EINVAL;
+ return -1;
+ }
+ size = datasize;
+ }
+ else if (datasize < 0) {
+ if (-datasize > (int)sizeof(sb.space)+1) {
+ errno = EINVAL;
+ return -1;
+ }
+ size = -datasize;
+ memcpy(sb.params.in.bBuffer, data, size);
+ }
+ else if (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS)
+ size = sizeof(IDEREGS);
+ else
+ size = 0;
+ sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(sb.srbc.Signature, "SCSIDISK", 8); // atapi.sys
+ sb.srbc.Timeout = 60; // seconds
+ sb.srbc.ControlCode = code;
+ //sb.srbc.ReturnCode = 0;
+ sb.srbc.Length = sizeof(SENDCMDINPARAMS)-1 + size;
+ sb.params.in.irDriveRegs = *regs;
+ sb.params.in.cBufferSize = size;
+
+ // Call miniport ioctl
+ size += sizeof(SRB_IO_CONTROL) + sizeof(SENDCMDINPARAMS)-1;
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
+ &sb, size, &sb, size, &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT_%s failed, Error=%ld\n", name, err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (err == ERROR_INVALID_FUNCTION || err == ERROR_NOT_SUPPORTED ? ENOSYS : EIO);
+ return -1;
+ }
+
+ // Check result
+ if (sb.srbc.ReturnCode) {
+ if (ata_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT_%s failed, ReturnCode=0x%08x\n", name, (unsigned)sb.srbc.ReturnCode);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = EIO;
+ return -1;
+ }
+
+ if (sb.params.out.DriverStatus.bDriverError) {
+ if (ata_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT_%s failed, DriverError=0x%02x, IDEError=0x%02x\n", name,
+ sb.params.out.DriverStatus.bDriverError, sb.params.out.DriverStatus.bIDEError);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (!sb.params.out.DriverStatus.bIDEError ? ENOSYS : EIO);
+ return -1;
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_SCSI_MINIPORT_%s succeeded, bytes returned: %u (buffer %u)\n", name,
+ (unsigned)num_out, (unsigned)sb.params.out.cBufferSize);
+ print_ide_regs_io(regs, (code == IOCTL_SCSI_MINIPORT_RETURN_STATUS ?
+ (const IDEREGS *)(sb.params.out.bBuffer) : 0));
+ }
+
+ if (datasize > 0)
+ memcpy(data, sb.params.out.bBuffer, datasize);
+ else if (datasize == 0 && code == IOCTL_SCSI_MINIPORT_RETURN_STATUS)
+ memcpy(regs, sb.params.out.bBuffer, sizeof(IDEREGS));
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// ATA PASS THROUGH via 3ware specific SCSI MINIPORT ioctl
+
+static int ata_via_3ware_miniport_ioctl(HANDLE hdevice, IDEREGS * regs, char * data, int datasize, int port)
+{
+ struct {
+ SRB_IO_CONTROL srbc;
+ IDEREGS regs;
+ UCHAR buffer[512];
+ } sb;
+ STATIC_ASSERT(sizeof(sb) == sizeof(SRB_IO_CONTROL)+sizeof(IDEREGS)+512);
+
+ if (!(0 <= datasize && datasize <= (int)sizeof(sb.buffer) && port >= 0)) {
+ errno = EINVAL;
+ return -1;
+ }
+ memset(&sb, 0, sizeof(sb));
+ strncpy((char *)sb.srbc.Signature, "<3ware>", sizeof(sb.srbc.Signature));
+ sb.srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
+ sb.srbc.Timeout = 60; // seconds
+ sb.srbc.ControlCode = 0xA0000000;
+ sb.srbc.ReturnCode = 0;
+ sb.srbc.Length = sizeof(IDEREGS) + (datasize > 0 ? datasize : 1);
+ sb.regs = *regs;
+ sb.regs.bReserved = port;
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode) {
+ pout(" ATA via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ return -1;
+ }
+
+ if (sb.srbc.ReturnCode) {
+ if (ata_debugmode) {
+ pout(" ATA via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08x\n", (unsigned)sb.srbc.ReturnCode);
+ print_ide_regs_io(regs, NULL);
+ }
+ errno = EIO;
+ return -1;
+ }
+
+ // Copy data
+ if (datasize > 0)
+ memcpy(data, sb.buffer, datasize);
+
+ if (ata_debugmode > 1) {
+ pout(" ATA via IOCTL_SCSI_MINIPORT succeeded, bytes returned: %u\n", (unsigned)num_out);
+ print_ide_regs_io(regs, &sb.regs);
+ }
+ *regs = sb.regs;
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// 3ware specific call to update the devicemap returned by SMART_GET_VERSION.
+// 3DM/CLI "Rescan Controller" function does not to always update it.
+
+static int update_3ware_devicemap_ioctl(HANDLE hdevice)
+{
+ SRB_IO_CONTROL srbc;
+ memset(&srbc, 0, sizeof(srbc));
+ strncpy((char *)srbc.Signature, "<3ware>", sizeof(srbc.Signature));
+ srbc.HeaderLength = sizeof(SRB_IO_CONTROL);
+ srbc.Timeout = 60; // seconds
+ srbc.ControlCode = 0xCC010014;
+ srbc.ReturnCode = 0;
+ srbc.Length = 0;
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_SCSI_MINIPORT,
+ &srbc, sizeof(srbc), &srbc, sizeof(srbc), &num_out, NULL)) {
+ long err = GetLastError();
+ if (ata_debugmode)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, Error=%ld\n", err);
+ errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ return -1;
+ }
+ if (srbc.ReturnCode) {
+ if (ata_debugmode)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT failed, ReturnCode=0x%08x\n", (unsigned)srbc.ReturnCode);
+ errno = EIO;
+ return -1;
+ }
+ if (ata_debugmode > 1)
+ pout(" UPDATE DEVICEMAP via IOCTL_SCSI_MINIPORT succeeded\n");
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+union STORAGE_DEVICE_DESCRIPTOR_DATA {
+ STORAGE_DEVICE_DESCRIPTOR desc;
+ char raw[256];
+};
+
+// Get STORAGE_DEVICE_DESCRIPTOR_DATA for device.
+// (This works without admin rights)
+
+static int storage_query_property_ioctl(HANDLE hdevice, STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, PropertyStandardQuery, {0} };
+ memset(data, 0, sizeof(*data));
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), data, sizeof(*data), &num_out, NULL)) {
+ if (ata_debugmode > 1 || scsi_debugmode > 1)
+ pout(" IOCTL_STORAGE_QUERY_PROPERTY failed, Error=%u\n", (unsigned)GetLastError());
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (ata_debugmode > 1 || scsi_debugmode > 1) {
+ pout(" IOCTL_STORAGE_QUERY_PROPERTY returns:\n"
+ " Vendor: \"%s\"\n"
+ " Product: \"%s\"\n"
+ " Revision: \"%s\"\n"
+ " Removable: %s\n"
+ " BusType: 0x%02x\n",
+ (data->desc.VendorIdOffset ? data->raw+data->desc.VendorIdOffset : "(null)"),
+ (data->desc.ProductIdOffset ? data->raw+data->desc.ProductIdOffset : "(null)"),
+ (data->desc.ProductRevisionOffset ? data->raw+data->desc.ProductRevisionOffset : "(null)"),
+ (data->desc.RemovableMedia? "Yes":"No"), data->desc.BusType
+ );
+ }
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// IOCTL_STORAGE_PREDICT_FAILURE
+
+// Call IOCTL_STORAGE_PREDICT_FAILURE, return PredictFailure value
+// or -1 on error, optionally return VendorSpecific data.
+// (This works without admin rights)
+
+static int storage_predict_failure_ioctl(HANDLE hdevice, char * data = 0)
+{
+ STORAGE_PREDICT_FAILURE pred;
+ memset(&pred, 0, sizeof(pred));
+
+ DWORD num_out;
+ if (!DeviceIoControl(hdevice, IOCTL_STORAGE_PREDICT_FAILURE,
+ 0, 0, &pred, sizeof(pred), &num_out, NULL)) {
+ if (ata_debugmode > 1)
+ pout(" IOCTL_STORAGE_PREDICT_FAILURE failed, Error=%u\n", (unsigned)GetLastError());
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (ata_debugmode > 1) {
+ pout(" IOCTL_STORAGE_PREDICT_FAILURE returns:\n"
+ " PredictFailure: 0x%08x\n"
+ " VendorSpecific: 0x%02x,0x%02x,0x%02x,...,0x%02x\n",
+ (unsigned)pred.PredictFailure,
+ pred.VendorSpecific[0], pred.VendorSpecific[1], pred.VendorSpecific[2],
+ pred.VendorSpecific[sizeof(pred.VendorSpecific)-1]
+ );
+ }
+ if (data)
+ memcpy(data, pred.VendorSpecific, sizeof(pred.VendorSpecific));
+ return (!pred.PredictFailure ? 0 : 1);
+}
+
+
+// Build IDENTIFY information from STORAGE_DEVICE_DESCRIPTOR
+static int get_identify_from_device_property(HANDLE hdevice, ata_identify_device * id)
+{
+ STORAGE_DEVICE_DESCRIPTOR_DATA data;
+ if (storage_query_property_ioctl(hdevice, &data))
+ return -1;
+
+ memset(id, 0, sizeof(*id));
+
+ // Some drivers split ATA model string into VendorId and ProductId,
+ // others return it as ProductId only.
+ char model[sizeof(id->model) + 1] = "";
+
+ unsigned i = 0;
+ if (data.desc.VendorIdOffset) {
+ for ( ;i < sizeof(model)-1 && data.raw[data.desc.VendorIdOffset+i]; i++)
+ model[i] = data.raw[data.desc.VendorIdOffset+i];
+ }
+
+ if (data.desc.ProductIdOffset) {
+ // Keep only first trailing blank after VendorId
+ while (i > 0 && model[i-1] == ' ' && (i < 2 || model[i-2] == ' '))
+ i--;
+ // Ignore VendorId "ATA"
+ if (i <= 4 && !memcmp(model, "ATA", 3) && (i == 3 || model[3] == ' '))
+ i = 0;
+ for (unsigned j = 0; i < sizeof(model)-1 && data.raw[data.desc.ProductIdOffset+j]; i++, j++)
+ model[i] = data.raw[data.desc.ProductIdOffset+j];
+ }
+
+ while (i > 0 && model[i-1] == ' ')
+ i--;
+ model[i] = 0;
+ copy_swapped(id->model, model, sizeof(id->model));
+
+ if (data.desc.ProductRevisionOffset)
+ copy_swapped(id->fw_rev, data.raw+data.desc.ProductRevisionOffset, sizeof(id->fw_rev));
+
+ id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid
+ id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid
+ return 0;
+}
+
+// Get Serial Number in IDENTIFY from WMI
+static bool get_serial_from_wmi(int drive, ata_identify_device * id)
+{
+ bool debug = (ata_debugmode > 1);
+
+ wbem_services ws;
+ if (!ws.connect()) {
+ if (debug)
+ pout("WMI connect failed\n");
+ return false;
+ }
+
+ wbem_object wo;
+ if (!ws.query1(wo, "SELECT Model,SerialNumber FROM Win32_DiskDrive WHERE "
+ "DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", drive))
+ return false;
+
+ std::string serial = wo.get_str("SerialNumber");
+ if (debug)
+ pout(" WMI:PhysicalDrive%d: \"%s\", S/N:\"%s\"\n", drive, wo.get_str("Model").c_str(), serial.c_str());
+
+ copy_swapped(id->serial_no, serial.c_str(), sizeof(id->serial_no));
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// USB ID detection using WMI
+
+// Get USB ID for a physical or logical drive number
+static bool get_usb_id(int phydrive, int logdrive,
+ unsigned short & vendor_id,
+ unsigned short & product_id)
+{
+ bool debug = (scsi_debugmode > 1);
+
+ wbem_services ws;
+ if (!ws.connect()) {
+ if (debug)
+ pout("WMI connect failed\n");
+ return false;
+ }
+
+ // Get device name
+ std::string name;
+
+ wbem_object wo;
+ if (0 <= logdrive && logdrive <= 'Z'-'A') {
+ // Drive letter -> Partition info
+ if (!ws.query1(wo, "ASSOCIATORS OF {Win32_LogicalDisk.DeviceID=\"%c:\"} WHERE ResultClass = Win32_DiskPartition",
+ 'A'+logdrive))
+ return false;
+
+ std::string partid = wo.get_str("DeviceID");
+ if (debug)
+ pout("%c: --> \"%s\" -->\n", 'A'+logdrive, partid.c_str());
+
+ // Partition ID -> Physical drive info
+ if (!ws.query1(wo, "ASSOCIATORS OF {Win32_DiskPartition.DeviceID=\"%s\"} WHERE ResultClass = Win32_DiskDrive",
+ partid.c_str()))
+ return false;
+
+ name = wo.get_str("Model");
+ if (debug)
+ pout("%s --> \"%s\":\n", wo.get_str("DeviceID").c_str(), name.c_str());
+ }
+
+ else if (phydrive >= 0) {
+ // Physical drive number -> Physical drive info
+ if (!ws.query1(wo, "SELECT Model FROM Win32_DiskDrive WHERE DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE%d\"", phydrive))
+ return false;
+
+ name = wo.get_str("Model");
+ if (debug)
+ pout("\\.\\\\PHYSICALDRIVE%d --> \"%s\":\n", phydrive, name.c_str());
+ }
+ else
+ return false;
+
+
+ // Get USB_CONTROLLER -> DEVICE associations
+ wbem_enumerator we;
+ if (!ws.query(we, "SELECT Antecedent,Dependent FROM Win32_USBControllerDevice"))
+ return false;
+
+ unsigned short usb_venid = 0, prev_usb_venid = 0;
+ unsigned short usb_proid = 0, prev_usb_proid = 0;
+ std::string prev_usb_ant;
+ std::string prev_ant, ant, dep;
+
+ const regular_expression regex("^.*PnPEntity\\.DeviceID=\"([^\"]*)\"");
+
+ while (we.next(wo)) {
+ prev_ant = ant;
+ // Find next 'USB_CONTROLLER, DEVICE' pair
+ ant = wo.get_str("Antecedent");
+ dep = wo.get_str("Dependent");
+
+ if (debug && ant != prev_ant)
+ pout(" %s:\n", ant.c_str());
+
+ // Extract DeviceID
+ regular_expression::match_range match[2];
+ if (!(regex.execute(dep.c_str(), 2, match) && match[1].rm_so >= 0)) {
+ if (debug)
+ pout(" | (\"%s\")\n", dep.c_str());
+ continue;
+ }
+
+ std::string devid(dep.c_str()+match[1].rm_so, match[1].rm_eo-match[1].rm_so);
+
+ if (str_starts_with(devid, "USB\\\\VID_")) {
+ // USB bridge entry, save CONTROLLER, ID
+ int nc = -1;
+ if (!(sscanf(devid.c_str(), "USB\\\\VID_%4hx&PID_%4hx%n",
+ &prev_usb_venid, &prev_usb_proid, &nc) == 2 && nc == 9+4+5+4)) {
+ prev_usb_venid = prev_usb_proid = 0;
+ }
+ prev_usb_ant = ant;
+ if (debug)
+ pout(" +-> \"%s\" [0x%04x:0x%04x]\n", devid.c_str(), prev_usb_venid, prev_usb_proid);
+ }
+ else if (str_starts_with(devid, "USBSTOR\\\\") || str_starts_with(devid, "SCSI\\\\")) {
+ // USBSTORage or SCSI device found
+ if (debug)
+ pout(" +--> \"%s\"\n", devid.c_str());
+
+ // Retrieve name
+ wbem_object wo2;
+ if (!ws.query1(wo2, "SELECT Name FROM Win32_PnPEntity WHERE DeviceID=\"%s\"", devid.c_str()))
+ continue;
+ std::string name2 = wo2.get_str("Name");
+
+ // Continue if not name of physical disk drive
+ if (name2 != name) {
+ if (debug)
+ pout(" +---> (\"%s\")\n", name2.c_str());
+ continue;
+ }
+
+ // Fail if previous USB bridge is associated to other controller or ID is unknown
+ if (!(ant == prev_usb_ant && prev_usb_venid)) {
+ if (debug)
+ pout(" +---> \"%s\" (Error: No USB bridge found)\n", name2.c_str());
+ return false;
+ }
+
+ // Handle multiple devices with same name
+ if (usb_venid) {
+ // Fail if multiple devices with same name have different USB bridge types
+ if (!(usb_venid == prev_usb_venid && usb_proid == prev_usb_proid)) {
+ if (debug)
+ pout(" +---> \"%s\" (Error: More than one USB ID found)\n", name2.c_str());
+ return false;
+ }
+ }
+
+ // Found
+ usb_venid = prev_usb_venid;
+ usb_proid = prev_usb_proid;
+ if (debug)
+ pout(" +===> \"%s\" [0x%04x:0x%04x]\n", name2.c_str(), usb_venid, usb_proid);
+
+ // Continue to check for duplicate names ...
+ }
+ else {
+ if (debug)
+ pout(" | \"%s\"\n", devid.c_str());
+ }
+ }
+
+ if (!usb_venid)
+ return false;
+
+ vendor_id = usb_venid;
+ product_id = usb_proid;
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Call GetDevicePowerState()
+// returns: 1=active, 0=standby, -1=error
+// (This would also work for SCSI drives)
+
+static int get_device_power_state(HANDLE hdevice)
+{
+ BOOL state = TRUE;
+ if (!GetDevicePowerState(hdevice, &state)) {
+ long err = GetLastError();
+ if (ata_debugmode)
+ pout(" GetDevicePowerState() failed, Error=%ld\n", err);
+ errno = (err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO);
+ // TODO: This may not work as expected on transient errors,
+ // because smartd interprets -1 as SLEEP mode regardless of errno.
+ return -1;
+ }
+
+ if (ata_debugmode > 1)
+ pout(" GetDevicePowerState() succeeded, state=%d\n", state);
+ return state;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_ata_device
+
+class win_ata_device
+: public /*implements*/ ata_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type);
+
+ virtual ~win_ata_device();
+
+ virtual bool open() override;
+
+ virtual bool is_powered_down() override;
+
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;
+
+ virtual bool ata_identify_is_cached() const override;
+
+private:
+ bool open(bool query_device);
+
+ bool open(int phydrive, int logdrive, const char * options, int port, bool query_device);
+
+ std::string m_options;
+ bool m_usr_options; // options set by user?
+ bool m_admin; // open with admin access?
+ int m_phydrive; // PhysicalDriveN or -1
+ bool m_id_is_cached; // ata_identify_is_cached() return value.
+ bool m_is_3ware; // LSI/3ware controller detected?
+ int m_port; // LSI/3ware port
+ int m_smartver_state;
+};
+
+
+win_ata_device::win_ata_device(smart_interface * intf, const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "ata", req_type),
+ m_usr_options(false),
+ m_admin(false),
+ m_phydrive(-1),
+ m_id_is_cached(false),
+ m_is_3ware(false),
+ m_port(-1),
+ m_smartver_state(0)
+{
+}
+
+win_ata_device::~win_ata_device()
+{
+}
+
+// Get default ATA device options
+
+static const char * ata_get_def_options()
+{
+ return "pasifm"; // GetDevicePowerState(), ATA_, SMART_*, IDE_PASS_THROUGH,
+ // STORAGE_*, SCSI_MINIPORT_*
+}
+
+// Open ATA device
+
+bool win_ata_device::open()
+{
+ // Open device for r/w operations
+ return open(false);
+}
+
+bool win_ata_device::open(bool query_device)
+{
+ const char * name = skipdev(get_dev_name()); int len = strlen(name);
+ // [sh]d[a-z]([a-z])?(:[saicmfp]+)? => Physical drive 0-701, with options
+ char drive[2+1] = "", options[8+1] = ""; int n1 = -1, n2 = -1;
+ if ( sscanf(name, "%*[sh]d%2[a-z]%n:%6[saimfp]%n", drive, &n1, options, &n2) >= 1
+ && ((n1 == len && !options[0]) || n2 == len) ) {
+ return open(sdxy_to_phydrive(drive), -1, options, -1, query_device);
+ }
+ // [sh]d[a-z],N(:[saicmfp3]+)? => Physical drive 0-701, RAID port N, with options
+ drive[0] = 0; options[0] = 0; n1 = -1; n2 = -1;
+ unsigned port = ~0;
+ if ( sscanf(name, "%*[sh]d%2[a-z],%u%n:%7[saimfp3]%n", drive, &port, &n1, options, &n2) >= 2
+ && port < 32 && ((n1 == len && !options[0]) || n2 == len) ) {
+ return open(sdxy_to_phydrive(drive), -1, options, port, query_device);
+ }
+ // pd<m>,N => Physical drive <m>, RAID port N
+ int phydrive = -1; port = ~0; n1 = -1; n2 = -1;
+ if ( sscanf(name, "pd%d%n,%u%n", &phydrive, &n1, &port, &n2) >= 1
+ && phydrive >= 0 && ((n1 == len && (int)port < 0) || (n2 == len && port < 32))) {
+ return open(phydrive, -1, "", (int)port, query_device);
+ }
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(name);
+ if (logdrive >= 0) {
+ return open(-1, logdrive, "", -1, query_device);
+ }
+
+ return set_err(EINVAL);
+}
+
+
+bool win_ata_device::open(int phydrive, int logdrive, const char * options, int port, bool query_device)
+{
+ m_phydrive = -1;
+ char devpath[30];
+ if (0 <= phydrive && phydrive <= 255)
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\PhysicalDrive%d", (m_phydrive = phydrive));
+ else if (0 <= logdrive && logdrive <= 'Z'-'A')
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\%c:", 'A'+logdrive);
+ else
+ return set_err(ENOENT);
+
+ // Open device
+ HANDLE h = INVALID_HANDLE_VALUE;
+ if (!(*options && !options[strspn(options, "fp")]) && !query_device) {
+ // Open with admin rights
+ m_admin = true;
+ h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, 0);
+ }
+ if (h == INVALID_HANDLE_VALUE) {
+ // Open without admin rights
+ m_admin = false;
+ h = CreateFileA(devpath, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, 0);
+ }
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND)
+ set_err(ENOENT, "%s: not found", devpath);
+ else if (err == ERROR_ACCESS_DENIED)
+ set_err(EACCES, "%s: access denied", devpath);
+ else
+ set_err(EIO, "%s: Error=%ld", devpath, err);
+ return false;
+ }
+ set_fh(h);
+
+ // Warn once if admin rights are missing
+ if (!m_admin && !query_device) {
+ static bool noadmin_warning = false;
+ if (!noadmin_warning) {
+ pout("Warning: Limited functionality due to missing admin rights\n");
+ noadmin_warning = true;
+ }
+ }
+
+ if (ata_debugmode > 1)
+ pout("%s: successfully opened%s\n", devpath, (!m_admin ? " (without admin rights)" :""));
+
+ m_usr_options = false;
+ if (*options) {
+ // Save user options
+ m_options = options; m_usr_options = true;
+ }
+ else if (port >= 0)
+ // RAID: SMART_* and SCSI_MINIPORT
+ m_options = "s3";
+ else {
+ // Set default options according to Windows version
+ static const char * def_options = ata_get_def_options();
+ m_options = def_options;
+ }
+
+ // SMART_GET_VERSION may spin up disk, so delay until first real SMART_* call
+ m_port = port;
+ if (port < 0)
+ return true;
+
+ // 3ware RAID: Get port map
+ GETVERSIONINPARAMS_EX vers_ex;
+ int devmap = smart_get_version(h, &vers_ex);
+
+ // 3ware RAID if vendor id present
+ m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE);
+
+ unsigned portmap = 0;
+ if (devmap >= 0) {
+ // 3ware RAID: check vendor id
+ if (!m_is_3ware) {
+ pout("SMART_GET_VERSION returns unknown Identifier = 0x%04x\n"
+ "This is no 3ware 9000 controller or driver has no SMART support.\n",
+ vers_ex.wIdentifier);
+ devmap = -1;
+ }
+ else
+ portmap = vers_ex.dwDeviceMapEx;
+ }
+ if (devmap < 0) {
+ pout("%s: ATA driver has no SMART support\n", devpath);
+ if (!is_permissive()) {
+ close();
+ return set_err(ENOSYS);
+ }
+ }
+ m_smartver_state = 1;
+
+ {
+ // 3ware RAID: update devicemap first
+ if (!update_3ware_devicemap_ioctl(h)) {
+ if ( smart_get_version(h, &vers_ex) >= 0
+ && vers_ex.wIdentifier == SMART_VENDOR_3WARE )
+ portmap = vers_ex.dwDeviceMapEx;
+ }
+ // Check port existence
+ if (!(portmap & (1U << port))) {
+ if (!is_permissive()) {
+ close();
+ return set_err(ENOENT, "%s: Port %d is empty or does not exist", devpath, port);
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Query OS if device is powered up or down.
+bool win_ata_device::is_powered_down()
+{
+ // To check power mode, we open device for query operations only.
+ // Opening for SMART r/w operations can already spin up the disk.
+ bool self_open = !is_open();
+ if (self_open)
+ if (!open(true))
+ return false;
+ int rc = get_device_power_state(get_fh());
+ if (self_open)
+ close();
+ return !rc;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Interface to ATA devices
+bool win_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+ // No multi-sector support for now, see above
+ // warning about IOCTL_ATA_PASS_THROUGH
+ if (!ata_cmd_is_supported(in,
+ ata_device::supports_data_out |
+ ata_device::supports_output_regs |
+ ata_device::supports_48bit)
+ )
+ return false;
+
+ // 3ware RAID: SMART DISABLE without port number disables SMART functions
+ if ( m_is_3ware && m_port < 0
+ && in.in_regs.command == ATA_SMART_CMD
+ && in.in_regs.features == ATA_SMART_DISABLE)
+ return set_err(ENOSYS, "SMART DISABLE requires 3ware port number");
+
+ // Determine ioctl functions valid for this ATA cmd
+ const char * valid_options = 0;
+
+ switch (in.in_regs.command) {
+ case ATA_IDENTIFY_DEVICE:
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE
+ // and SCSI_MINIPORT_* if requested by user
+ valid_options = (m_usr_options ? "saimf" : "saif");
+ break;
+
+ case ATA_CHECK_POWER_MODE:
+ // Try GetDevicePowerState() first, ATA/IDE_PASS_THROUGH may spin up disk
+ valid_options = "pai3";
+ break;
+
+ case ATA_SMART_CMD:
+ switch (in.in_regs.features) {
+ case ATA_SMART_READ_VALUES:
+ case ATA_SMART_READ_THRESHOLDS:
+ case ATA_SMART_AUTOSAVE:
+ case ATA_SMART_ENABLE:
+ case ATA_SMART_DISABLE:
+ case ATA_SMART_AUTO_OFFLINE:
+ // SMART_*, ATA_, IDE_, SCSI_PASS_THROUGH, STORAGE_PREDICT_FAILURE
+ // and SCSI_MINIPORT_* if requested by user
+ valid_options = (m_usr_options ? "saimf" : "saif");
+ break;
+
+ case ATA_SMART_IMMEDIATE_OFFLINE:
+ // SMART_SEND_DRIVE_COMMAND does not support ABORT_SELF_TEST
+ valid_options = (m_usr_options || in.in_regs.lba_low != 127/*ABORT*/ ?
+ "saim3" : "aim3");
+ break;
+
+ case ATA_SMART_READ_LOG_SECTOR:
+ // SMART_RCV_DRIVE_DATA does not support READ_LOG
+ // Try SCSI_MINIPORT also to skip buggy class driver
+ // SMART functions do not support multi sector I/O.
+ if (in.size == 512)
+ valid_options = (m_usr_options ? "saim3" : "aim3");
+ else
+ valid_options = "a";
+ break;
+
+ case ATA_SMART_WRITE_LOG_SECTOR:
+ // ATA_PASS_THROUGH, SCSI_MINIPORT, others don't support DATA_OUT
+ // but SCSI_MINIPORT_* only if requested by user and single sector.
+ valid_options = (in.size == 512 && m_usr_options ? "am" : "a");
+ break;
+
+ case ATA_SMART_STATUS:
+ valid_options = (m_usr_options ? "saimf" : "saif");
+ break;
+
+ default:
+ // Unknown SMART command, handle below
+ break;
+ }
+ break;
+
+ default:
+ // Other ATA command, handle below
+ break;
+ }
+
+ if (!valid_options) {
+ // No special ATA command found above, select a generic pass through ioctl.
+ if (!( in.direction == ata_cmd_in::no_data
+ || (in.direction == ata_cmd_in::data_in && in.size == 512))
+ || in.in_regs.is_48bit_cmd() )
+ // DATA_OUT, more than one sector, 48-bit command: ATA_PASS_THROUGH only
+ valid_options = "a";
+ else
+ // ATA/IDE_PASS_THROUGH
+ valid_options = "ai";
+ }
+
+ if (!m_admin) {
+ // Restrict to IOCTL_STORAGE_*
+ if (strchr(valid_options, 'f'))
+ valid_options = "f";
+ else if (strchr(valid_options, 'p'))
+ valid_options = "p";
+ else
+ return set_err(ENOSYS, "Function requires admin rights");
+ }
+
+ // Set IDEREGS
+ IDEREGS regs, prev_regs;
+ {
+ const ata_in_regs & lo = in.in_regs;
+ regs.bFeaturesReg = lo.features;
+ regs.bSectorCountReg = lo.sector_count;
+ regs.bSectorNumberReg = lo.lba_low;
+ regs.bCylLowReg = lo.lba_mid;
+ regs.bCylHighReg = lo.lba_high;
+ regs.bDriveHeadReg = lo.device;
+ regs.bCommandReg = lo.command;
+ regs.bReserved = 0;
+ }
+ if (in.in_regs.is_48bit_cmd()) {
+ const ata_in_regs & hi = in.in_regs.prev;
+ prev_regs.bFeaturesReg = hi.features;
+ prev_regs.bSectorCountReg = hi.sector_count;
+ prev_regs.bSectorNumberReg = hi.lba_low;
+ prev_regs.bCylLowReg = hi.lba_mid;
+ prev_regs.bCylHighReg = hi.lba_high;
+ prev_regs.bDriveHeadReg = hi.device;
+ prev_regs.bCommandReg = hi.command;
+ prev_regs.bReserved = 0;
+ }
+
+ // Set data direction
+ int datasize = 0;
+ char * data = 0;
+ switch (in.direction) {
+ case ata_cmd_in::no_data:
+ break;
+ case ata_cmd_in::data_in:
+ datasize = (int)in.size;
+ data = (char *)in.buffer;
+ break;
+ case ata_cmd_in::data_out:
+ datasize = -(int)in.size;
+ data = (char *)in.buffer;
+ break;
+ default:
+ return set_err(EINVAL, "win_ata_device::ata_pass_through: invalid direction=%d",
+ (int)in.direction);
+ }
+
+
+ // Try all valid ioctls in the order specified in m_options
+ bool powered_up = false;
+ bool out_regs_set = false;
+ bool id_is_cached = false;
+ const char * options = m_options.c_str();
+
+ for (int i = 0; ; i++) {
+ char opt = options[i];
+
+ if (!opt) {
+ if (in.in_regs.command == ATA_CHECK_POWER_MODE && powered_up) {
+ // Power up reported by GetDevicePowerState() and no ioctl available
+ // to detect the actual mode of the drive => simulate ATA result ACTIVE/IDLE.
+ regs.bSectorCountReg = 0xff;
+ out_regs_set = true;
+ break;
+ }
+ // No IOCTL found
+ return set_err(ENOSYS);
+ }
+ if (!strchr(valid_options, opt))
+ // Invalid for this command
+ continue;
+
+ errno = 0;
+ assert( datasize == 0 || datasize == 512
+ || (datasize == -512 && strchr("am", opt))
+ || (datasize > 512 && opt == 'a'));
+ int rc;
+ switch (opt) {
+ default: assert(0);
+ case 's':
+ // call SMART_GET_VERSION once for each drive
+ if (m_smartver_state > 1) {
+ rc = -1; errno = ENOSYS;
+ break;
+ }
+ if (!m_smartver_state) {
+ assert(m_port == -1);
+ GETVERSIONINPARAMS_EX vers_ex;
+ if (smart_get_version(get_fh(), &vers_ex) < 0) {
+ if (!failuretest_permissive) {
+ m_smartver_state = 2;
+ rc = -1; errno = ENOSYS;
+ break;
+ }
+ failuretest_permissive--;
+ }
+ else {
+ // 3ware RAID if vendor id present
+ m_is_3ware = (vers_ex.wIdentifier == SMART_VENDOR_3WARE);
+ }
+
+ m_smartver_state = 1;
+ }
+ rc = smart_ioctl(get_fh(), &regs, data, datasize, m_port);
+ out_regs_set = (in.in_regs.features == ATA_SMART_STATUS);
+ id_is_cached = (m_port < 0); // Not cached by 3ware driver
+ break;
+ case 'm':
+ rc = ata_via_scsi_miniport_smart_ioctl(get_fh(), &regs, data, datasize);
+ id_is_cached = (m_port < 0);
+ break;
+ case 'a':
+ rc = ata_pass_through_ioctl(get_fh(), &regs,
+ (in.in_regs.is_48bit_cmd() ? &prev_regs : 0),
+ data, datasize);
+ out_regs_set = true;
+ break;
+ case 'i':
+ rc = ide_pass_through_ioctl(get_fh(), &regs, data, datasize);
+ out_regs_set = true;
+ break;
+ case 'f':
+ if (in.in_regs.command == ATA_IDENTIFY_DEVICE) {
+ ata_identify_device * id = reinterpret_cast<ata_identify_device *>(data);
+ rc = get_identify_from_device_property(get_fh(), id);
+ if (rc == 0 && m_phydrive >= 0)
+ get_serial_from_wmi(m_phydrive, id);
+ id_is_cached = true;
+ }
+ else if (in.in_regs.command == ATA_SMART_CMD) switch (in.in_regs.features) {
+ case ATA_SMART_READ_VALUES:
+ rc = storage_predict_failure_ioctl(get_fh(), data);
+ if (rc > 0)
+ rc = 0;
+ break;
+ case ATA_SMART_ENABLE:
+ rc = 0;
+ break;
+ case ATA_SMART_STATUS:
+ rc = storage_predict_failure_ioctl(get_fh());
+ if (rc == 0) {
+ // Good SMART status
+ out.out_regs.lba_high = 0xc2; out.out_regs.lba_mid = 0x4f;
+ }
+ else if (rc > 0) {
+ // Bad SMART status
+ out.out_regs.lba_high = 0x2c; out.out_regs.lba_mid = 0xf4;
+ rc = 0;
+ }
+ break;
+ default:
+ errno = ENOSYS; rc = -1;
+ }
+ else {
+ errno = ENOSYS; rc = -1;
+ }
+ break;
+ case '3':
+ rc = ata_via_3ware_miniport_ioctl(get_fh(), &regs, data, datasize, m_port);
+ out_regs_set = true;
+ break;
+ case 'p':
+ assert(in.in_regs.command == ATA_CHECK_POWER_MODE && in.size == 0);
+ rc = get_device_power_state(get_fh());
+ if (rc == 0) {
+ // Power down reported by GetDevicePowerState(), using a passthrough ioctl would
+ // spin up the drive => simulate ATA result STANDBY.
+ regs.bSectorCountReg = 0x00;
+ out_regs_set = true;
+ }
+ else if (rc > 0) {
+ // Power up reported by GetDevicePowerState(), but this reflects the actual mode
+ // only if it is selected by the device driver => try a passthrough ioctl to get the
+ // actual mode, if none available simulate ACTIVE/IDLE.
+ powered_up = true;
+ rc = -1; errno = ENOSYS;
+ }
+ break;
+ }
+
+ if (!rc)
+ // Working ioctl found
+ break;
+
+ if (errno != ENOSYS)
+ // Abort on I/O error
+ return set_err(errno);
+
+ out_regs_set = false;
+ // CAUTION: *_ioctl() MUST NOT change "regs" Parameter in the ENOSYS case
+ }
+
+ // Return IDEREGS if set
+ if (out_regs_set) {
+ ata_out_regs & lo = out.out_regs;
+ lo.error = regs.bFeaturesReg;
+ lo.sector_count = regs.bSectorCountReg;
+ lo.lba_low = regs.bSectorNumberReg;
+ lo.lba_mid = regs.bCylLowReg;
+ lo.lba_high = regs.bCylHighReg;
+ lo.device = regs.bDriveHeadReg;
+ lo.status = regs.bCommandReg;
+ if (in.in_regs.is_48bit_cmd()) {
+ ata_out_regs & hi = out.out_regs.prev;
+ hi.sector_count = prev_regs.bSectorCountReg;
+ hi.lba_low = prev_regs.bSectorNumberReg;
+ hi.lba_mid = prev_regs.bCylLowReg;
+ hi.lba_high = prev_regs.bCylHighReg;
+ }
+ }
+
+ if ( in.in_regs.command == ATA_IDENTIFY_DEVICE
+ || in.in_regs.command == ATA_IDENTIFY_PACKET_DEVICE)
+ // Update ata_identify_is_cached() result according to ioctl used.
+ m_id_is_cached = id_is_cached;
+
+ return true;
+}
+
+// Return true if OS caches the ATA identify sector
+bool win_ata_device::ata_identify_is_cached() const
+{
+ return m_id_is_cached;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// csmi_device
+
+class csmi_device
+: virtual public /*extends*/ smart_device
+{
+public:
+ enum { max_number_of_ports = 32 };
+
+ /// Get bitmask of used ports
+ unsigned get_ports_used();
+
+protected:
+ csmi_device()
+ : smart_device(never_called)
+ { memset(&m_phy_ent, 0, sizeof(m_phy_ent)); }
+
+ typedef signed char port_2_index_map[max_number_of_ports];
+
+ /// Get phy info and port mapping, return #ports or -1 on error
+ int get_phy_info(CSMI_SAS_PHY_INFO & phy_info, port_2_index_map & p2i);
+
+ /// Select physical drive
+ bool select_port(int port);
+
+ /// Get info for selected physical drive
+ const CSMI_SAS_PHY_ENTITY & get_phy_ent() const
+ { return m_phy_ent; }
+
+ /// Call platform-specific CSMI ioctl
+ virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
+ unsigned csmi_bufsiz) = 0;
+
+private:
+ CSMI_SAS_PHY_ENTITY m_phy_ent; ///< CSMI info for this phy
+
+ static bool guess_amd_drives(CSMI_SAS_PHY_INFO & phy_info, unsigned max_phy_drives);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+bool csmi_device::guess_amd_drives(CSMI_SAS_PHY_INFO & phy_info, unsigned max_phy_drives)
+{
+ if (max_phy_drives > max_number_of_ports)
+ return false;
+ if (max_phy_drives <= phy_info.bNumberOfPhys)
+ return false;
+ if (nonempty(phy_info.Phy + phy_info.bNumberOfPhys,
+ (max_number_of_ports - phy_info.bNumberOfPhys) * sizeof(phy_info.Phy[0])))
+ return false; // Phy[phy_info.bNumberOfPhys...] nonempty
+
+ // Get range of used ports, abort on unexpected values
+ int min_pi = max_number_of_ports, max_pi = 0, i;
+ for (i = 0; i < phy_info.bNumberOfPhys; i++) {
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ if (pe.Identify.bPhyIdentifier != i)
+ return false;
+ if (pe.bPortIdentifier >= max_phy_drives)
+ return false;
+ if (nonempty(&pe.Attached.bSASAddress, sizeof(pe.Attached.bSASAddress)))
+ return false;
+ if (min_pi > pe.bPortIdentifier)
+ min_pi = pe.bPortIdentifier;
+ if (max_pi < pe.bPortIdentifier)
+ max_pi = pe.bPortIdentifier;
+ }
+
+ // Append possibly used ports
+ for (int pi = 0; i < (int)max_phy_drives; i++, pi++) {
+ if (min_pi <= pi && pi <= max_pi)
+ pi = max_pi + 1;
+ if (pi >= (int)max_phy_drives)
+ break;
+ CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ pe.Identify.bDeviceType = pe.Attached.bDeviceType = CSMI_SAS_END_DEVICE;
+ pe.Attached.bTargetPortProtocol = CSMI_SAS_PROTOCOL_SATA;
+ pe.Identify.bPhyIdentifier = i;
+ pe.bPortIdentifier = pi;
+ }
+
+ return true;
+}
+
+int csmi_device::get_phy_info(CSMI_SAS_PHY_INFO & phy_info, port_2_index_map & p2i)
+{
+ // max_number_of_ports must match CSMI_SAS_PHY_INFO.Phy[] array size
+ STATIC_ASSERT(sizeof(phy_info.Phy) == max_number_of_ports * sizeof(phy_info.Phy[0]));
+
+ // Get driver info to check CSMI support
+ CSMI_SAS_DRIVER_INFO_BUFFER driver_info_buf;
+ memset(&driver_info_buf, 0, sizeof(driver_info_buf));
+ if (!csmi_ioctl(CC_CSMI_SAS_GET_DRIVER_INFO, &driver_info_buf.IoctlHeader, sizeof(driver_info_buf)))
+ return -1;
+
+ const CSMI_SAS_DRIVER_INFO & driver_info = driver_info_buf.Information;
+
+ if (scsi_debugmode > 1) {
+ pout("CSMI_SAS_DRIVER_INFO:\n");
+ pout(" Name: \"%.81s\"\n", driver_info.szName);
+ pout(" Description: \"%.81s\"\n", driver_info.szDescription);
+ pout(" Revision: %d.%d\n", driver_info.usMajorRevision, driver_info.usMinorRevision);
+ }
+
+ // Get Phy info
+ CSMI_SAS_PHY_INFO_BUFFER phy_info_buf;
+ memset(&phy_info_buf, 0, sizeof(phy_info_buf));
+ if (!csmi_ioctl(CC_CSMI_SAS_GET_PHY_INFO, &phy_info_buf.IoctlHeader, sizeof(phy_info_buf)))
+ return -1;
+
+ phy_info = phy_info_buf.Information;
+
+ if (phy_info.bNumberOfPhys > max_number_of_ports) {
+ set_err(EIO, "CSMI_SAS_PHY_INFO: Bogus NumberOfPhys=%d", phy_info.bNumberOfPhys);
+ return -1;
+ }
+
+ // Get RAID info
+ CSMI_SAS_RAID_INFO_BUFFER raid_info_buf;
+ memset(&raid_info_buf, 0, sizeof(raid_info_buf));
+ if (!csmi_ioctl(CC_CSMI_SAS_GET_RAID_INFO, &raid_info_buf.IoctlHeader, sizeof(raid_info_buf))) {
+ memset(&raid_info_buf, 0, sizeof(raid_info_buf)); // Ignore error
+ }
+
+ const CSMI_SAS_RAID_INFO & raid_info = raid_info_buf.Information;
+
+ if (scsi_debugmode > 1 && nonempty(&raid_info_buf, sizeof(raid_info_buf))) {
+ pout("CSMI_SAS_RAID_INFO:\n");
+ pout(" NumRaidSets: %u\n", (unsigned)raid_info.uNumRaidSets);
+ pout(" MaxDrvPerSet: %u\n", (unsigned)raid_info.uMaxDrivesPerSet);
+ pout(" MaxRaidSets: %u\n", (unsigned)raid_info.uMaxRaidSets);
+ pout(" MaxRaidTypes: %d\n", raid_info.bMaxRaidTypes);
+ pout(" MaxPhyDrives: %u\n", (unsigned)raid_info.uMaxPhysicalDrives);
+ }
+
+ // Create port -> index map
+ // Intel RST AMD rcraid
+ // Phy[i].Value 9/10.x 14.8 15.2 16.0/17.7 9.2
+ // ---------------------------------------------------------------------
+ // bPortIdentifier 0xff port 0x00 port (port)
+ // Identify.bPhyIdentifier index? index index port index
+ // Attached.bPhyIdentifier 0x00 0x00 index 0x00 0x00
+ //
+ // AMD: Phy[] may be incomplete (single drives not counted) and port
+ // numbers may be invalid (single drives skipped).
+ // IRST: Empty ports with hotplug support may appear in Phy[].
+
+ int first_guessed_index = max_number_of_ports;
+ if (!memcmp(driver_info.szName, "rcraid", 6+1)) {
+ // Workaround for AMD driver
+ if (guess_amd_drives(phy_info, raid_info.uMaxPhysicalDrives))
+ first_guessed_index = phy_info.bNumberOfPhys;
+ }
+
+ int number_of_ports;
+ for (int mode = 0; ; mode++) {
+ for (int i = 0; i < max_number_of_ports; i++)
+ p2i[i] = -1;
+
+ number_of_ports = 0;
+ bool found = false;
+ for (int i = 0; i < max_number_of_ports; i++) {
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ if (pe.Identify.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ continue;
+
+ // Try to detect which field contains the actual port number.
+ // Use a bPhyIdentifier or the bPortIdentifier if unique
+ // and not always identical to table index, otherwise use index.
+ int port;
+ switch (mode) {
+ case 0: port = pe.Attached.bPhyIdentifier; break;
+ case 1: port = pe.Identify.bPhyIdentifier; break;
+ case 2: port = pe.bPortIdentifier; break;
+ default: port = i; break;
+ }
+ if (!(port < max_number_of_ports && p2i[port] == -1)) {
+ found = false;
+ break;
+ }
+
+ p2i[port] = i;
+ if (number_of_ports <= port)
+ number_of_ports = port + 1;
+ if (port != i)
+ found = true;
+ }
+
+ if (found || mode > 2)
+ break;
+ }
+
+ if (scsi_debugmode > 1) {
+ pout("CSMI_SAS_PHY_INFO: NumberOfPhys=%d\n", phy_info.bNumberOfPhys);
+ for (int i = 0; i < max_number_of_ports; i++) {
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ if (!nonempty(&pe, sizeof(pe)))
+ continue;
+ const CSMI_SAS_IDENTIFY & id = pe.Identify, & at = pe.Attached;
+
+ int port = -1;
+ for (int p = 0; p < max_number_of_ports && port < 0; p++) {
+ if (p2i[p] == i)
+ port = p;
+ }
+
+ pout("Phy[%d] Port: %2d%s\n", i, port, (i >= first_guessed_index ? " (*guessed*)" : ""));
+ pout(" Type: 0x%02x, 0x%02x\n", id.bDeviceType, at.bDeviceType);
+ pout(" InitProto: 0x%02x, 0x%02x\n", id.bInitiatorPortProtocol, at.bInitiatorPortProtocol);
+ pout(" TargetProto: 0x%02x, 0x%02x\n", id.bTargetPortProtocol, at.bTargetPortProtocol);
+ pout(" PortIdent: 0x%02x\n", pe.bPortIdentifier);
+ pout(" PhyIdent: 0x%02x, 0x%02x\n", id.bPhyIdentifier, at.bPhyIdentifier);
+ pout(" SignalClass: 0x%02x, 0x%02x\n", id.bSignalClass, at.bSignalClass);
+ pout(" Restricted: 0x%02x, 0x%02x\n", id.bRestricted, at.bRestricted);
+ const unsigned char * b = id.bSASAddress;
+ pout(" SASAddress: %02x %02x %02x %02x %02x %02x %02x %02x, ",
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
+ b = at.bSASAddress;
+ pout( "%02x %02x %02x %02x %02x %02x %02x %02x\n",
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
+ }
+ }
+
+ return number_of_ports;
+}
+
+unsigned csmi_device::get_ports_used()
+{
+ CSMI_SAS_PHY_INFO phy_info;
+ port_2_index_map p2i;
+ int number_of_ports = get_phy_info(phy_info, p2i);
+ if (number_of_ports < 0)
+ return 0;
+
+ unsigned ports_used = 0;
+ for (int p = 0; p < max_number_of_ports; p++) {
+ int i = p2i[p];
+ if (i < 0)
+ continue;
+ const CSMI_SAS_PHY_ENTITY & pe = phy_info.Phy[i];
+ if (pe.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ continue;
+ switch (pe.Attached.bTargetPortProtocol) {
+ case CSMI_SAS_PROTOCOL_SATA:
+ case CSMI_SAS_PROTOCOL_STP:
+ break;
+ default:
+ continue;
+ }
+
+ ports_used |= (1U << p);
+ }
+
+ return ports_used;
+}
+
+bool csmi_device::select_port(int port)
+{
+ if (!(0 <= port && port < max_number_of_ports))
+ return set_err(EINVAL, "Invalid port number %d", port);
+
+ CSMI_SAS_PHY_INFO phy_info;
+ port_2_index_map p2i;
+ int number_of_ports = get_phy_info(phy_info, p2i);
+ if (number_of_ports < 0)
+ return false;
+
+ int port_index = p2i[port];
+ if (port_index < 0) {
+ if (port < number_of_ports)
+ return set_err(ENOENT, "Port %d is disabled", port);
+ else
+ return set_err(ENOENT, "Port %d does not exist (#ports: %d)", port,
+ number_of_ports);
+ }
+
+ const CSMI_SAS_PHY_ENTITY & phy_ent = phy_info.Phy[port_index];
+ if (phy_ent.Attached.bDeviceType == CSMI_SAS_NO_DEVICE_ATTACHED)
+ return set_err(ENOENT, "No device on port %d", port);
+
+ switch (phy_ent.Attached.bTargetPortProtocol) {
+ case CSMI_SAS_PROTOCOL_SATA:
+ case CSMI_SAS_PROTOCOL_STP:
+ break;
+ default:
+ return set_err(ENOENT, "No SATA device on port %d (protocol: %d)",
+ port, phy_ent.Attached.bTargetPortProtocol);
+ }
+
+ m_phy_ent = phy_ent;
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// csmi_ata_device
+
+class csmi_ata_device
+: virtual public /*extends*/ csmi_device,
+ virtual public /*implements*/ ata_device
+{
+public:
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;
+
+protected:
+ csmi_ata_device()
+ : smart_device(never_called) { }
+};
+
+
+//////////////////////////////////////////////////////////////////////
+
+bool csmi_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+ if (!ata_cmd_is_supported(in,
+ ata_device::supports_data_out |
+ ata_device::supports_output_regs |
+ ata_device::supports_multi_sector |
+ ata_device::supports_48bit,
+ "CSMI")
+ )
+ return false;
+
+ // Create buffer with appropriate size
+ raw_buffer pthru_raw_buf(sizeof(CSMI_SAS_STP_PASSTHRU_BUFFER) + in.size);
+ CSMI_SAS_STP_PASSTHRU_BUFFER * pthru_buf = (CSMI_SAS_STP_PASSTHRU_BUFFER *)pthru_raw_buf.data();
+
+ // Set addresses from Phy info
+ CSMI_SAS_STP_PASSTHRU & pthru = pthru_buf->Parameters;
+ const CSMI_SAS_PHY_ENTITY & phy_ent = get_phy_ent();
+ pthru.bPhyIdentifier = phy_ent.Identify.bPhyIdentifier; // Used by AMD, ignored by IRST
+ pthru.bPortIdentifier = phy_ent.bPortIdentifier; // Ignored
+ memcpy(pthru.bDestinationSASAddress, phy_ent.Attached.bSASAddress,
+ sizeof(pthru.bDestinationSASAddress)); // Used by IRST (at index 1), ignored by AMD
+ pthru.bConnectionRate = CSMI_SAS_LINK_RATE_NEGOTIATED;
+
+ // Set transfer mode
+ switch (in.direction) {
+ case ata_cmd_in::no_data:
+ pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_UNSPECIFIED;
+ break;
+ case ata_cmd_in::data_in:
+ pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_READ;
+ pthru.uDataLength = in.size;
+ break;
+ case ata_cmd_in::data_out:
+ pthru.uFlags = CSMI_SAS_STP_PIO | CSMI_SAS_STP_WRITE;
+ pthru.uDataLength = in.size;
+ memcpy(pthru_buf->bDataBuffer, in.buffer, in.size);
+ break;
+ default:
+ return set_err(EINVAL, "csmi_ata_device::ata_pass_through: invalid direction=%d",
+ (int)in.direction);
+ }
+
+ // Set host-to-device FIS
+ {
+ unsigned char * fis = pthru.bCommandFIS;
+ const ata_in_regs & lo = in.in_regs;
+ const ata_in_regs & hi = in.in_regs.prev;
+ fis[ 0] = 0x27; // Type: host-to-device FIS
+ fis[ 1] = 0x80; // Bit7: Update command register
+ fis[ 2] = lo.command;
+ fis[ 3] = lo.features;
+ fis[ 4] = lo.lba_low;
+ fis[ 5] = lo.lba_mid;
+ fis[ 6] = lo.lba_high;
+ fis[ 7] = lo.device;
+ fis[ 8] = hi.lba_low;
+ fis[ 9] = hi.lba_mid;
+ fis[10] = hi.lba_high;
+ fis[11] = hi.features;
+ fis[12] = lo.sector_count;
+ fis[13] = hi.sector_count;
+ }
+
+ // Call ioctl
+ if (!csmi_ioctl(CC_CSMI_SAS_STP_PASSTHRU, &pthru_buf->IoctlHeader, pthru_raw_buf.size())) {
+ return false;
+ }
+
+ // Get device-to-host FIS
+ // Assume values are unavailable if all register fields are zero (AMD RAID driver)
+ if (nonempty(pthru_buf->Status.bStatusFIS + 2, 13 - 2 + 1)) {
+ const unsigned char * fis = pthru_buf->Status.bStatusFIS;
+ ata_out_regs & lo = out.out_regs;
+ lo.status = fis[ 2];
+ lo.error = fis[ 3];
+ lo.lba_low = fis[ 4];
+ lo.lba_mid = fis[ 5];
+ lo.lba_high = fis[ 6];
+ lo.device = fis[ 7];
+ lo.sector_count = fis[12];
+ if (in.in_regs.is_48bit_cmd()) {
+ ata_out_regs & hi = out.out_regs.prev;
+ hi.lba_low = fis[ 8];
+ hi.lba_mid = fis[ 9];
+ hi.lba_high = fis[10];
+ hi.sector_count = fis[13];
+ }
+ }
+
+ // Get data
+ if (in.direction == ata_cmd_in::data_in)
+ // TODO: Check ptru_buf->Status.uDataBytes
+ memcpy(in.buffer, pthru_buf->bDataBuffer, in.size);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// win_csmi_device
+
+class win_csmi_device
+: public /*implements*/ csmi_ata_device
+{
+public:
+ win_csmi_device(smart_interface * intf, const char * dev_name,
+ const char * req_type);
+
+ virtual ~win_csmi_device();
+
+ virtual bool open() override;
+
+ virtual bool close() override;
+
+ virtual bool is_open() const override;
+
+ bool open_scsi();
+
+protected:
+ virtual bool csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
+ unsigned csmi_bufsiz) override;
+
+private:
+ HANDLE m_fh; ///< Controller device handle
+ int m_port; ///< Port number
+};
+
+
+//////////////////////////////////////////////////////////////////////
+
+win_csmi_device::win_csmi_device(smart_interface * intf, const char * dev_name,
+ const char * req_type)
+: smart_device(intf, dev_name, "ata", req_type),
+ m_fh(INVALID_HANDLE_VALUE), m_port(-1)
+{
+}
+
+win_csmi_device::~win_csmi_device()
+{
+ if (m_fh != INVALID_HANDLE_VALUE)
+ CloseHandle(m_fh);
+}
+
+bool win_csmi_device::is_open() const
+{
+ return (m_fh != INVALID_HANDLE_VALUE);
+}
+
+bool win_csmi_device::close()
+{
+ if (m_fh == INVALID_HANDLE_VALUE)
+ return true;
+ BOOL rc = CloseHandle(m_fh);
+ m_fh = INVALID_HANDLE_VALUE;
+ return !!rc;
+}
+
+
+bool win_csmi_device::open_scsi()
+{
+ // Parse name
+ unsigned contr_no = ~0, port = ~0; int nc = -1;
+ const char * name = skipdev(get_dev_name());
+ if (!( sscanf(name, "csmi%u,%u%n", &contr_no, &port, &nc) >= 0
+ && nc == (int)strlen(name) && contr_no <= 9 && port < 32) )
+ return set_err(EINVAL);
+
+ // Open controller handle
+ char devpath[30];
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%u:", contr_no);
+
+ HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0);
+
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND)
+ set_err(ENOENT, "%s: not found", devpath);
+ else if (err == ERROR_ACCESS_DENIED)
+ set_err(EACCES, "%s: access denied", devpath);
+ else
+ set_err(EIO, "%s: Error=%ld", devpath, err);
+ return false;
+ }
+
+ if (scsi_debugmode > 1)
+ pout(" %s: successfully opened\n", devpath);
+
+ m_fh = h;
+ m_port = port;
+ return true;
+}
+
+
+bool win_csmi_device::open()
+{
+ if (!open_scsi())
+ return false;
+
+ // Get Phy info for this drive
+ if (!select_port(m_port)) {
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+
+bool win_csmi_device::csmi_ioctl(unsigned code, IOCTL_HEADER * csmi_buffer,
+ unsigned csmi_bufsiz)
+{
+ // Determine signature
+ const char * sig;
+ switch (code) {
+ case CC_CSMI_SAS_GET_DRIVER_INFO:
+ sig = CSMI_ALL_SIGNATURE; break;
+ case CC_CSMI_SAS_GET_RAID_INFO:
+ sig = CSMI_RAID_SIGNATURE; break;
+ case CC_CSMI_SAS_GET_PHY_INFO:
+ case CC_CSMI_SAS_STP_PASSTHRU:
+ sig = CSMI_SAS_SIGNATURE; break;
+ default:
+ return set_err(ENOSYS, "Unknown CSMI code=%u", code);
+ }
+
+ // Set header
+ csmi_buffer->HeaderLength = sizeof(IOCTL_HEADER);
+ strncpy((char *)csmi_buffer->Signature, sig, sizeof(csmi_buffer->Signature));
+ csmi_buffer->Timeout = CSMI_SAS_TIMEOUT;
+ csmi_buffer->ControlCode = code;
+ csmi_buffer->ReturnCode = 0;
+ csmi_buffer->Length = csmi_bufsiz - sizeof(IOCTL_HEADER);
+
+ // Call function
+ DWORD num_out = 0;
+ if (!DeviceIoControl(m_fh, IOCTL_SCSI_MINIPORT,
+ csmi_buffer, csmi_bufsiz, csmi_buffer, csmi_bufsiz, &num_out, (OVERLAPPED*)0)) {
+ long err = GetLastError();
+ if (scsi_debugmode)
+ pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, Error=%ld\n", code, err);
+ if ( err == ERROR_INVALID_FUNCTION
+ || err == ERROR_NOT_SUPPORTED
+ || err == ERROR_DEV_NOT_EXIST)
+ return set_err(ENOSYS, "CSMI is not supported (Error=%ld)", err);
+ else
+ return set_err(EIO, "CSMI(%u) failed with Error=%ld", code, err);
+ }
+
+ // Check result
+ if (csmi_buffer->ReturnCode) {
+ if (scsi_debugmode) {
+ pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) failed, ReturnCode=%u\n",
+ code, (unsigned)csmi_buffer->ReturnCode);
+ }
+ return set_err(EIO, "CSMI(%u) failed with ReturnCode=%u", code, (unsigned)csmi_buffer->ReturnCode);
+ }
+
+ if (scsi_debugmode > 1)
+ pout(" IOCTL_SCSI_MINIPORT(CC_CSMI_%u) succeeded, bytes returned: %u\n", code, (unsigned)num_out);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// win_tw_cli_device
+
+// Routines for pseudo device /dev/tw_cli/*
+// Parses output of 3ware "tw_cli /cx/py show all" or 3DM SMART data window
+// TODO: This is OS independent
+
+class win_tw_cli_device
+: public /*implements*/ ata_device_with_command_set
+{
+public:
+ win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type);
+
+ virtual bool is_open() const override;
+
+ virtual bool open() override;
+
+ virtual bool close() override;
+
+protected:
+ virtual int ata_command_interface(smart_command_set command, int select, char * data);
+
+private:
+ bool m_ident_valid, m_smart_valid;
+ ata_identify_device m_ident_buf;
+ ata_smart_values m_smart_buf;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_tw_cli_device::win_tw_cli_device(smart_interface * intf, const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "tw_cli", req_type),
+ m_ident_valid(false), m_smart_valid(false)
+{
+ memset(&m_ident_buf, 0, sizeof(m_ident_buf));
+ memset(&m_smart_buf, 0, sizeof(m_smart_buf));
+}
+
+
+bool win_tw_cli_device::is_open() const
+{
+ return (m_ident_valid || m_smart_valid);
+}
+
+
+// Get clipboard data
+
+static int get_clipboard(char * data, int datasize)
+{
+ if (!OpenClipboard(NULL))
+ return -1;
+ HANDLE h = GetClipboardData(CF_TEXT);
+ if (!h) {
+ CloseClipboard();
+ return 0;
+ }
+ const void * p = GlobalLock(h);
+ int n = GlobalSize(h);
+ if (n > datasize)
+ n = datasize;
+ memcpy(data, p, n);
+ GlobalFree(h);
+ CloseClipboard();
+ return n;
+}
+
+
+static const char * findstr(const char * str, const char * sub)
+{
+ const char * s = strstr(str, sub);
+ return (s ? s+strlen(sub) : "");
+}
+
+
+bool win_tw_cli_device::open()
+{
+ m_ident_valid = m_smart_valid = false;
+ const char * name = skipdev(get_dev_name());
+ // Read tw_cli or 3DM browser output into buffer
+ char buffer[4096];
+ int size = -1, n1 = -1, n2 = -1;
+ if (!strcmp(name, "tw_cli/clip")) { // read clipboard
+ size = get_clipboard(buffer, sizeof(buffer));
+ }
+ else if (!strcmp(name, "tw_cli/stdin")) { // read stdin
+ size = fread(buffer, 1, sizeof(buffer), stdin);
+ }
+ else if (sscanf(name, "tw_cli/%nc%*u/p%*u%n", &n1, &n2) >= 0 && n2 == (int)strlen(name)) {
+ // tw_cli/cx/py => read output from "tw_cli /cx/py show all"
+ char cmd[100];
+ snprintf(cmd, sizeof(cmd), "tw_cli /%s show all", name+n1);
+ if (ata_debugmode > 1)
+ pout("%s: Run: \"%s\"\n", name, cmd);
+ FILE * f = popen(cmd, "rb");
+ if (f) {
+ size = fread(buffer, 1, sizeof(buffer), f);
+ pclose(f);
+ }
+ }
+ else {
+ return set_err(EINVAL);
+ }
+
+ if (ata_debugmode > 1)
+ pout("%s: Read %d bytes\n", name, size);
+ if (size <= 0)
+ return set_err(ENOENT);
+ if (size >= (int)sizeof(buffer))
+ return set_err(EIO);
+
+ buffer[size] = 0;
+ if (ata_debugmode > 1)
+ pout("[\n%.100s%s\n]\n", buffer, (size>100?"...":""));
+
+ // Fake identify sector
+ STATIC_ASSERT(sizeof(ata_identify_device) == 512);
+ ata_identify_device * id = &m_ident_buf;
+ memset(id, 0, sizeof(*id));
+ copy_swapped(id->model , findstr(buffer, " Model = " ), sizeof(id->model));
+ copy_swapped(id->fw_rev , findstr(buffer, " Firmware Version = "), sizeof(id->fw_rev));
+ copy_swapped(id->serial_no, findstr(buffer, " Serial = " ), sizeof(id->serial_no));
+ unsigned long nblocks = 0; // "Capacity = N.N GB (N Blocks)"
+ sscanf(findstr(buffer, "Capacity = "), "%*[^(\r\n](%lu", &nblocks);
+ if (nblocks) {
+ id->words047_079[49-47] = 0x0200; // size valid
+ id->words047_079[60-47] = (unsigned short)(nblocks ); // secs_16
+ id->words047_079[61-47] = (unsigned short)(nblocks>>16); // secs_32
+ }
+ id->command_set_1 = 0x0001; id->command_set_2 = 0x4000; // SMART supported, words 82,83 valid
+ id->cfs_enable_1 = 0x0001; id->csf_default = 0x4000; // SMART enabled, words 85,87 valid
+
+ // Parse smart data hex dump
+ const char * s = findstr(buffer, "Drive Smart Data:");
+ if (!*s)
+ s = findstr(buffer, "Drive SMART Data:"); // tw_cli from 9.5.x
+ if (!*s) {
+ s = findstr(buffer, "S.M.A.R.T. (Controller"); // from 3DM browser window
+ if (*s) {
+ const char * s1 = findstr(s, "<td class"); // html version
+ if (*s1)
+ s = s1;
+ s += strcspn(s, "\r\n");
+ }
+ else
+ s = buffer; // try raw hex dump without header
+ }
+ unsigned char * sd = (unsigned char *)&m_smart_buf;
+ int i = 0;
+ for (;;) {
+ unsigned x = ~0; int n = -1;
+ if (!(sscanf(s, "%x %n", &x, &n) == 1 && !(x & ~0xff)))
+ break;
+ sd[i] = (unsigned char)x;
+ if (!(++i < 512 && n > 0))
+ break;
+ s += n;
+ if (*s == '<') // "<br>"
+ s += strcspn(s, "\r\n");
+ }
+ if (i < 512) {
+ if (!id->model[1]) {
+ // No useful data found
+ char * err = strstr(buffer, "Error:");
+ if (!err)
+ err = strstr(buffer, "error :");
+ if (err && (err = strchr(err, ':'))) {
+ // Show tw_cli error message
+ err++;
+ err[strcspn(err, "\r\n")] = 0;
+ return set_err(EIO, "%s", err);
+ }
+ return set_err(EIO);
+ }
+ sd = 0;
+ }
+
+ m_ident_valid = true;
+ m_smart_valid = !!sd;
+ return true;
+}
+
+
+bool win_tw_cli_device::close()
+{
+ m_ident_valid = m_smart_valid = false;
+ return true;
+}
+
+
+int win_tw_cli_device::ata_command_interface(smart_command_set command, int /*select*/, char * data)
+{
+ switch (command) {
+ case IDENTIFY:
+ if (!m_ident_valid)
+ break;
+ memcpy(data, &m_ident_buf, 512);
+ return 0;
+ case READ_VALUES:
+ if (!m_smart_valid)
+ break;
+ memcpy(data, &m_smart_buf, 512);
+ return 0;
+ case ENABLE:
+ case STATUS:
+ case STATUS_CHECK: // Fake "good" SMART status
+ return 0;
+ default:
+ break;
+ }
+ // Arrive here for all unsupported commands
+ set_err(ENOSYS);
+ return -1;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_scsi_device
+// SPT Interface (for SCSI devices and ATA devices behind SATLs)
+
+class win_scsi_device
+: public /*implements*/ scsi_device,
+ virtual public /*extends*/ win_smart_device
+{
+public:
+ win_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type);
+
+ virtual bool open() override;
+
+ virtual bool scsi_pass_through(scsi_cmnd_io * iop) override;
+
+private:
+ bool open(int pd_num, int ld_num, int tape_num, int sub_addr);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_scsi_device::win_scsi_device(smart_interface * intf,
+ const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "scsi", req_type)
+{
+}
+
+bool win_scsi_device::open()
+{
+ const char * name = skipdev(get_dev_name()); int len = strlen(name);
+ // sd[a-z]([a-z])?,N => Physical drive 0-701, RAID port N
+ char drive[2+1] = ""; int sub_addr = -1; int n1 = -1; int n2 = -1;
+ if ( sscanf(name, "sd%2[a-z]%n,%d%n", drive, &n1, &sub_addr, &n2) >= 1
+ && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0)) ) {
+ return open(sdxy_to_phydrive(drive), -1, -1, sub_addr);
+ }
+ // pd<m>,N => Physical drive <m>, RAID port N
+ int pd_num = -1; sub_addr = -1; n1 = -1; n2 = -1;
+ if ( sscanf(name, "pd%d%n,%d%n", &pd_num, &n1, &sub_addr, &n2) >= 1
+ && pd_num >= 0 && ((n1 == len && sub_addr == -1) || (n2 == len && sub_addr >= 0))) {
+ return open(pd_num, -1, -1, sub_addr);
+ }
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(name);
+ if (logdrive >= 0) {
+ return open(-1, logdrive, -1, -1);
+ }
+ // n?st<m> => tape drive <m> (same names used in Cygwin's /dev emulation)
+ int tape_num = -1; n1 = -1;
+ if (sscanf(name, "st%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return open(-1, -1, tape_num, -1);
+ }
+ tape_num = -1; n1 = -1;
+ if (sscanf(name, "nst%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return open(-1, -1, tape_num, -1);
+ }
+ // tape<m> => tape drive <m>
+ tape_num = -1; n1 = -1;
+ if (sscanf(name, "tape%d%n", &tape_num, &n1) == 1 && tape_num >= 0 && n1 == len) {
+ return open(-1, -1, tape_num, -1);
+ }
+
+ return set_err(EINVAL);
+}
+
+bool win_scsi_device::open(int pd_num, int ld_num, int tape_num, int /*sub_addr*/)
+{
+ char b[128];
+ b[sizeof(b) - 1] = '\0';
+ if (pd_num >= 0)
+ snprintf(b, sizeof(b) - 1, "\\\\.\\PhysicalDrive%d", pd_num);
+ else if (ld_num >= 0)
+ snprintf(b, sizeof(b) - 1, "\\\\.\\%c:", 'A' + ld_num);
+ else if (tape_num >= 0)
+ snprintf(b, sizeof(b) - 1, "\\\\.\\TAPE%d", tape_num);
+ else {
+ set_err(EINVAL);
+ return false;
+ }
+
+ // Open device
+ HANDLE h = CreateFileA(b, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, 0);
+ if (h == INVALID_HANDLE_VALUE) {
+ set_err(ENODEV, "%s: Open failed, Error=%u", b, (unsigned)GetLastError());
+ return false;
+ }
+ set_fh(h);
+ return true;
+}
+
+
+typedef struct {
+ SCSI_PASS_THROUGH_DIRECT spt;
+ ULONG Filler;
+ UCHAR ucSenseBuf[64];
+} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
+
+
+// Issue command via IOCTL_SCSI_PASS_THROUGH instead of *_DIRECT.
+// Used if DataTransferLength not supported by *_DIRECT.
+static long scsi_pass_through_indirect(HANDLE h,
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER * sbd)
+{
+ struct SCSI_PASS_THROUGH_WITH_BUFFERS {
+ SCSI_PASS_THROUGH spt;
+ ULONG Filler;
+ UCHAR ucSenseBuf[sizeof(sbd->ucSenseBuf)];
+ UCHAR ucDataBuf[512];
+ };
+
+ SCSI_PASS_THROUGH_WITH_BUFFERS sb;
+ memset(&sb, 0, sizeof(sb));
+
+ // DATA_OUT not implemented yet
+ if (!( sbd->spt.DataIn == SCSI_IOCTL_DATA_IN
+ && sbd->spt.DataTransferLength <= sizeof(sb.ucDataBuf)))
+ return ERROR_INVALID_PARAMETER;
+
+ sb.spt.Length = sizeof(sb.spt);
+ sb.spt.CdbLength = sbd->spt.CdbLength;
+ memcpy(sb.spt.Cdb, sbd->spt.Cdb, sizeof(sb.spt.Cdb));
+ sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
+ sb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ sb.spt.DataIn = sbd->spt.DataIn;
+ sb.spt.DataTransferLength = sbd->spt.DataTransferLength;
+ sb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
+ sb.spt.TimeOutValue = sbd->spt.TimeOutValue;
+
+ DWORD num_out;
+ if (!DeviceIoControl(h, IOCTL_SCSI_PASS_THROUGH,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
+ return GetLastError();
+
+ sbd->spt.ScsiStatus = sb.spt.ScsiStatus;
+ if (sb.spt.ScsiStatus & SCSI_STATUS_CHECK_CONDITION)
+ memcpy(sbd->ucSenseBuf, sb.ucSenseBuf, sizeof(sbd->ucSenseBuf));
+
+ sbd->spt.DataTransferLength = sb.spt.DataTransferLength;
+ if (sbd->spt.DataIn == SCSI_IOCTL_DATA_IN && sb.spt.DataTransferLength > 0)
+ memcpy(sbd->spt.DataBuffer, sb.ucDataBuf, sb.spt.DataTransferLength);
+ return 0;
+}
+
+
+// Interface to SPT SCSI devices. See scsicmds.h and os_linux.c
+bool win_scsi_device::scsi_pass_through(struct scsi_cmnd_io * iop)
+{
+ int report = scsi_debugmode; // TODO
+
+ if (report > 0) {
+ int k, j;
+ const unsigned char * ucp = iop->cmnd;
+ const char * np;
+ char buff[256];
+ const int sz = (int)sizeof(buff);
+
+ np = scsi_get_opcode_name(ucp);
+ j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
+ for (k = 0; k < (int)iop->cmnd_len; ++k)
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
+ if ((report > 1) &&
+ (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
+ "data, len=%d%s:\n", (int)iop->dxfer_len,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+ else
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+ pout("%s", buff);
+ }
+
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
+ if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
+ set_err(EINVAL, "cmnd_len too large");
+ return false;
+ }
+
+ memset(&sb, 0, sizeof(sb));
+ sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ sb.spt.CdbLength = iop->cmnd_len;
+ memcpy(sb.spt.Cdb, iop->cmnd, iop->cmnd_len);
+ sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
+ sb.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf);
+ sb.spt.TimeOutValue = (iop->timeout ? iop->timeout : 60);
+
+ bool direct = true;
+ switch (iop->dxfer_dir) {
+ case DXFER_NONE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ break;
+ case DXFER_FROM_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ // IOCTL_SCSI_PASS_THROUGH_DIRECT does not support single byte
+ // transfers (needed for SMART STATUS check of JMicron USB bridges)
+ if (sb.spt.DataTransferLength == 1)
+ direct = false;
+ break;
+ case DXFER_TO_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ break;
+ default:
+ set_err(EINVAL, "bad dxfer_dir");
+ return false;
+ }
+
+ long err = 0;
+ if (direct) {
+ DWORD num_out;
+ if (!DeviceIoControl(get_fh(), IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
+ err = GetLastError();
+ }
+ else
+ err = scsi_pass_through_indirect(get_fh(), &sb);
+
+ if (err)
+ return set_err((err == ERROR_INVALID_FUNCTION ? ENOSYS : EIO),
+ "IOCTL_SCSI_PASS_THROUGH%s failed, Error=%ld",
+ (direct ? "_DIRECT" : ""), err);
+
+ iop->scsi_status = sb.spt.ScsiStatus;
+ if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
+ int slen = sb.ucSenseBuf[7] + 8;
+
+ if (slen > (int)sizeof(sb.ucSenseBuf))
+ slen = sizeof(sb.ucSenseBuf);
+ if (slen > (int)iop->max_sense_len)
+ slen = iop->max_sense_len;
+ memcpy(iop->sensep, sb.ucSenseBuf, slen);
+ iop->resp_sense_len = slen;
+ if (report) {
+ if (report > 1) {
+ pout(" >>> Sense buffer, len=%d:\n", slen);
+ dStrHex(iop->sensep, slen , 1);
+ }
+ if ((iop->sensep[0] & 0x7f) > 0x71)
+ pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[1] & 0xf,
+ iop->sensep[2], iop->sensep[3]);
+ else
+ pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[2] & 0xf,
+ iop->sensep[12], iop->sensep[13]);
+ }
+ } else
+ iop->resp_sense_len = 0;
+
+ if (iop->dxfer_len > sb.spt.DataTransferLength)
+ iop->resid = iop->dxfer_len - sb.spt.DataTransferLength;
+ else
+ iop->resid = 0;
+
+ if ((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+ pout(" Incoming data, len=%d, resid=%d%s:\n", (int)iop->dxfer_len, iop->resid,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+/// Areca RAID support
+
+// TODO: combine with above scsi_pass_through_direct()
+static long scsi_pass_through_direct(HANDLE fd, UCHAR targetid, struct scsi_cmnd_io * iop)
+{
+ int report = scsi_debugmode; // TODO
+
+ if (report > 0) {
+ int k, j;
+ const unsigned char * ucp = iop->cmnd;
+ const char * np;
+ char buff[256];
+ const int sz = (int)sizeof(buff);
+
+ np = scsi_get_opcode_name(ucp);
+ j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
+ for (k = 0; k < (int)iop->cmnd_len; ++k)
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
+ if ((report > 1) &&
+ (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
+ "data, len=%d%s:\n", (int)iop->dxfer_len,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+ else
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+ pout("%s", buff);
+ }
+
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sb;
+ if (iop->cmnd_len > (int)sizeof(sb.spt.Cdb)) {
+ return EINVAL;
+ }
+
+ memset(&sb, 0, sizeof(sb));
+ sb.spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ //sb.spt.PathId = 0;
+ sb.spt.TargetId = targetid;
+ //sb.spt.Lun = 0;
+ sb.spt.CdbLength = iop->cmnd_len;
+ memcpy(sb.spt.Cdb, iop->cmnd, iop->cmnd_len);
+ sb.spt.SenseInfoLength = sizeof(sb.ucSenseBuf);
+ sb.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf);
+ sb.spt.TimeOutValue = (iop->timeout ? iop->timeout : 60);
+
+ bool direct = true;
+ switch (iop->dxfer_dir) {
+ case DXFER_NONE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ break;
+ case DXFER_FROM_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ // IOCTL_SCSI_PASS_THROUGH_DIRECT does not support single byte
+ // transfers (needed for SMART STATUS check of JMicron USB bridges)
+ if (sb.spt.DataTransferLength == 1)
+ direct = false;
+ break;
+ case DXFER_TO_DEVICE:
+ sb.spt.DataIn = SCSI_IOCTL_DATA_OUT;
+ sb.spt.DataTransferLength = iop->dxfer_len;
+ sb.spt.DataBuffer = iop->dxferp;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ long err = 0;
+ if (direct) {
+ DWORD num_out;
+ if (!DeviceIoControl(fd, IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &sb, sizeof(sb), &sb, sizeof(sb), &num_out, 0))
+ err = GetLastError();
+ }
+ else
+ err = scsi_pass_through_indirect(fd, &sb);
+
+ if (err)
+ {
+ return err;
+ }
+
+ iop->scsi_status = sb.spt.ScsiStatus;
+ if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
+ int slen = sb.ucSenseBuf[7] + 8;
+
+ if (slen > (int)sizeof(sb.ucSenseBuf))
+ slen = sizeof(sb.ucSenseBuf);
+ if (slen > (int)iop->max_sense_len)
+ slen = iop->max_sense_len;
+ memcpy(iop->sensep, sb.ucSenseBuf, slen);
+ iop->resp_sense_len = slen;
+ if (report) {
+ if (report > 1) {
+ pout(" >>> Sense buffer, len=%d:\n", slen);
+ dStrHex(iop->sensep, slen , 1);
+ }
+ if ((iop->sensep[0] & 0x7f) > 0x71)
+ pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[1] & 0xf,
+ iop->sensep[2], iop->sensep[3]);
+ else
+ pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[2] & 0xf,
+ iop->sensep[12], iop->sensep[13]);
+ }
+ } else
+ iop->resp_sense_len = 0;
+
+ if (iop->dxfer_len > sb.spt.DataTransferLength)
+ iop->resid = iop->dxfer_len - sb.spt.DataTransferLength;
+ else
+ iop->resid = 0;
+
+ if ((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+ pout(" Incoming data, len=%d, resid=%d%s:\n", (int)iop->dxfer_len, iop->resid,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
+ }
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_areca_scsi_device
+// SAS(SCSI) device behind Areca RAID Controller
+
+class win_areca_scsi_device
+: public /*implements*/ areca_scsi_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1);
+ virtual bool open() override;
+ virtual smart_device * autodetect_open() override;
+ virtual bool arcmsr_lock() override;
+ virtual bool arcmsr_unlock() override;
+ virtual int arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) override;
+
+private:
+ HANDLE m_mutex;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_areca_scsi_device::win_areca_scsi_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
+: smart_device(intf, dev_name, "areca", "areca")
+{
+ set_fh(INVALID_HANDLE_VALUE);
+ set_disknum(disknum);
+ set_encnum(encnum);
+ set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
+}
+
+bool win_areca_scsi_device::open()
+{
+ HANDLE hFh;
+
+ if( is_open() )
+ {
+ return true;
+ }
+ hFh = CreateFile( get_dev_name(),
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL );
+ if(hFh == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ set_fh(hFh);
+ return true;
+}
+
+smart_device * win_areca_scsi_device::autodetect_open()
+{
+ return this;
+}
+
+int win_areca_scsi_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop)
+{
+ int ioctlreturn = 0;
+
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 16, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 127, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ // errors found
+ return -1;
+ }
+ }
+
+ return ioctlreturn;
+}
+
+bool win_areca_scsi_device::arcmsr_lock()
+{
+#define SYNCOBJNAME "Global\\SynIoctlMutex"
+ int ctlrnum = -1;
+ char mutexstr[64];
+
+ if (sscanf(get_dev_name(), "\\\\.\\scsi%d:", &ctlrnum) < 1)
+ return set_err(EINVAL, "unable to parse device name");
+
+ snprintf(mutexstr, sizeof(mutexstr), "%s%d", SYNCOBJNAME, ctlrnum);
+ m_mutex = CreateMutex(NULL, FALSE, mutexstr);
+ if ( m_mutex == NULL )
+ {
+ return set_err(EIO, "CreateMutex failed");
+ }
+
+ // atomic access to driver
+ WaitForSingleObject(m_mutex, INFINITE);
+
+ return true;
+}
+
+
+bool win_areca_scsi_device::arcmsr_unlock()
+{
+ if( m_mutex != NULL)
+ {
+ ReleaseMutex(m_mutex);
+ CloseHandle(m_mutex);
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_areca_ata_device
+// SATA(ATA) device behind Areca RAID Controller
+
+class win_areca_ata_device
+: public /*implements*/ areca_ata_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum = 1);
+ virtual bool open() override;
+ virtual smart_device * autodetect_open() override;
+ virtual bool arcmsr_lock() override;
+ virtual bool arcmsr_unlock() override;
+ virtual int arcmsr_do_scsi_io(struct scsi_cmnd_io * iop) override;
+
+private:
+ HANDLE m_mutex;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_areca_ata_device::win_areca_ata_device(smart_interface * intf, const char * dev_name, int disknum, int encnum)
+: smart_device(intf, dev_name, "areca", "areca")
+{
+ set_fh(INVALID_HANDLE_VALUE);
+ set_disknum(disknum);
+ set_encnum(encnum);
+ set_info().info_name = strprintf("%s [areca_disk#%02d_enc#%02d]", dev_name, disknum, encnum);
+}
+
+bool win_areca_ata_device::open()
+{
+ HANDLE hFh;
+
+ if( is_open() )
+ {
+ return true;
+ }
+ hFh = CreateFile( get_dev_name(),
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL );
+ if(hFh == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ set_fh(hFh);
+ return true;
+}
+
+smart_device * win_areca_ata_device::autodetect_open()
+{
+ // autodetect device type
+ int is_ata = arcmsr_get_dev_type();
+ if(is_ata < 0)
+ {
+ set_err(EIO);
+ return this;
+ }
+
+ if(is_ata == 1)
+ {
+ // SATA device
+ return this;
+ }
+
+ // SAS device
+ smart_device_auto_ptr newdev(new win_areca_scsi_device(smi(), get_dev_name(), get_disknum(), get_encnum()));
+ close();
+ delete this;
+ newdev->open(); // TODO: Can possibly pass open fd
+
+ return newdev.release();
+}
+
+int win_areca_ata_device::arcmsr_do_scsi_io(struct scsi_cmnd_io * iop)
+{
+ int ioctlreturn = 0;
+
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 16, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ ioctlreturn = scsi_pass_through_direct(get_fh(), 127, iop);
+ if ( ioctlreturn || iop->scsi_status )
+ {
+ // errors found
+ return -1;
+ }
+ }
+
+ return ioctlreturn;
+}
+
+bool win_areca_ata_device::arcmsr_lock()
+{
+#define SYNCOBJNAME "Global\\SynIoctlMutex"
+ int ctlrnum = -1;
+ char mutexstr[64];
+
+ if (sscanf(get_dev_name(), "\\\\.\\scsi%d:", &ctlrnum) < 1)
+ return set_err(EINVAL, "unable to parse device name");
+
+ snprintf(mutexstr, sizeof(mutexstr), "%s%d", SYNCOBJNAME, ctlrnum);
+ m_mutex = CreateMutex(NULL, FALSE, mutexstr);
+ if ( m_mutex == NULL )
+ {
+ return set_err(EIO, "CreateMutex failed");
+ }
+
+ // atomic access to driver
+ WaitForSingleObject(m_mutex, INFINITE);
+
+ return true;
+}
+
+
+bool win_areca_ata_device::arcmsr_unlock()
+{
+ if( m_mutex != NULL)
+ {
+ ReleaseMutex(m_mutex);
+ CloseHandle(m_mutex);
+ }
+
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_aacraid_device
+// PMC aacraid Support
+
+class win_aacraid_device
+:public /*implements*/ scsi_device,
+public /*extends*/ win_smart_device
+{
+public:
+ win_aacraid_device(smart_interface *intf, const char *dev_name,unsigned int ctrnum, unsigned int target, unsigned int lun);
+
+ virtual ~win_aacraid_device();
+
+ virtual bool open() override;
+
+ virtual bool scsi_pass_through(struct scsi_cmnd_io *iop) override;
+
+private:
+ //Device Host number
+ int m_ctrnum;
+
+ //Channel(Lun) of the device
+ int m_lun;
+
+ //Id of the device
+ int m_target;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_aacraid_device::win_aacraid_device(smart_interface * intf,
+ const char *dev_name, unsigned ctrnum, unsigned target, unsigned lun)
+: smart_device(intf, dev_name, "aacraid", "aacraid"),
+ m_ctrnum(ctrnum), m_lun(lun), m_target(target)
+{
+ set_info().info_name = strprintf("%s [aacraid_disk_%02d_%02d_%d]", dev_name, m_ctrnum, m_lun, m_target);
+ set_info().dev_type = strprintf("aacraid,%d,%d,%d", m_ctrnum, m_lun, m_target);
+}
+
+win_aacraid_device::~win_aacraid_device()
+{
+}
+
+bool win_aacraid_device::open()
+{
+ if (is_open())
+ return true;
+
+ HANDLE hFh = CreateFile( get_dev_name(),
+ GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ 0);
+ if (hFh == INVALID_HANDLE_VALUE)
+ return set_err(ENODEV, "Open failed, Error=%u", (unsigned)GetLastError());
+
+ set_fh(hFh);
+ return true;
+}
+
+bool win_aacraid_device::scsi_pass_through(struct scsi_cmnd_io *iop)
+{
+ int report = scsi_debugmode;
+ if (report > 0)
+ {
+ int k, j;
+ const unsigned char * ucp = iop->cmnd;
+ const char * np;
+ char buff[256];
+ const int sz = (int)sizeof(buff);
+ np = scsi_get_opcode_name(ucp);
+ j = snprintf(buff, sz, " [%s: ", np ? np : "<unknown opcode>");
+ for (k = 0; k < (int)iop->cmnd_len; ++k)
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "%02x ", ucp[k]);
+ if ((report > 1) &&
+ (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n Outgoing "
+ "data, len=%d%s:\n", (int)iop->dxfer_len,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex(iop->dxferp, (trunc ? 256 : (int)iop->dxfer_len) , 1);
+ }
+ else
+ j += snprintf(&buff[j], (sz > j ? (sz - j) : 0), "]\n");
+ pout("buff %s\n",buff);
+ }
+
+ // Create buffer with appropriate size
+ constexpr unsigned scsiRequestBlockSize = sizeof(SCSI_REQUEST_BLOCK);
+ constexpr unsigned dataOffset = (sizeof(SRB_IO_CONTROL) + scsiRequestBlockSize + 7) & 0xfffffff8;
+ raw_buffer pthru_raw_buf(dataOffset + iop->dxfer_len + 8); // 32|64-bit: 96|120 + ...
+
+ char * ioBuffer = reinterpret_cast<char *>(pthru_raw_buf.data());
+ SRB_IO_CONTROL * pSrbIO = (SRB_IO_CONTROL *) ioBuffer;
+ SCSI_REQUEST_BLOCK * pScsiIO = (SCSI_REQUEST_BLOCK *) (ioBuffer + sizeof(SRB_IO_CONTROL));
+ char *pRequestSenseIO = (char *) (ioBuffer + sizeof(SRB_IO_CONTROL) + scsiRequestBlockSize);
+ char *pDataIO = (char *) (ioBuffer + dataOffset);
+ memset(pScsiIO, 0, scsiRequestBlockSize);
+ pScsiIO->Length = (USHORT) scsiRequestBlockSize;
+ pScsiIO->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ pScsiIO->PathId = 0;
+ pScsiIO->TargetId = m_target;
+ pScsiIO->Lun = m_lun;
+ pScsiIO->CdbLength = (int)iop->cmnd_len;
+ switch(iop->dxfer_dir){
+ case DXFER_NONE:
+ pScsiIO->SrbFlags = SRB_NoDataXfer;
+ break;
+ case DXFER_FROM_DEVICE:
+ pScsiIO->SrbFlags |= SRB_DataIn;
+ break;
+ case DXFER_TO_DEVICE:
+ pScsiIO->SrbFlags |= SRB_DataOut;
+ break;
+ default:
+ pout("aacraid: bad dxfer_dir\n");
+ return set_err(EINVAL, "aacraid: bad dxfer_dir\n");
+ }
+ pScsiIO->DataTransferLength = (ULONG)iop->dxfer_len;
+ pScsiIO->TimeOutValue = iop->timeout;
+ UCHAR *pCdb = (UCHAR *) pScsiIO->Cdb;
+ memcpy(pCdb, iop->cmnd, 16);
+ if (iop->max_sense_len){
+ memset(pRequestSenseIO, 0, iop->max_sense_len);
+ }
+ if (pScsiIO->SrbFlags & SRB_FLAGS_DATA_OUT){
+ memcpy(pDataIO, iop->dxferp, iop->dxfer_len);
+ }
+ else if (pScsiIO->SrbFlags & SRB_FLAGS_DATA_IN){
+ memset(pDataIO, 0, iop->dxfer_len);
+ }
+
+ DWORD bytesReturned = 0;
+ memset(pSrbIO, 0, sizeof(SRB_IO_CONTROL));
+ pSrbIO->HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(pSrbIO->Signature, "AACAPI", 7);
+ pSrbIO->ControlCode = ARCIOCTL_SEND_RAW_SRB;
+ pSrbIO->Length = (dataOffset + iop->dxfer_len - sizeof(SRB_IO_CONTROL) + 7) & 0xfffffff8;
+ pSrbIO->Timeout = 3*60;
+
+ if (!DeviceIoControl(
+ get_fh(),
+ IOCTL_SCSI_MINIPORT,
+ ioBuffer,
+ sizeof(SRB_IO_CONTROL) + pSrbIO->Length,
+ ioBuffer,
+ sizeof(SRB_IO_CONTROL) + pSrbIO->Length,
+ &bytesReturned,
+ NULL)
+ ) {
+ return set_err(EIO, "ARCIOCTL_SEND_RAW_SRB failed, Error=%u", (unsigned)GetLastError());
+ }
+
+ iop->scsi_status = pScsiIO->ScsiStatus;
+ if (SCSI_STATUS_CHECK_CONDITION & iop->scsi_status) {
+ int slen = sizeof(pRequestSenseIO) + 8;
+ if (slen > (int)sizeof(pRequestSenseIO))
+ slen = sizeof(pRequestSenseIO);
+ if (slen > (int)iop->max_sense_len)
+ slen = (int)iop->max_sense_len;
+ memcpy(iop->sensep, pRequestSenseIO, slen);
+ iop->resp_sense_len = slen;
+ if (report) {
+ if (report > 1) {
+ pout(" >>> Sense buffer, len=%d:\n", slen);
+ dStrHex(iop->sensep, slen , 1);
+ }
+ if ((iop->sensep[0] & 0x7f) > 0x71)
+ pout(" status=%x: [desc] sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[1] & 0xf,
+ iop->sensep[2], iop->sensep[3]);
+ else
+ pout(" status=%x: sense_key=%x asc=%x ascq=%x\n",
+ iop->scsi_status, iop->sensep[2] & 0xf,
+ iop->sensep[12], iop->sensep[13]);
+ }
+ }
+ else {
+ iop->resp_sense_len = 0;
+ }
+
+ if (iop->dxfer_dir == DXFER_FROM_DEVICE){
+ memcpy(iop->dxferp,pDataIO, iop->dxfer_len);
+ }
+ if((iop->dxfer_dir == DXFER_FROM_DEVICE) && (report > 1)){
+ int trunc = (iop->dxfer_len > 256) ? 1 : 0;
+ pout(" Incoming data, len=%d, resid=%d%s:\n", (int)iop->dxfer_len, iop->resid,
+ (trunc ? " [only first 256 bytes shown]" : ""));
+ dStrHex((const uint8_t *)pDataIO, (trunc ? 256 : (int)(iop->dxfer_len)) , 1);
+ }
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win_nvme_device
+
+class win_nvme_device
+: public /*implements*/ nvme_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid);
+
+ virtual bool open() override;
+
+ virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
+
+ bool open_scsi(int n);
+
+ bool probe();
+
+private:
+ int m_scsi_no;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win_nvme_device::win_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid)
+: smart_device(intf, dev_name, "nvme", req_type),
+ nvme_device(nsid),
+ m_scsi_no(-1)
+{
+}
+
+bool win_nvme_device::open_scsi(int n)
+{
+ // TODO: Use common open function for all devices using "\\.\ScsiN:"
+ char devpath[32];
+ snprintf(devpath, sizeof(devpath)-1, "\\\\.\\Scsi%d:", n);
+
+ HANDLE h = CreateFileA(devpath, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0);
+
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (nvme_debugmode > 1)
+ pout(" %s: Open failed, Error=%ld\n", devpath, err);
+ if (err == ERROR_FILE_NOT_FOUND)
+ set_err(ENOENT, "%s: not found", devpath);
+ else if (err == ERROR_ACCESS_DENIED)
+ set_err(EACCES, "%s: access denied", devpath);
+ else
+ set_err(EIO, "%s: Error=%ld", devpath, err);
+ return false;
+ }
+
+ if (nvme_debugmode > 1)
+ pout(" %s: successfully opened\n", devpath);
+
+ set_fh(h);
+ return true;
+}
+
+// Check if NVMe DeviceIoControl(IOCTL_SCSI_MINIPORT) pass-through works.
+// On Win10 and later that returns false with an errorNumber of 1
+// ("Incorrect function"). Win10 has new pass-through:
+// DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND). However for commonly
+// requested NVMe commands like Identify and Get Features Microsoft want
+// "Protocol specific queries" sent.
+bool win_nvme_device::probe()
+{
+ smartmontools::nvme_id_ctrl id_ctrl;
+ nvme_cmd_in in;
+ in.set_data_in(smartmontools::nvme_admin_identify, &id_ctrl, sizeof(id_ctrl));
+ // in.nsid = 0;
+ in.cdw10 = 0x1;
+ nvme_cmd_out out;
+
+ bool ok = nvme_pass_through(in, out);
+ if (!ok && nvme_debugmode > 1)
+ pout(" nvme probe failed: %s\n", get_errmsg());
+ return ok;
+}
+
+bool win_nvme_device::open()
+{
+ if (m_scsi_no < 0) {
+ // First open -> search of NVMe devices
+ const char * name = skipdev(get_dev_name());
+ char s[2+1] = ""; int n1 = -1, n2 = -1, len = strlen(name);
+ unsigned no = ~0, nsid = 0xffffffff;
+ sscanf(name, "nvm%2[es]%u%nn%u%n", s, &no, &n1, &nsid, &n2);
+
+ if (!( (n1 == len || (n2 == len && nsid > 0))
+ && s[0] == 'e' && (!s[1] || s[1] == 's') ))
+ return set_err(EINVAL);
+
+ if (!s[1]) {
+ // /dev/nvmeN* -> search for nth NVMe device
+ unsigned nvme_cnt = 0;
+ for (int i = 0; i < 32; i++) {
+ if (!open_scsi(i)) {
+ if (get_errno() == EACCES)
+ return false;
+ continue;
+ }
+ // Done if pass-through works and correct number
+ if (probe()) {
+ if (nvme_cnt == no) {
+ m_scsi_no = i;
+ break;
+ }
+ nvme_cnt++;
+ }
+ close();
+ }
+
+ if (!is_open())
+ return set_err(ENOENT);
+ clear_err();
+ }
+ else {
+ // /dev/nvmesN* -> use "\\.\ScsiN:"
+ if (!open_scsi(no))
+ return false;
+ m_scsi_no = no;
+ }
+
+ if (!get_nsid())
+ set_nsid(nsid);
+ }
+ else {
+ // Reopen same "\\.\ScsiN:"
+ if (!open_scsi(m_scsi_no))
+ return false;
+ }
+
+ return true;
+}
+
+bool win_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+ // Create buffer with appropriate size
+ raw_buffer pthru_raw_buf(offsetof(NVME_PASS_THROUGH_IOCTL, DataBuffer) + in.size);
+ NVME_PASS_THROUGH_IOCTL * pthru =
+ reinterpret_cast<NVME_PASS_THROUGH_IOCTL *>(pthru_raw_buf.data());
+
+ // Set NVMe command
+ pthru->SrbIoCtrl.HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(pthru->SrbIoCtrl.Signature, NVME_SIG_STR, sizeof(NVME_SIG_STR)-1);
+ pthru->SrbIoCtrl.Timeout = 60;
+ pthru->SrbIoCtrl.ControlCode = NVME_PASS_THROUGH_SRB_IO_CODE;
+ pthru->SrbIoCtrl.ReturnCode = 0;
+ pthru->SrbIoCtrl.Length = pthru_raw_buf.size() - sizeof(SRB_IO_CONTROL);
+
+ pthru->NVMeCmd[0] = in.opcode;
+ pthru->NVMeCmd[1] = in.nsid;
+ pthru->NVMeCmd[10] = in.cdw10;
+ pthru->NVMeCmd[11] = in.cdw11;
+ pthru->NVMeCmd[12] = in.cdw12;
+ pthru->NVMeCmd[13] = in.cdw13;
+ pthru->NVMeCmd[14] = in.cdw14;
+ pthru->NVMeCmd[15] = in.cdw15;
+
+ pthru->Direction = in.direction();
+ // pthru->QueueId = 0; // AdminQ
+ // pthru->DataBufferLen = 0;
+ if (in.direction() & nvme_cmd_in::data_out) {
+ pthru->DataBufferLen = in.size;
+ memcpy(pthru->DataBuffer, in.buffer, in.size);
+ }
+ // pthru->MetaDataLen = 0;
+ pthru->ReturnBufferLen = pthru_raw_buf.size();
+
+ // Call NVME_PASS_THROUGH
+ DWORD num_out = 0;
+ BOOL ok = DeviceIoControl(get_fh(), IOCTL_SCSI_MINIPORT,
+ pthru, pthru_raw_buf.size(), pthru, pthru_raw_buf.size(),
+ &num_out, (OVERLAPPED*)0);
+
+ // Check status
+ unsigned status = pthru->CplEntry[3] >> 17;
+ if (status)
+ return set_nvme_err(out, status);
+
+ if (!ok)
+ return set_err(EIO, "NVME_PASS_THROUGH failed, Error=%u", (unsigned)GetLastError());
+
+ if (in.direction() & nvme_cmd_in::data_in)
+ memcpy(in.buffer, pthru->DataBuffer, in.size);
+
+ out.result = pthru->CplEntry[0];
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// win10_nvme_device
+
+class win10_nvme_device
+: public /*implements*/ nvme_device,
+ public /*extends*/ win_smart_device
+{
+public:
+ win10_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid);
+
+ virtual bool open() override;
+
+ virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
+
+private:
+ bool open(int phydrive, int logdrive);
+
+ bool nvme_storage_query_property(const nvme_cmd_in & in, nvme_cmd_out & out);
+
+ bool nvme_storage_protocol_command(const nvme_cmd_in & in, nvme_cmd_out & out);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+win10_nvme_device::win10_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid)
+: smart_device(intf, dev_name, "nvme", req_type),
+ nvme_device(nsid)
+{
+}
+
+bool win10_nvme_device::open()
+{
+ // TODO: Use common /dev/ parsing functions
+ const char * name = skipdev(get_dev_name()); int len = strlen(name);
+ // sd[a-z]([a-z])? => Physical drive 0-701
+ char drive[2 + 1] = ""; int n = -1;
+ if (sscanf(name, "sd%2[a-z]%n", drive, &n) == 1 && n == len)
+ return open(sdxy_to_phydrive(drive), -1);
+
+ // pdN => Physical drive N
+ int phydrive = -1; n = -1;
+ if (sscanf(name, "pd%d%n", &phydrive, &n) == 1 && phydrive >= 0 && n == len)
+ return open(phydrive, -1);
+
+ // [a-zA-Z]: => Physical drive behind logical drive 0-25
+ int logdrive = drive_letter(name);
+ if (logdrive >= 0)
+ return open(-1, logdrive);
+
+ return set_err(EINVAL);
+}
+
+bool win10_nvme_device::open(int phydrive, int logdrive)
+{
+ // TODO: Use common open function for all devices using "\\.\PhysicalDriveN"
+ char devpath[64];
+ if (phydrive >= 0)
+ snprintf(devpath, sizeof(devpath), "\\\\.\\PhysicalDrive%d", phydrive);
+ else
+ snprintf(devpath, sizeof(devpath), "\\\\.\\%c:", 'A'+logdrive);
+
+ bool admin = true;
+ HANDLE h = CreateFileA(devpath, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, (HANDLE)0);
+ if (h == INVALID_HANDLE_VALUE) {
+ // STORAGE_QUERY_PROPERTY works without GENERIC_READ/WRITE access
+ admin = false;
+ h = CreateFileA(devpath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES*)0, OPEN_EXISTING, 0, (HANDLE)0);
+ }
+
+ if (h == INVALID_HANDLE_VALUE) {
+ long err = GetLastError();
+ if (nvme_debugmode > 1)
+ pout(" %s: Open failed, Error=%ld\n", devpath, err);
+ if (err == ERROR_FILE_NOT_FOUND)
+ set_err(ENOENT, "%s: not found", devpath);
+ else if (err == ERROR_ACCESS_DENIED)
+ set_err(EACCES, "%s: access denied", devpath);
+ else
+ set_err(EIO, "%s: Error=%ld", devpath, err);
+ return false;
+ }
+
+ if (nvme_debugmode > 1)
+ pout(" %s: successfully opened%s\n", devpath, (!admin ? " (without admin rights)" : ""));
+
+ set_fh(h);
+
+ // Use broadcast namespace if no NSID specified
+ // TODO: Get NSID of current device
+ if (!get_nsid())
+ set_nsid(0xffffffff);
+ return true;
+}
+
+struct STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER
+{
+ struct { // STORAGE_PROPERTY_QUERY without AdditionalsParameters[1]
+ STORAGE_PROPERTY_ID PropertyId;
+ STORAGE_QUERY_TYPE QueryType;
+ } PropertyQuery;
+ win10::STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecific;
+ BYTE DataBuffer[1];
+};
+
+bool win10_nvme_device::nvme_storage_query_property(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+ // Create buffer with appropriate size
+ raw_buffer spsq_raw_buf(offsetof(STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER, DataBuffer) + in.size);
+ STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER * spsq =
+ reinterpret_cast<STORAGE_PROTOCOL_SPECIFIC_QUERY_WITH_BUFFER *>(spsq_raw_buf.data());
+
+ // Set NVMe specific STORAGE_PROPERTY_QUERY
+ spsq->PropertyQuery.QueryType = PropertyStandardQuery;
+ spsq->ProtocolSpecific.ProtocolType = win10::ProtocolTypeNvme;
+
+ switch (in.opcode) {
+ case smartmontools::nvme_admin_identify:
+ if (!in.nsid) // Identify controller
+ spsq->PropertyQuery.PropertyId = win10::StorageAdapterProtocolSpecificProperty;
+ else
+ spsq->PropertyQuery.PropertyId = win10::StorageDeviceProtocolSpecificProperty;
+ spsq->ProtocolSpecific.DataType = win10::NVMeDataTypeIdentify;
+ spsq->ProtocolSpecific.ProtocolDataRequestValue = in.cdw10;
+ spsq->ProtocolSpecific.ProtocolDataRequestSubValue = in.nsid;
+ break;
+ case smartmontools::nvme_admin_get_log_page:
+ spsq->PropertyQuery.PropertyId = win10::StorageDeviceProtocolSpecificProperty;
+ spsq->ProtocolSpecific.DataType = win10::NVMeDataTypeLogPage;
+ spsq->ProtocolSpecific.ProtocolDataRequestValue = in.cdw10 & 0xff; // LID only ?
+ // Older drivers (Win10 1607) ignore SubValue
+ // Newer drivers (Win10 1809) pass SubValue to CDW12 (DW aligned)
+ spsq->ProtocolSpecific.ProtocolDataRequestSubValue = 0; // in.cdw12 (LPOL, NVMe 1.2.1+) ?
+ break;
+ // case smartmontools::nvme_admin_get_features: // TODO
+ default:
+ return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
+ }
+
+ spsq->ProtocolSpecific.ProtocolDataOffset = sizeof(spsq->ProtocolSpecific);
+ spsq->ProtocolSpecific.ProtocolDataLength = in.size;
+
+ if (in.direction() & nvme_cmd_in::data_out)
+ memcpy(spsq->DataBuffer, in.buffer, in.size);
+
+ if (nvme_debugmode > 1)
+ pout(" [STORAGE_QUERY_PROPERTY: Id=%u, Type=%u, Value=0x%08x, SubVal=0x%08x]\n",
+ (unsigned)spsq->PropertyQuery.PropertyId,
+ (unsigned)spsq->ProtocolSpecific.DataType,
+ (unsigned)spsq->ProtocolSpecific.ProtocolDataRequestValue,
+ (unsigned)spsq->ProtocolSpecific.ProtocolDataRequestSubValue);
+
+ // Call IOCTL_STORAGE_QUERY_PROPERTY
+ DWORD num_out = 0;
+ long err = 0;
+ if (!DeviceIoControl(get_fh(), IOCTL_STORAGE_QUERY_PROPERTY,
+ spsq, spsq_raw_buf.size(), spsq, spsq_raw_buf.size(),
+ &num_out, (OVERLAPPED*)0)) {
+ err = GetLastError();
+ }
+
+ if (nvme_debugmode > 1)
+ pout(" [STORAGE_QUERY_PROPERTY: ReturnData=0x%08x, Reserved[3]={0x%x, 0x%x, 0x%x}]\n",
+ (unsigned)spsq->ProtocolSpecific.FixedProtocolReturnData,
+ (unsigned)spsq->ProtocolSpecific.Reserved[0],
+ (unsigned)spsq->ProtocolSpecific.Reserved[1],
+ (unsigned)spsq->ProtocolSpecific.Reserved[2]);
+
+ // NVMe status is checked by IOCTL
+ if (err)
+ return set_err(EIO, "IOCTL_STORAGE_QUERY_PROPERTY(NVMe) failed, Error=%ld", err);
+
+ if (in.direction() & nvme_cmd_in::data_in)
+ memcpy(in.buffer, spsq->DataBuffer, in.size);
+
+ out.result = spsq->ProtocolSpecific.FixedProtocolReturnData; // Completion DW0 ?
+ return true;
+}
+
+bool win10_nvme_device::nvme_storage_protocol_command(const nvme_cmd_in & in, nvme_cmd_out & /* out */)
+{
+ // Limit to self-test command for now
+ switch (in.opcode) {
+ case smartmontools::nvme_admin_dev_self_test:
+ break;
+ default:
+ return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
+ }
+
+ // This is based on info from https://github.com/ken-yossy/nvmetool-win (License: MIT)
+
+ // Assume NO_DATA command
+ char spcm_buf[offsetof(STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME]{};
+ STORAGE_PROTOCOL_COMMAND * spcm = reinterpret_cast<STORAGE_PROTOCOL_COMMAND *>(spcm_buf);
+
+ // Set NVMe specific STORAGE_PROTOCOL_COMMAND
+ spcm->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION;
+ spcm->Length = sizeof(STORAGE_PROTOCOL_COMMAND);
+ spcm->ProtocolType = (decltype(spcm->ProtocolType))win10::ProtocolTypeNvme;
+ spcm->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST;
+ spcm->CommandLength = STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;
+ spcm->TimeOutValue = 60;
+ spcm->CommandSpecific = STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;
+
+ NVME_COMMAND * nvcm = reinterpret_cast<NVME_COMMAND *>(&spcm->Command);
+ nvcm->CDW0.OPC = in.opcode;
+ nvcm->NSID = in.nsid;
+ nvcm->u.GENERAL.CDW10 = in.cdw10;
+
+ if (nvme_debugmode > 1)
+ pout(" [IOCTL_STORAGE_PROTOCOL_COMMAND(NVMe): CDW0.OPC=0x%02x, NSID=0x%04x, CDW10=0x%04x]\n",
+ (unsigned)nvcm->CDW0.OPC,
+ (unsigned)nvcm->NSID,
+ (unsigned)nvcm->u.GENERAL.CDW10);
+
+ // Call IOCTL_STORAGE_PROTOCOL_COMMAND
+ DWORD num_out = 0;
+ long err = 0;
+ if (!DeviceIoControl(get_fh(), IOCTL_STORAGE_PROTOCOL_COMMAND,
+ spcm, sizeof(spcm_buf), spcm, sizeof(spcm_buf),
+ &num_out, (OVERLAPPED*)0)) {
+ err = GetLastError();
+ }
+
+ // NVMe status checked by IOCTL?
+ if (err)
+ return set_err(EIO, "IOCTL_STORAGE_PROTOCOL_COMMAND(NVMe) failed, Error=%ld", err);
+
+ // out.result = 0;
+ return true;
+}
+
+bool win10_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+ if (in.cdw11 || in.cdw12 || in.cdw13 || in.cdw14 || in.cdw15)
+ return set_err(ENOSYS, "Nonzero NVMe command dwords 11-15 not supported");
+
+ switch (in.opcode) {
+ case smartmontools::nvme_admin_identify:
+ case smartmontools::nvme_admin_get_log_page:
+ // case smartmontools::nvme_admin_get_features: // TODO
+ return nvme_storage_query_property(in, out);
+ default:
+ return nvme_storage_protocol_command(in, out);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// win_smart_interface
+// Platform specific interface
+
+class win_smart_interface
+: public /*implements*/ smart_interface
+{
+public:
+ virtual std::string get_os_version_str() override;
+
+ virtual std::string get_app_examples(const char * appname) override;
+
+ virtual bool disable_system_auto_standby(bool disable) override;
+
+ virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
+ const char * pattern = 0) override;
+
+protected:
+ virtual ata_device * get_ata_device(const char * name, const char * type) override;
+
+ virtual scsi_device * get_scsi_device(const char * name, const char * type) override;
+
+ virtual nvme_device * get_nvme_device(const char * name, const char * type, unsigned nsid) override;
+
+ virtual smart_device * autodetect_smart_device(const char * name) override;
+
+ virtual smart_device * get_custom_smart_device(const char * name, const char * type) override;
+
+ virtual std::string get_valid_custom_dev_types_str() override;
+
+private:
+ smart_device * get_usb_device(const char * name, int phydrive, int logdrive = -1);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WIN64
+// Running on 64-bit Windows as 32-bit app ?
+static bool is_wow64()
+{
+ BOOL (WINAPI * IsWow64Process_p)(HANDLE, PBOOL) =
+ (BOOL (WINAPI *)(HANDLE, PBOOL))(void *)
+ GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process");
+ if (!IsWow64Process_p)
+ return false;
+ BOOL w64 = FALSE;
+ if (!IsWow64Process_p(GetCurrentProcess(), &w64))
+ return false;
+ return !!w64;
+}
+#endif // _WIN64
+
+// Return info string about build host and OS version
+std::string win_smart_interface::get_os_version_str()
+{
+ char vstr[sizeof(SMARTMONTOOLS_BUILD_HOST)-1+sizeof("-2003r2(64)-sp2.1")+13]
+ = SMARTMONTOOLS_BUILD_HOST;
+ if (vstr[1] < '6')
+ vstr[1] = '6';
+ char * const vptr = vstr+sizeof(SMARTMONTOOLS_BUILD_HOST)-1;
+ const int vlen = sizeof(vstr)-sizeof(SMARTMONTOOLS_BUILD_HOST);
+ assert(vptr == vstr+strlen(vstr) && vptr+vlen+1 == vstr+sizeof(vstr));
+
+ // Starting with Windows 8.1, GetVersionEx() does no longer report the
+ // actual OS version. RtlGetVersion() is not affected.
+ LONG /*NTSTATUS*/ (WINAPI /*NTAPI*/ * RtlGetVersion_p)(LPOSVERSIONINFOEXW) =
+ (LONG (WINAPI *)(LPOSVERSIONINFOEXW))(void *)
+ GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion");
+
+ OSVERSIONINFOEXW vi; memset(&vi, 0, sizeof(vi));
+ vi.dwOSVersionInfoSize = sizeof(vi);
+ if (!RtlGetVersion_p || RtlGetVersion_p(&vi)) {
+ if (!GetVersionExW((OSVERSIONINFOW *)&vi))
+ return vstr;
+ }
+
+ const char * w = 0;
+ unsigned build = 0;
+ if ( vi.dwPlatformId == VER_PLATFORM_WIN32_NT
+ && vi.dwMajorVersion <= 0xf && vi.dwMinorVersion <= 0xf) {
+ switch ( (vi.dwMajorVersion << 4 | vi.dwMinorVersion) << 1
+ | (vi.wProductType > VER_NT_WORKSTATION ? 1 : 0) ) {
+ case 0x50<<1 :
+ case 0x50<<1 | 1: w = "2000"; break;
+ case 0x51<<1 : w = "xp"; break;
+ case 0x52<<1 : w = "xp64"; break;
+ case 0x52<<1 | 1: w = (!GetSystemMetrics(89/*SM_SERVERR2*/)
+ ? "2003"
+ : "2003r2"); break;
+ case 0x60<<1 : w = "vista"; break;
+ case 0x60<<1 | 1: w = "2008"; break;
+ case 0x61<<1 : w = "win7"; break;
+ case 0x61<<1 | 1: w = "2008r2"; break;
+ case 0x62<<1 : w = "win8"; break;
+ case 0x62<<1 | 1: w = "2012"; break;
+ case 0x63<<1 : w = "win8.1"; break;
+ case 0x63<<1 | 1: w = "2012r2"; break;
+ case 0xa0<<1 :
+ switch (vi.dwBuildNumber) {
+ case 10240: w = "w10-1507"; break;
+ case 10586: w = "w10-1511"; break;
+ case 14393: w = "w10-1607"; break;
+ case 15063: w = "w10-1703"; break;
+ case 16299: w = "w10-1709"; break;
+ case 17134: w = "w10-1803"; break;
+ case 17763: w = "w10-1809"; break;
+ case 18362: w = "w10-1903"; break;
+ case 18363: w = "w10-1909"; break;
+ case 19041: w = "w10-2004"; break;
+ case 19042: w = "w10-20H2"; break;
+ case 19043: w = "w10-21H1"; break;
+ case 19044: w = "w10-21H2"; break;
+ case 19045: w = "w10-22H2"; break;
+ case 22000: w = "w11-21H2"; break;
+ case 22621: w = "w11-22H2"; break;
+ default: w = (vi.dwBuildNumber < 22000
+ ? "w10"
+ : "w11");
+ build = vi.dwBuildNumber; break;
+ } break;
+ case 0xa0<<1 | 1:
+ switch (vi.dwBuildNumber) {
+ case 14393: w = "2016-1607"; break;
+ case 16299: w = "2016-1709"; break;
+ case 17134: w = "2016-1803"; break;
+ case 17763: w = "2019-1809"; break;
+ case 18362: w = "2019-1903"; break;
+ case 18363: w = "2019-1909"; break;
+ case 19041: w = "2019-2004"; break;
+ case 19042: w = "2019-20H2"; break;
+ case 20348: w = "2022-21H2"; break;
+ default: w = (vi.dwBuildNumber < 17763
+ ? "2016"
+ : vi.dwBuildNumber < 20348
+ ? "2019"
+ : "2022");
+ build = vi.dwBuildNumber; break;
+ } break;
+ }
+ }
+
+ const char * w64 = "";
+#ifndef _WIN64
+ if (is_wow64())
+ w64 = "(64)";
+#endif
+
+ if (!w)
+ snprintf(vptr, vlen, "-%s%u.%u%s",
+ (vi.dwPlatformId==VER_PLATFORM_WIN32_NT ? "nt" : "??"),
+ (unsigned)vi.dwMajorVersion, (unsigned)vi.dwMinorVersion, w64);
+ else if (build)
+ snprintf(vptr, vlen, "-%s-b%u%s", w, build, w64);
+ else if (vi.wServicePackMinor)
+ snprintf(vptr, vlen, "-%s-sp%u.%u%s", w, vi.wServicePackMajor, vi.wServicePackMinor, w64);
+ else if (vi.wServicePackMajor)
+ snprintf(vptr, vlen, "-%s-sp%u%s", w, vi.wServicePackMajor, w64);
+ else
+ snprintf(vptr, vlen, "-%s%s", w, w64);
+ return vstr;
+}
+
+
+ata_device * win_smart_interface::get_ata_device(const char * name, const char * type)
+{
+ const char * testname = skipdev(name);
+ if (!strncmp(testname, "csmi", 4))
+ return new win_csmi_device(this, name, type);
+ if (!strncmp(testname, "tw_cli", 6))
+ return new win_tw_cli_device(this, name, type);
+ return new win_ata_device(this, name, type);
+}
+
+scsi_device * win_smart_interface::get_scsi_device(const char * name, const char * type)
+{
+ return new win_scsi_device(this, name, type);
+}
+
+nvme_device * win_smart_interface::get_nvme_device(const char * name, const char * type,
+ unsigned nsid)
+{
+ if (str_starts_with(skipdev(name), "nvme"))
+ return new win_nvme_device(this, name, type, nsid);
+ return new win10_nvme_device(this, name, type, nsid);
+}
+
+
+smart_device * win_smart_interface::get_custom_smart_device(const char * name, const char * type)
+{
+ // Areca?
+ int disknum = -1, n1 = -1, n2 = -1;
+ int encnum = 1;
+ char devpath[32];
+
+ if (sscanf(type, "areca,%n%d/%d%n", &n1, &disknum, &encnum, &n2) >= 1 || n1 == 6) {
+ if (!(1 <= disknum && disknum <= 128)) {
+ set_err(EINVAL, "Option -d areca,N/E (N=%d) must have 1 <= N <= 128", disknum);
+ return 0;
+ }
+ if (!(1 <= encnum && encnum <= 8)) {
+ set_err(EINVAL, "Option -d areca,N/E (E=%d) must have 1 <= E <= 8", encnum);
+ return 0;
+ }
+
+ name = skipdev(name);
+#define ARECA_MAX_CTLR_NUM 16
+ n1 = -1;
+ int ctlrindex = 0;
+ if (sscanf(name, "arcmsr%d%n", &ctlrindex, &n1) >= 1 && n1 == (int)strlen(name)) {
+ /*
+ 1. scan from "\\\\.\\scsi[0]:" up to "\\\\.\\scsi[ARECA_MAX_CTLR_NUM]:" and
+ 2. map arcmsrX into "\\\\.\\scsiX"
+ */
+ for (int idx = 0; idx < ARECA_MAX_CTLR_NUM; idx++) {
+ memset(devpath, 0, sizeof(devpath));
+ snprintf(devpath, sizeof(devpath), "\\\\.\\scsi%d:", idx);
+ win_areca_ata_device *arcdev = new win_areca_ata_device(this, devpath, disknum, encnum);
+ if(arcdev->arcmsr_probe()) {
+ if(ctlrindex-- == 0) {
+ return arcdev;
+ }
+ }
+ delete arcdev;
+ }
+ set_err(ENOENT, "No Areca controller found");
+ }
+ else
+ set_err(EINVAL, "Option -d areca,N/E requires device name /dev/arcmsrX");
+ return 0;
+ }
+
+ // aacraid?
+ unsigned ctrnum, lun, target;
+ n1 = -1; n2 = -1;
+
+ if ( sscanf(type, "aacraid,%u,%u,%u%n,force%n", &ctrnum, &lun, &target, &n1, &n2) >= 3
+ && (n1 == (int)strlen(type) || n2 == (int)strlen(type))) {
+
+ if (n2 < 0) {
+ set_err(ENOSYS,
+ "smartmontools AACRAID support is reportedly broken on Windows.\n"
+ "See https://www.smartmontools.org/ticket/1515 for details.\n"
+ "Use '-d aacraid,H,L,ID,force' to try anyway at your own risk.\n"
+ "If you could provide help to fix the problem, please inform\n"
+ PACKAGE_BUGREPORT "\n");
+ return 0;
+ }
+
+#define aacraid_MAX_CTLR_NUM 16
+ if (ctrnum >= aacraid_MAX_CTLR_NUM) {
+ set_err(EINVAL, "aacraid: invalid host number %u", ctrnum);
+ return 0;
+ }
+
+ /*
+ 1. scan from "\\\\.\\scsi[0]:" up to "\\\\.\\scsi[AACRAID_MAX_CTLR_NUM]:" and
+ 2. map ARCX into "\\\\.\\scsiX"
+ */
+ memset(devpath, 0, sizeof(devpath));
+ unsigned ctlrindex = 0;
+ for (int portNum = 0; portNum < aacraid_MAX_CTLR_NUM; portNum++){
+ char subKey[63];
+ snprintf(subKey, sizeof(subKey), "HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port %d", portNum);
+ HKEY hScsiKey = 0;
+ long regStatus = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hScsiKey);
+ if (regStatus == ERROR_SUCCESS){
+ char driverName[20];
+ DWORD driverNameSize = sizeof(driverName);
+ DWORD regType = 0;
+ regStatus = RegQueryValueExA(hScsiKey, "Driver", NULL, &regType, (LPBYTE) driverName, &driverNameSize);
+ if (regStatus == ERROR_SUCCESS){
+ if (regType == REG_SZ){
+ if (stricmp(driverName, "arcsas") == 0){
+ if(ctrnum == ctlrindex){
+ snprintf(devpath, sizeof(devpath), "\\\\.\\Scsi%d:", portNum);
+ return get_sat_device("sat,auto",
+ new win_aacraid_device(this, devpath, ctrnum, target, lun));
+ }
+ ctlrindex++;
+ }
+ }
+ }
+ RegCloseKey(hScsiKey);
+ }
+ }
+
+ set_err(EINVAL, "aacraid: host %u not found", ctrnum);
+ return 0;
+ }
+
+ return 0;
+}
+
+std::string win_smart_interface::get_valid_custom_dev_types_str()
+{
+ return "aacraid,H,L,ID, areca,N[/E]";
+}
+
+
+// Return value for device detection functions
+enum win_dev_type { DEV_UNKNOWN = 0, DEV_ATA, DEV_SCSI, DEV_SAT, DEV_USB, DEV_NVME };
+
+// Return true if ATA drive behind a SAT layer
+static bool is_sat(const STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ if (!data->desc.VendorIdOffset)
+ return false;
+ if (strcmp(data->raw + data->desc.VendorIdOffset, "ATA "))
+ return false;
+ return true;
+}
+
+// Return true if Intel ICHxR RAID volume
+static bool is_intel_raid_volume(const STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ if (!(data->desc.VendorIdOffset && data->desc.ProductIdOffset))
+ return false;
+ const char * vendor = data->raw + data->desc.VendorIdOffset;
+ if (!(!strnicmp(vendor, "Intel", 5) && strspn(vendor+5, " ") == strlen(vendor+5)))
+ return false;
+ if (strnicmp(data->raw + data->desc.ProductIdOffset, "Raid ", 5))
+ return false;
+ return true;
+}
+
+// get DEV_* for open handle
+static win_dev_type get_controller_type(HANDLE hdevice, bool admin, GETVERSIONINPARAMS_EX * ata_version_ex)
+{
+ // Get BusType from device descriptor
+ STORAGE_DEVICE_DESCRIPTOR_DATA data;
+ if (storage_query_property_ioctl(hdevice, &data))
+ return DEV_UNKNOWN;
+
+ // Newer BusType* values are missing in older includes
+ switch ((int)data.desc.BusType) {
+ case BusTypeAta:
+ case 0x0b: // BusTypeSata
+ // Certain Intel AHCI drivers (C600+/C220+) have broken
+ // IOCTL_ATA_PASS_THROUGH support and a working SAT layer
+ if (is_sat(&data))
+ return DEV_SAT;
+
+ if (ata_version_ex)
+ memset(ata_version_ex, 0, sizeof(*ata_version_ex));
+ return DEV_ATA;
+
+ case BusTypeScsi:
+ case BusTypeRAID:
+ if (is_sat(&data))
+ return DEV_SAT;
+
+ // Intel ICHxR RAID volume: reports SMART_GET_VERSION but does not support SMART_*
+ if (is_intel_raid_volume(&data))
+ return DEV_SCSI;
+ // LSI/3ware RAID volume: supports SMART_*
+ if (admin && smart_get_version(hdevice, ata_version_ex) >= 0)
+ return DEV_ATA;
+
+ return DEV_SCSI;
+
+ case 0x09: // BusTypeiScsi
+ case 0x0a: // BusTypeSas
+ if (is_sat(&data))
+ return DEV_SAT;
+
+ return DEV_SCSI;
+
+ case BusTypeUsb:
+ return DEV_USB;
+
+ case 0x11: // BusTypeNvme
+ return DEV_NVME;
+
+ case 0x12: //BusTypeSCM
+ case 0x13: //BusTypeUfs
+ case 0x14: //BusTypeMax,
+ default:
+ return DEV_UNKNOWN;
+ }
+ /*NOTREACHED*/
+}
+
+// get DEV_* for device path
+static win_dev_type get_controller_type(const char * path, GETVERSIONINPARAMS_EX * ata_version_ex = 0)
+{
+ bool admin = true;
+ HANDLE h = CreateFileA(path, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (h == INVALID_HANDLE_VALUE) {
+ admin = false;
+ h = CreateFileA(path, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (h == INVALID_HANDLE_VALUE)
+ return DEV_UNKNOWN;
+ }
+ if (ata_debugmode > 1 || scsi_debugmode > 1)
+ pout(" %s: successfully opened%s\n", path, (!admin ? " (without admin rights)" :""));
+ win_dev_type type = get_controller_type(h, admin, ata_version_ex);
+ CloseHandle(h);
+ return type;
+}
+
+// get DEV_* for physical drive number
+static win_dev_type get_phy_drive_type(int drive, GETVERSIONINPARAMS_EX * ata_version_ex)
+{
+ char path[30];
+ snprintf(path, sizeof(path)-1, "\\\\.\\PhysicalDrive%d", drive);
+ return get_controller_type(path, ata_version_ex);
+}
+
+static win_dev_type get_phy_drive_type(int drive)
+{
+ return get_phy_drive_type(drive, 0);
+}
+
+// get DEV_* for logical drive number
+static win_dev_type get_log_drive_type(int drive)
+{
+ char path[30];
+ snprintf(path, sizeof(path)-1, "\\\\.\\%c:", 'A'+drive);
+ return get_controller_type(path);
+}
+
+static win_dev_type get_dev_type(const char * name, int & phydrive, int & logdrive)
+{
+ phydrive = logdrive = -1;
+
+ name = skipdev(name);
+ if (!strncmp(name, "st", 2))
+ return DEV_SCSI;
+ if (!strncmp(name, "nst", 3))
+ return DEV_SCSI;
+ if (!strncmp(name, "tape", 4))
+ return DEV_SCSI;
+
+ logdrive = drive_letter(name);
+ if (logdrive >= 0) {
+ win_dev_type type = get_log_drive_type(logdrive);
+ return (type != DEV_UNKNOWN ? type : DEV_SCSI);
+ }
+
+ char drive[2+1] = "";
+ if (sscanf(name, "sd%2[a-z]", drive) == 1) {
+ phydrive = sdxy_to_phydrive(drive);
+ return get_phy_drive_type(phydrive);
+ }
+
+ if (sscanf(name, "pd%d", &phydrive) == 1 && phydrive >= 0)
+ return get_phy_drive_type(phydrive);
+
+ return DEV_UNKNOWN;
+}
+
+
+smart_device * win_smart_interface::get_usb_device(const char * name,
+ int phydrive, int logdrive /* = -1 */)
+{
+ // Get USB bridge ID
+ unsigned short vendor_id = 0, product_id = 0;
+ if (!get_usb_id(phydrive, logdrive, vendor_id, product_id)) {
+ set_err(EINVAL, "Unable to read USB device ID");
+ return 0;
+ }
+
+ // Get type name for this ID
+ const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id);
+ if (!usbtype)
+ return 0;
+
+ // Return SAT/USB device for this type
+ return get_scsi_passthrough_device(usbtype, new win_scsi_device(this, name, ""));
+}
+
+smart_device * win_smart_interface::autodetect_smart_device(const char * name)
+{
+ const char * testname = skipdev(name);
+ if (str_starts_with(testname, "hd"))
+ return new win_ata_device(this, name, "");
+
+ if (str_starts_with(testname, "tw_cli"))
+ return new win_tw_cli_device(this, name, "");
+
+ if (str_starts_with(testname, "csmi"))
+ return new win_csmi_device(this, name, "");
+
+ if (str_starts_with(testname, "nvme"))
+ return new win_nvme_device(this, name, "", 0 /* use default nsid */);
+
+ int phydrive = -1, logdrive = -1;
+ win_dev_type type = get_dev_type(name, phydrive, logdrive);
+
+ if (type == DEV_ATA)
+ return new win_ata_device(this, name, "");
+
+ if (type == DEV_SCSI)
+ return new win_scsi_device(this, name, "");
+
+ if (type == DEV_SAT)
+ return get_sat_device("sat", new win_scsi_device(this, name, ""));
+
+ if (type == DEV_USB)
+ return get_usb_device(name, phydrive, logdrive);
+
+ if (type == DEV_NVME)
+ return new win10_nvme_device(this, name, "", 0 /* use default nsid */);
+
+ return 0;
+}
+
+
+// Scan for devices
+bool win_smart_interface::scan_smart_devices(smart_device_list & devlist,
+ const char * type, const char * pattern /* = 0*/)
+{
+ if (pattern) {
+ set_err(EINVAL, "DEVICESCAN with pattern not implemented yet");
+ return false;
+ }
+
+ // Check for "[*,]pd" type
+ bool pd = false;
+ char type2[16+1] = "";
+ if (type) {
+ int nc = -1;
+ if (!strcmp(type, "pd")) {
+ pd = true;
+ type = 0;
+ }
+ else if (sscanf(type, "%16[^,],pd%n", type2, &nc) == 1 &&
+ nc == (int)strlen(type)) {
+ pd = true;
+ type = type2;
+ }
+ }
+
+ // Set valid types
+ bool ata, scsi, sat, usb, csmi, nvme;
+ if (!type) {
+ ata = scsi = usb = sat = csmi = true;
+#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL
+ nvme = true;
+#else
+ nvme = false;
+#endif
+ }
+ else {
+ ata = scsi = usb = sat = csmi = nvme = false;
+ if (!strcmp(type, "ata"))
+ ata = true;
+ else if (!strcmp(type, "scsi"))
+ scsi = true;
+ else if (!strcmp(type, "sat"))
+ sat = true;
+ else if (!strcmp(type, "usb"))
+ usb = true;
+ else if (!strcmp(type, "csmi"))
+ csmi = true;
+ else if (!strcmp(type, "nvme"))
+ nvme = true;
+ else {
+ set_err(EINVAL,
+ "Invalid type '%s', valid arguments are: ata[,pd], scsi[,pd], "
+ "sat[,pd], usb[,pd], csmi, nvme, pd", type);
+ return false;
+ }
+ }
+
+ char name[32];
+
+ if (ata || scsi || sat || usb || nvme) {
+ // Scan up to 128 drives and 2 3ware controllers
+ const int max_raid = 2;
+ bool raid_seen[max_raid] = {false, false};
+
+ for (int i = 0; i < 128; i++) {
+ if (pd)
+ snprintf(name, sizeof(name), "/dev/pd%d", i);
+ else if (i + 'a' <= 'z')
+ snprintf(name, sizeof(name), "/dev/sd%c", i + 'a');
+ else
+ snprintf(name, sizeof(name), "/dev/sd%c%c",
+ i / ('z'-'a'+1) - 1 + 'a',
+ i % ('z'-'a'+1) + 'a');
+
+ smart_device * dev = 0;
+ GETVERSIONINPARAMS_EX vers_ex;
+
+ switch (get_phy_drive_type(i, (ata ? &vers_ex : 0))) {
+ case DEV_ATA:
+ // Driver supports SMART_GET_VERSION or STORAGE_QUERY_PROPERTY returned ATA/SATA
+ if (!ata)
+ continue;
+
+ // Interpret RAID drive map if present
+ if (vers_ex.wIdentifier == SMART_VENDOR_3WARE) {
+ // Skip if too many controllers or logical drive from this controller already seen
+ if (!(vers_ex.wControllerId < max_raid && !raid_seen[vers_ex.wControllerId]))
+ continue;
+ raid_seen[vers_ex.wControllerId] = true;
+ // Add physical drives
+ int len = strlen(name);
+ for (unsigned int pi = 0; pi < 32; pi++) {
+ if (vers_ex.dwDeviceMapEx & (1U << pi)) {
+ snprintf(name+len, sizeof(name)-1-len, ",%u", pi);
+ devlist.push_back( new win_ata_device(this, name, "ata") );
+ }
+ }
+ continue;
+ }
+
+ dev = new win_ata_device(this, name, "ata");
+ break;
+
+ case DEV_SCSI:
+ // STORAGE_QUERY_PROPERTY returned SCSI/SAS/...
+ if (!scsi)
+ continue;
+ dev = new win_scsi_device(this, name, "scsi");
+ break;
+
+ case DEV_SAT:
+ // STORAGE_QUERY_PROPERTY returned VendorId "ATA "
+ if (!sat)
+ continue;
+ dev = get_sat_device("sat", new win_scsi_device(this, name, ""));
+ break;
+
+ case DEV_USB:
+ // STORAGE_QUERY_PROPERTY returned USB
+ if (!usb)
+ continue;
+ dev = get_usb_device(name, i);
+ if (!dev)
+ // Unknown or unsupported USB ID, return as SCSI
+ dev = new win_scsi_device(this, name, "");
+ break;
+
+ case DEV_NVME:
+ // STORAGE_QUERY_PROPERTY returned NVMe
+ if (!nvme)
+ continue;
+ dev = new win10_nvme_device(this, name, "", 0 /* use default nsid */);
+ break;
+
+ default:
+ // Unknown type
+ continue;
+ }
+
+ devlist.push_back(dev);
+ }
+ }
+
+ if (csmi) {
+ // Scan CSMI devices
+ for (int i = 0; i <= 9; i++) {
+ snprintf(name, sizeof(name)-1, "/dev/csmi%d,0", i);
+ win_csmi_device test_dev(this, name, "");
+ if (!test_dev.open_scsi())
+ continue;
+
+ unsigned ports_used = test_dev.get_ports_used();
+ if (!ports_used)
+ continue;
+
+ for (int pi = 0; pi < 32; pi++) {
+ if (!(ports_used & (1U << pi)))
+ continue;
+ snprintf(name, sizeof(name)-1, "/dev/csmi%d,%d", i, pi);
+ devlist.push_back( new win_csmi_device(this, name, "ata") );
+ }
+ }
+ }
+
+ if (nvme) {
+ // Scan \\.\Scsi[0-31] for up to 10 NVMe devices
+ int nvme_cnt = 0;
+ for (int i = 0; i < 32; i++) {
+ snprintf(name, sizeof(name)-1, "/dev/nvme%d", i);
+ win_nvme_device test_dev(this, name, "", 0);
+ if (!test_dev.open_scsi(i)) {
+ if (test_dev.get_errno() == EACCES)
+ break;
+ continue;
+ }
+
+ if (!test_dev.probe())
+ continue;
+ if (++nvme_cnt >= 10)
+ break;
+ }
+
+ for (int i = 0; i < nvme_cnt; i++) {
+ snprintf(name, sizeof(name)-1, "/dev/nvme%d", i);
+ devlist.push_back( new win_nvme_device(this, name, "nvme", 0) );
+ }
+ }
+ return true;
+}
+
+
+// get examples for smartctl
+std::string win_smart_interface::get_app_examples(const char * appname)
+{
+ if (strcmp(appname, "smartctl"))
+ return "";
+ return "=================================================== SMARTCTL EXAMPLES =====\n\n"
+ " smartctl -a /dev/sda (Prints all SMART information)\n\n"
+ " smartctl --smart=on --offlineauto=on --saveauto=on /dev/sda\n"
+ " (Enables SMART on first disk)\n\n"
+ " smartctl -t long /dev/sda (Executes extended disk self-test)\n\n"
+ " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/sda\n"
+ " (Prints Self-Test & Attribute errors)\n"
+ " smartctl -a /dev/sda\n"
+ " (Prints all information for disk on PhysicalDrive 0)\n"
+ " smartctl -a /dev/pd3\n"
+ " (Prints all information for disk on PhysicalDrive 3)\n"
+ " smartctl -a /dev/tape1\n"
+ " (Prints all information for SCSI tape on Tape 1)\n"
+ " smartctl -A /dev/hdb,3\n"
+ " (Prints Attributes for physical drive 3 on 3ware 9000 RAID)\n"
+ " smartctl -A /dev/tw_cli/c0/p1\n"
+ " (Prints Attributes for 3ware controller 0, port 1 using tw_cli)\n"
+ " smartctl --all --device=areca,3/1 /dev/arcmsr0\n"
+ " (Prints all SMART info for 3rd ATA disk of the 1st enclosure\n"
+ " on 1st Areca RAID controller)\n"
+ "\n"
+ " ATA SMART access methods and ordering may be specified by modifiers\n"
+ " following the device name: /dev/hdX:[saicm], where\n"
+ " 's': SMART_* IOCTLs, 'a': IOCTL_ATA_PASS_THROUGH,\n"
+ " 'i': IOCTL_IDE_PASS_THROUGH, 'f': IOCTL_STORAGE_*,\n"
+ " 'm': IOCTL_SCSI_MINIPORT_*.\n"
+ + strprintf(
+ " The default on this system is /dev/sdX:%s\n", ata_get_def_options()
+ );
+}
+
+
+bool win_smart_interface::disable_system_auto_standby(bool disable)
+{
+ if (disable) {
+ SYSTEM_POWER_STATUS ps;
+ if (!GetSystemPowerStatus(&ps))
+ return set_err(ENOSYS, "Unknown power status");
+ if (ps.ACLineStatus != 1) {
+ SetThreadExecutionState(ES_CONTINUOUS);
+ if (ps.ACLineStatus == 0)
+ set_err(EIO, "AC offline");
+ else
+ set_err(EIO, "Unknown AC line status");
+ return false;
+ }
+ }
+
+ if (!SetThreadExecutionState(ES_CONTINUOUS | (disable ? ES_SYSTEM_REQUIRED : 0)))
+ return set_err(ENOSYS);
+ return true;
+}
+
+
+} // namespace
+
+/////////////////////////////////////////////////////////////////////////////
+
+// Initialize platform interface and register with smi()
+void smart_interface::init()
+{
+ {
+ // Remove "." from DLL search path if supported
+ // to prevent DLL preloading attacks
+ BOOL (WINAPI * SetDllDirectoryA_p)(LPCSTR) =
+ (BOOL (WINAPI *)(LPCSTR))(void *)
+ GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetDllDirectoryA");
+ if (SetDllDirectoryA_p)
+ SetDllDirectoryA_p("");
+ }
+
+ static os_win32::win_smart_interface the_win_interface;
+ smart_interface::set(&the_win_interface);
+}
+
+
+#ifndef __CYGWIN__
+
+// Get exe directory
+// (prototype in utiliy.h)
+std::string get_exe_dir()
+{
+ char path[MAX_PATH];
+ // Get path of this exe
+ if (!GetModuleFileNameA(GetModuleHandleA(0), path, sizeof(path)))
+ throw std::runtime_error("GetModuleFileName() failed");
+ // Replace backslash by slash
+ int sl = -1;
+ for (int i = 0; path[i]; i++)
+ if (path[i] == '\\') {
+ path[i] = '/'; sl = i;
+ }
+ // Remove filename
+ if (sl >= 0)
+ path[sl] = 0;
+ return path;
+}
+
+#endif
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-----
+
+mQENBFgOYoEBCAC93841SlFmpp6640hKUvZ8PbZR6OGnZnMXD6QRVzpibXGZXUDB
+f6unujun5Ql4ObAWt6QuRqz5Gk2gF8tcOfN6edR/uK5gyX2rlWVLoZKOV91a3aDI
+iIDh018tLWOpHg3VxgHL6f0iMcFogUYnD5zhC5Z2GVhFb/cVpj+ocZWcxQLQGPVv
+uZPUQWrvdpFEzcnxPMtJJDqXEChzhrdFTXGm69ERxULOro7yDmG1Y5xWmhdGnPPM
+cuCXVVlADz/Gh1w+ay7RqFnzPjqjQmHAuggns467TJEcS0yiX4LJnEoKLyPGen9L
+FH6z38xHCNt4Da05/OeRgXwVLH9M95lu8d6TABEBAAG0U1NtYXJ0bW9udG9vbHMg
+U2lnbmluZyBLZXkgKGV4dC4gdG8gMjAyNCkgPHNtYXJ0bW9udG9vbHMtZGF0YWJh
+c2VAbGlzdGkuanBiZXJsaW4uZGU+iQFBBBMBAgArAhsDBQkPZe4NBgsJCAcDAgYV
+CAIJCgsEFgIDAQIeAQIXgAUCXheK5gIZAQAKCRDzh2PO39IlWdUTCAC8v9Oa7umW
++/tXBiEtElDW/U2rEOC3OHWSzPvqE4iGjWc5fbvrAKS7bfccZM8Aq0a1t2pSbIlB
+MvRrsNTGdQSPsOdhxPD8pEJW0uH9Z5VyPzoO9VIaoqi1irRdWnXCfhBJX9PLySAb
+9BPQZXXQypmACieRDv31E4hiB+vYet/SpVuRyfL57XU3jmwFREip9OiFOp+61X2+
+oIlgvNU60JZy2vXpTo6PNbDGetEycfH6Y8vfCXniihMkSfeOnNqWI/hycBDprFB5
+CB5ShIH71vhCOPnVGwtYY30wlJ1+Ybg2ZAIi6JN8E38Dpx382IzeT2LydnZydiC6
+PcLCr7mbsX3hiQEcBBMBAgAGBQJeF4sWAAoJEC/N7AvTrxqr7ZAH/jB4xFtBTo1x
+w8CGwslZCJ+/BeEZ5XpV+8zLdeRV2tXegUFjGZ9FI6UpzBeVyK2R1qGbcdSf2S45
+KutcM2gjKETW+ZwW76qHJD52mYihPPLXu2pRAG2WyH5GDnqNMj5iQ1inoPdZOTpi
+evBMTv1YHJML6SiF6t/HoKorl5ffvHBE/1onBfUzLwQ/ct14sZ2UXHzyxdHo73vm
+XWgcjQ1TQhCSdLqucQbwR78EyUa9tYxk/NWBqfc5YHt7t+KTVTLlp7Buk1wscLkj
+NTlxl+IjAxRwsWc6PWnyRdAgXxtt2q6llYgFahWM21OyJVLVjbMGVF+oBtFumqq3
+lQy6H6tp/1uJAhwEEwECAAYFAl4XiyMACgkQvwsznGS8qosSiw//QjbWDldB2gHf
+3Tfs+LaFdzkDbioWdnj96DiCynTSwZF8d5ISqwA+QTL/43Y0msU26WBMvIRBg2Xm
++r4TMMfWF4a1Yjq6cisKEaUsbjV9ztzH/XB2ydo8HgnxZuVKQoIuh1sSrE7p6mpQ
+YUrV5eWRpqc79AI9ZzRBM5nhbBejqLVw2F8dyz6c3lfGM9IOenp+Y8N43SdNpBcp
+DuHnzbQIMtkyoX7tTKDDv5gnoRNCsdBsCduTyNWYOIEdhRiCfo5Ce7kufIoo4ZqV
+BM8dzwm1RrcYa0kMKPZAucJDRjwevEYDbOg7vmEYsuGPRbVmOFdx4uMx4gX8vF5+
+AG3rTSA805zkwD+WQXyYQohVZxNjeK7P/ukr6NCZx226gwAiw1ms7PYOo8snjK8e
+nRlMTLKiGiMIH7xJu55JliVlcEvn3G7WO0n4qQOJj3Msh+xflBSfZmzBDAzPgxwC
+m/RSmonGV0uZVJFDHCpqus35E6bzFF6yO3yXvpngAMTBrpX6Nzgea1SzlK2Iquls
+te1GYAx/IXaY7cVYo4iEv/m346SINzLGHpXZkbbcenSgljBfHLCz7vF33IotfEWh
+C7Kb4iKbEjERa+zzqR+vK+nDj6YG9Mvguj1EqnM47oDwgMaqWY6oPfefLCD8Tg51
+rlAAGFdcWb9g034vgtK8l+ooUtn63PKJAhwEEwECAAYFAl4XiywACgkQ6nSrJXIQ
+QsUuTRAAsSMmQ7jsvmljExwrmIu6Oyh+1J5D/GPBRYhSyip/bnxCscCBnpjEk8+7
+VG9JtGTCa0zVY14Y3Cl4obND25QN9LhiE/y8olnIgJ2adtmpi6+zFpdGWVYUpDgZ
+IMePUVKyZenTjezFwRlLsYsxbSb9wIR1iofP1l/dQF8DwhwFL9AGRmHTcWM1ZYoc
+fv80A5SAposnspnkKKcuC3q2+pMsUtbHT9t/+iusVXBDERh+FPlvtYh+Khze3c8z
+g4M9RsQLCanMp4jZhzgSakjeg9tCr33SIJIEKpn6MUftX9QC82S75UNwxXgC38EA
+s2t+BjPLUaXENSdOe3l+KKY5ozbmRpRmQIHw7jlT3+9C0RUHGTPQYCidsx8OdYA0
+4wDRWcjCQcXWxTaUoeaoMJcE1iv5IIf/X0MXYMlCPG8OKAlDE2Kkrx0A8agPp7JH
+0UAOaqpAA74kZnpuvJ6BqrX2hMbNbyVg1rWu1BQA3qESa41rKiWyEtjiLdQ/NtNu
+6BsPhDGvaQqGbu4t0GfJ1PhbFnHrVkLW8v1NzYZRpLXAFJGZdD6Ue/L6bHFOJ6SJ
+JwAHjH26nxSMuDV779AUrnOcmoXIkj6sdAwDZ5Z2ri7b2MgkrJzeapKd0SItnWUQ
+TMe7YUl8B+kUATj01YWMLtHsX9yciFP0iDagW14/rFJHtchOBcu0U1NtYXJ0bW9u
+dG9vbHMgU2lnbmluZyBLZXkgKHRocm91Z2ggMjAxOCkgPHNtYXJ0bW9udG9vbHMt
+ZGF0YWJhc2VAbGlzdGkuanBiZXJsaW4uZGU+iQE+BBMBAgAoAhsDBgsJCAcDAgYV
+CAIJCgsEFgIDAQIeAQIXgAUJD2XuDQUCXheK5gAKCRDzh2PO39IlWTDxCACtkOGn
+vUs/m/uE7IHoSM6wj/6OXXo+TEM1rgnl40oySVoMgyonx7PSwi9rSoDC8AfRhN2q
+bFLEQcrGI8V7PxLpjsz5Z0m/ZnZJAP7TB5WhLRJdu3w2cssjekhIRc+I2B00gcRl
+H//okXyvGte3kr1JdgaownbslwcZRxyNdvWigQH/Vnz91lKAujGULJyl7hv6Kl02
+HYynYmxGmES3pd5VEOpA/DR7n54T2J+Vubh99RT+RH2v46e7LnPhZhN2uxvIiJKE
+8Lp67l1aeMXfgZv6dQ7Dl+pu5lUUyyMQ+nUMBGKZBWftyqhekZrvYcVnTJYU93kU
+41QULaRVIwg888kUiQEcBBMBAgAGBQJZ7kylAAoJEC/N7AvTrxqroQQH/jrZAGT5
+t8uyzRTzJCf3Bco8FqwKcfw8hhpF1Uaypa+quxkpYz9PtP+3e9lGxl0XSEzOwHjf
+gGWXISUOM1ufVxo2hSLG87yO7naFAtylL8l0Zny8Fb6kmT9f3vMktbHdXHUTDNrC
+UkoElEwwDK3qaur8IPUaIKeSTC3C8E/DVnasLs9cpOs2LPIKr3ishbqbHNeWOgGy
+HbA4KCtvQzBhun9drmtQJW6OyCC9FcIoqPSFM/bs2KHf7qATNu9kSMg/YWw7WLAD
+4GPqH9us1GigQ0h6Y4KG5EgmkFvuQFPLHvT4rtqv51zzs1iwFh4+GIagFp+HJ2jn
+lp+GcZcySlwfnemJAT4EEwECACgFAlnuSe4CGwMFCQQcDQAGCwkIBwMCBhUIAgkK
+CwQWAgMBAh4BAheAAAoJEPOHY87f0iVZVMQIAK5wPezq0ROsxiCYPLcR9dF/Qdp2
+1pLfodi6wsC9FAlTVJ3fk2vkNQDb5rMkNvZ/MHf2EWoVIFHvPZcJ6paBjZlapvGF
+qDNrU6hDbakO0PIej5yy+qVeIYcSQpNZeHchAhOOJcnN0o8H6SzZik38b4Hb8H5X
+do78LsZJwU0jsKG6LH3gjiWJtrC+WCXCMYzEGjAJXev2npU2DMVVwxsfYLfdZWq7
+FJJINv8R9EUjtSQQIynJAwb2lFvZB+jC6u8Vv9N1Wid6wh5lF5ejMt6KXqWOvNn+
+YreopmQfbn2XJZxpyn9d7Ev91epYW11E5qG4xNI3m3AmtEGjMTGjfMUstNK0V1Nt
+YXJ0bW9udG9vbHMgU2lnbmluZyBLZXkgKHRocm91Z2ggMjAxOCkgPHNtYXJ0bW9u
+dG9vbHMtZGF0YWJhc2VAbGlzdHMuc291cmNlZm9yZ2UubmV0PokBPgQTAQIAKAIb
+AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AFAl4XiZMFCQ9l7g0ACgkQ84djzt/S
+JVnl5Qf+PVRoLmEpDIqQ+58DMIwz98+yajCJ1vQvEOKjMcgeePOn475eV5Phkvsp
+KtW6TedWhN9l/NcDZzEPCpkhrz24WJDLFV+o16B4MZwSkGTl4/3qijERKsd8M+MS
+tiLr3+eUCFi4dAp0uhPytETvUmtj3ByA0R2luoOK+kEutq6i2x9BPr8Qc55Lqdwt
+SK8pPU05WSaCu1m2oThJhkELVklOQ2cj+D8MrQdJGd3plEb9j5oUbhj7LW/y0i4M
+lqk1rQCQKnY3vTFQBpj1o7T6kLiGqQCOLTX0B6RQ8vt+PEzXPHi0lIdwOrQk5l7h
+utnjwXmWaWEpRjlsuQ5PBrFDsD9N+IkBHAQTAQIABgUCWA5kYwAKCRDfDxpJxKSQ
+Op+/CADTlsgisoXI6b+0oohRaD4ZVl5eBtkvTrxNQf6EF7Z1uPkVOqi1OLWFGyAm
+beLcRmN6c4/DVcaa6GAG7GA+KQwVPRCyC+9Ibsn/+uG6ZFXAez+0eG9NxOfkCnYH
+8ZP8o2VH+9uKJlGGujh9o5r1SNGVifoLGTc8NkWCW+MAKj8dw8WW+wDc80YrdCRr
+SyLrRU9NLTSE4pIJWKcHLwG63xkXHQPPR1lsJgzdAalfEv1TQdIF3sM+GXp4lZ6b
+uahFDiILBh1vj+5C9TdpWZAlqHDYFICa7Rv/MvQa4O9UUl3SlN3sed8zwAmL3Heo
+XE5tBu8iatMaS9e3BmSsVYlhd/q+iQEcBBMBAgAGBQJYDmSWAAoJEC/N7AvTrxqr
+8HsH+QGQuhHYt9Syccd8AF36psyT03mqgbGLMZL8H9ngoa9ZqVMq7O8Aqz23SGTt
+uNuw6EyrcHo7Dy1311GftshI6arsFNJxE2ZNGIfGocRxu9m3Ez+AysWT9sxz/haH
+E+d58NTg+/7R8YWS1q+Tk6m8dA0Xyf3tMBsIJfj0zJvuGMbCLmd93Yw4nk76qtSn
+9UHbnf76UJN5SctAd8+gK3uO6O4XDcZqC06xkWKl193lzcC8sZJBdI15NszC3y/e
+pnILDDMBUNQMBm/XlCYQUetyrJnAVzFGXurtjEXQ/DDnbfy2Z8efoG8rtq7v3fxS
+1TC5jSVOIEqOE4TwzRz1Y/dfqSU=
+=3Lcg
+-----END PGP PUBLIC KEY BLOCK-----
+'
+ }
+
+ '^RELEASE_7_[023]_DRIVEDB$' {
+# Smartmontools Signing Key (through 2025) <smartmontools-database@listi.jpberlin.de>
+# Smartmontools Signing Key (through 2020) <smartmontools-database@listi.jpberlin.de>
+# Key ID 721042C5
+ $key.Value = `
+'-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFwmhpUBEADRoOZaXq13MrqyAmbGe6FlHi6P9ujsT/SJGhTiAoN3W1X56Dbm
+KP21nO9ZAjdXnvA2OmzppfCUX7v5Q3/TG3vN3WwfyQIO/dgSaTrGa1E8odbHEGc7
+rhzYA8ekAn3TmxhOrEUTcRIogumW0zlQewHOlTe0OYsxat6/N8l3Cqn28HwZUpRH
+MrJW3RgefFihQGEhXlnfzo+Tltl14IriURbwBZIDeZOk2AWLGweI0+zqTgYSbF5A
+tI5rXO1QDeoyBYZhSX3MtnncwPdCnxoRasizU5w3KoZWYyKAc5bxJBJgUUp9HDOu
+ATgNqekc8j28x/cUAWerXe183SBYQp0QkzMPbmE9TCGW3GjtW+Kk/NDbNe8ufj6O
+hk0r7EbGyBO0qvgzHLzSsQiSsgaMCkLc5Xt4NzB4g2DvnReFU2WwgRh031lHOVLm
+mvFqRtHzJb20dKufyjOmSMzNKRzURVmobECKARaBlGNP0wHYhq97n4OxM1o0eq7a
+4ugaSp2q+6BSaAQhbZN8ULCF/oGA/376Sz7RNuoOmQwl9aFqnfl3YgopBIqKvnSP
+h4j0QynN45rUFOe/VywTmpWKj+DonGCupxe9VvyZ87NKRgKiHprXGDrhdB0GcNXM
+wV66WbjKBV7qlpSh/GH3oiHwlcYT8LNyZbxTJXcVF5ODtlZfc9zqRtUBWQARAQAB
+tFNTbWFydG1vbnRvb2xzIFNpZ25pbmcgS2V5ICh0aHJvdWdoIDIwMjUpIDxzbWFy
+dG1vbnRvb2xzLWRhdGFiYXNlQGxpc3RpLmpwYmVybGluLmRlPokCQQQTAQIAKwIb
+AwUJDS6amwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AFAl/gnzECGQEACgkQ6nSr
+JXIQQsW11g//UmnWOtIgozoqs6beK12NpZyubn/lecEd0yJPzed9cygKpObySBbT
+5jz7e5IDGwFLDsTm9fE/2GoyvuVW/riyTsowxrYYleoKm4Pmv30crNruVM7mC7c8
++rbwmx5ZlmHC1tMsM/BdIxK0gqHyAXxWmzyB/YDGElkWnq2/+wjEoARbROUoKQYL
+qG6q6bv/DQvv4tq/Yw+fsaLZsR4Cou87hB3wAwR3rv3p3GC7N+if86fbkS8rQh5b
+j3qwTHnf3ugyYz9iEy2pjrHqgnDMV227tP2UiC2ECy3u1Z7eQvMeN2r0x8EIB79D
+G7ny7ML3QXsJG9Pamg4VHlMh+Sb23GE6rRQuv9m265PeS4/6CsbuHdGer+UaG78V
+N4bfFhMWpE4sjDZlQZBcm6VLbExhuS89GI7+9zYMtLoXE6Z5Mz0XFjSKlzEK94UT
+RPcDdcQUHW59NvhG77SvTKN5PHGbcs+0uQkUkvaOxoovio2vWcYANG4eIPC/YvPZ
+9q7f/bhMDbKid7eIvtCgvijSiYKQLjt1FtJJZRYF/EESdWWNJTs2OgSFMgSDBE3K
+Da5alJyx3+IlYFwvF/khtQnGeTB1XRIGL8G7UMaNzpvJQOAEbqEiznyqoo5cNpz+
+03wTOw9IGVJ2fcvg2g+j7ffKQfs+GDYWAqicSKHDYpW2csBAW/1QE62JARwEEwEC
+AAYFAl/gnzoACgkQL83sC9OvGqvE0Af/XXZ4GWMf4rEB0G3lXr9L9bvX4a/tVWz0
+hag57D6By9R6cWNDpRtKx5R0Y1Fv+O+sPHptM3P6LUsWI0d7dEf307n34FxkI/vh
+4W1g8ITvhYfJWmJTzA1kNAief45uNPx0QWhGlVf4nQzhe41XnuBdFhYfOkHGf6k8
+9SJ9qWRitzE657h6mVO0EKqvjTld8w6lR2rA+oHPQnc9iDmXcZLfSTHP/NapQXPl
+qtXiR1z0BkswBBaKCnJxVPpzjQA0W8jSyhQ4qPheMjOmVaFoQxZ4CbEaFI67EmVl
+kwgwf+c6BlKr3DoOca/KmHYT/9dqUv1gfoYYTCm+ATN76vYCG794EokCHAQTAQIA
+BgUCX+CfRQAKCRC/CzOcZLyqiwQWD/9eNQNnKWxkYL3qjSRt0DwUUaCcFDoj40rb
+fRxWdU+LZKL7KjAWoRhdfaH7T30wZ9NFenrQXaU/QzuYioz1sHRwIIRYyUp2s0Jc
+VHAIuOPjk6Q3TDVnbEm0AO0Er32gdxC0DYk4RfGp95n1Aw1kd2BSvKPJuZSRJrIV
+f8iU3Im1KT4Avl7Fw7FEojQMMvn/qZzeo2pk/QdrrK3KnHkQwy2edx/szY82o2a5
+g5WarFFRcxVS2H/xrvNMGUL4TsWcGd3Z2oHoZ0u5A20/PpT2xG1LGXGEwBAqtMS2
+6iRAzbQFkkLhcdETTvOSqkDWkzr7NqJ6adhLOEVXsHXNLx23p1Tn+Li/ezpQ6/eQ
+QDPclU19BjARmfInDq0w5V1q0RNET1J2Xu+Adxtq+Dl8TyhCmJMzO8e4htYnIRZu
+90iSgZdt5cZgoH04weXCMwDugn/+Q3rzKvRUTrEfSOivJYg65D/mhbz6HoUTs4JD
+SstTYa9qNCwKQGRSeis4PAgu0hCpnDAhZuN3Ja5AFC2Wi2szQ7R+Zx/JucIBm5S4
+U30W66MtsyUHeulSJ3AV3HrbFfnqu6zfQM4XLw7MpAtQUNJceS/lWfGIquAp3tY/
+IjZIHwgZqKB3czWDhM83wBzCWgAmxyzIrpb4MBYJ5PGuCyC7R/YTdtPJXxsPQl2l
+znsX/9ssa4kBHAQTAQIABgUCX+CfSAAKCRDzh2PO39IlWVcuB/9UkLaPtGY4sDDV
+/A7qjSvSy93mv8gkaIj9dhqoZw+r7cLiEtX04Cz9PqocOFgCYJXKrufHNNkHke2A
+jE9EJfRKiPU/bkeWmrACvtrOd/DZbdmXfxTOekOr516D2ip/U8GBPw6zxfCQVot6
+htpBpB6zzMDtzMOeLnkOxoxR4EMu5K6eJ48bHvG/lbGBByyfRzhtqPh6AAA9G1CC
+IdhNkaA5W1qums3N1mCXrTBnWyjaFhdnttGQfrMdHvTQ77HeL0c2axT2y5PYfrXY
+2ZfZowYLEtFXRSTpDaJfgG+qem3N+pMv6SMOG/4CvlH4/3Hq0aCNvKcY5KUXfIgT
+xmc3/n/wtFNTbWFydG1vbnRvb2xzIFNpZ25pbmcgS2V5ICh0aHJvdWdoIDIwMjAp
+IDxzbWFydG1vbnRvb2xzLWRhdGFiYXNlQGxpc3RpLmpwYmVybGluLmRlPokCPgQT
+AQIAKAIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AFAl/gnzAFCQ0umpsACgkQ
+6nSrJXIQQsVK7RAAqbZfT3wZEfJkw8MK2JlvgGWH76fHKn5ZoH5i0mA4AvN4QLbU
+5Q20HmqHnO9mfAZQ6u4Tn/aFcYT7nlSsEsEmFX+s5QU2y6m2Tx9ThDbZ03ezREOS
+0wNf0FOQunV9ZVPT/7cKIgWJa5mZy+LClor9OHllyGUfs9tKNzwxaHh1zBrCNJow
+Fi/1bkWy3iMc7vZhWHASwPSp64KHjB4UdMz2hV4pROiUhWi7BY0exIHyZrkcANMP
+Hhl9lP32ZvNbOy8osBdPUgXyK3HePD+ftcwJMkoc4mFQXYi9UY7NQpk7STRO10cx
+Kq/CgDDvYxbnViRjQoJ0sfwKCaOfsnY/gea7I0aCx8uNISYpHO9iMidd/tJ7+lgx
+NiKZTI0EppHYvkyMY15/NGb0gTJbYjuVYdbqDS9mnLuLQAjAX43+n9ND2NjX1o0q
+Z9bBqV2VFioNmnxKqGphhRFX9jEzTklieOjhpRrd8v9ljprT6vLFNpYpeLkel8om
+VFXrHxrfzKtVFcto5wqHVOcyZyE2zm1QmsS8qvWOTrNfY6p2q9MA2rysqdfgfvN7
+pNDaXutK6ooQi6YlyyTA2ANnHFKa0ncRH+dg+5OF9rhNvM7RyaBXgxF7+5gnU5Gb
+VQRKbJ+LOtSKkj0pApR5AKSwyGslZ2bNVlKsADWhk5xj8QlHVlNWiht+i/6JARwE
+EwECAAYFAlwmhpwACgkQL83sC9OvGqsVOggAqLB5eQrUv8E9ikD6kJCito827bzD
+WF29yD7PvfhjXaz5in54jOVpwg3o9CsqIjjRW0/1bBVswC8ZL0sAdZ+GDSDMw5F2
+IpkD77gjnFY79M/e6C9xYyxYzHC7emDPSz9IroOvdkkEgrB+OABKkaOCcS18P4Lk
+3WNHaPw5c7aI0z1iJP52EmSfvB8r86mtUFJB+f15eD/4vaRfkZLFjF9FQ3kgEK1U
++rV4s1O2bCFfP3WPDcc83NgwRUvtXmcSOSOIoXnemJzyJr+JnqCWVET4XWF6i20m
+RFXVEpWtf5AkJYgR3z/jW0djELbBWA/35bAnpXy5pDHv9NbZsTkBZxK/kokBHAQT
+AQIABgUCXCaGnQAKCRAY7NpGy/a6xn4lB/90tXTnZsgmoftol9uivfQrPdR88WmO
+ZLYmUeQAd1rqSFMxe+KzO/qLuU8s6OF4nznwL2cPfbGZxezM4PiYmAmbbEU/3gTO
+NwjVBBA0Gfimy/fITEezFtCigo1thkaJ195g/dqY+zE3Vt4rzC03j1vx8mUHRPU6
+kkvKj8cP0j+XHX2xQDsTXTstfnom29wBmGnvSZ9HgcdL71e1VXJXwikmnO3P4J/1
+C2LeCOlWrGqWZ2c0WBLKdJnsYUx7Dm/OvkkB4lF+zWp98zS8jS/5h+1apVgEzrdT
+MvT8ydTkUr7ObKGkIhK+L+Xo5BD+V9Qf6xKGYPwhhdj/E5/kyjULrm10iQEcBBMB
+AgAGBQJcJoadAAoJEPOHY87f0iVZfiUH/3yKS5wGvTeRInse8+W1WzKuto3XzqXL
+ngb9QXWw7nCwqmNS7PbzDnufQi2ThKrMfcK14WgNYABNZPU75I+6bcb0oCB5tloo
+IUEV/2Ut/5Hl/83zFFoNA/kQKVz8kIDqgRcxC+zY2VJ4eTKHyQDvXygVk8wnKTBa
+e3gX+CIZqJHPXiiygHlbl31Mi3G1Iaxu57dP6ocV0vX1dytKSwd4Rbviwwb4L76o
+/tVT9t3GwFM15uK1SqtnAaiaktEdMi3XI4d01H3VUVz/iR0XQbf13RZoEM6CJWms
+Q/qvYlwkbKOdlahjoHrFlkhADSBaO9N1OZp3OYDjziIujMdt2IPKnmM=
+=7MQk
+-----END PGP PUBLIC KEY BLOCK-----
+'
+ }
+
+ default {
+ error "No known public key for branches/${branch}"
+ }
+ }
+}
+
+function run_join_out_err($cmd) # $arg1, $arg2, ...
+{
+ $cmdobj = Get-Command -CommandType Application -Name $cmd
+ # Don't prepend $input with BOM
+ $enc = [Console]::InputEncoding
+ [Console]::InputEncoding = [System.Text.UTF8Encoding]::new()
+ $ErrorActionPreference = "Continue" # Don't abort command on first stderr output
+ $LASTEXITCODE = 42
+ # Run command and convert ErrorRecords from stderr to plain Strings
+ $($input | & $cmdobj @args 2>&1) | %{ $_.ToString() }
+ $ErrorActionPreference = "Stop"
+ [Console]::InputEncoding = $enc
+}
+
+function gpg_verify($ascfile, $file, [ref]$ok)
+{
+ # Create temp home dir
+ if (!$env:TEMP) { error "Environment variable TEMP is not set" }
+ $gnupgtmp = Join-Path $env:TEMP "gnupg.$(New-Guid)"
+ rm_rf $gnupgtmp
+ New-Item -Type Directory -Path $gnupgtmp | Out-Null
+
+ # Import public key
+ $env:LC_MESSAGES = "C"
+ $out = ($public_key | run_join_out_err $gpg "--batch" "--no-tty" "--homedir=$gnupgtmp" "--import")
+ if ($LASTEXITCODE -ne 0) {
+ echo $out
+ exit 1
+ }
+ vecho $out
+
+ # Verify
+ $out = run_join_out_err $gpg "--batch" "--no-tty" "--homedir=$gnupgtmp" "--verify" $ascfile $file
+ if ($LASTEXITCODE -eq 0) {
+ vecho $out
+ $ok.Value = $true
+ }
+ else {
+ # print gpg error always
+ echo $out
+ $ok.Value = $false
+ }
+
+ # Stop the gpg-agent possibly started by gpg
+ if ($gpgconf) {
+ $out = run_join_out_err $gpgconf "--homedir=$gnupgtmp" "--kill" "gpg-agent"
+ if ($LASTEXITCODE -ne 0) {
+ echo $out
+ }
+ }
+
+ # Remove temp home dir
+ try {
+ rm_rf "$gnupgtmp"
+ } catch {
+ warning "$_"
+ }
+}
+
+function get_db_version($file)
+{
+ $x = (Select-String -CaseSensitive -Pattern '^[ {]*"VERSION: *[^"]*"' -Path $file).Line `
+ -replace '^[ {]*"VERSION: ([1-9][./0-9]* [^"]*)".*$','$1'
+ $v = $x -replace ' .*$',''
+ if ($v -match '^[1-9][.0-9]*$') { # trunk: get rev from expanded SVN-Id
+ $r = $x -replace '^[^$]*\$Id: drivedb\.h ([1-9][0-9]*) .*$','$1'
+ if (!($r -match '^[1-9][0-9]*$')) {
+ $r = "?"
+ }
+ $v = "$v/$r"
+ } elseif (!($v -match '^[1-9][./0-9]*$')) {
+ return ""
+ }
+ return $v
+}
+
+function mv_all($prefix, $old, $new)
+{
+ mv_f "${prefix}${old}" "${prefix}${new}"
+ mv_f "${prefix}${old}.raw" "${prefix}${new}.raw"
+ if (test_f "${prefix}${old}.raw.asc") {
+ mv_f "${prefix}${old}.raw.asc" "${prefix}${new}.raw.asc"
+ } else {
+ rm_f "${prefix}${new}.raw.asc"
+ }
+}
+
+# Set defaults
+if (!$Smartctl) {
+ $Smartctl = $default_smartctl
+}
+
+if ($Branch) {
+ $brname = $Branch -replace '^([567])\.([0-9][0-9]*)$','RELEASE_$1_$2_DRIVEDB'
+ if ($brname -eq $Branch) {
+ error "invalid branch version '${Branch}'"
+ }
+} else {
+ $brname = $default_branch
+}
+
+if (!$Drivedb) {
+ $Drivedb = $default_drivedb
+}
+
+if ($ExportKey) {
+ $key = ""
+ selectkey $brname ([ref]$key)
+ echo $key
+ exit 0
+}
+
+# Check selected source
+if (!$Url -and !$File) {
+ if (!$UrlOf) {
+ $UrlOf = "svn"
+ }
+ $Url = ""
+ selecturl $UrlOf ([ref]$Url)
+ if (!$Trunk) {
+ $Url = $Url -replace "/trunk/","/branches/$brname/"
+ $Url = $Url -replace "/master/","/origin/$brname/"
+ } elseif (!$NoVerify) {
+ error "'-Trunk' requires '-NoVerify'"
+ }
+} elseif (!$UrlOf -and $Url -and !$File) {
+ if (!($Url -match '^[a-z][a-z0-9]*:[^ ][^ ]*$')) {
+ error "${Url}: Invalid URL"
+ }
+} elseif (!$UrlOf -and !$Url -and $File) {
+} else {
+ error "only one of '-UrlOf', '-Url', '-File' is allowed"
+}
+
+# Determine path of signature file
+$FileAsc = ""
+$UrlAsc = ""
+if (!$NoVerify) {
+ if (!$Url) {
+ $FileAsc = "${File}.raw.asc"
+ } elseif ($Url -match '\?') {
+ $UrlAsc = $Url -replace '\?','.raw.asc?'
+ } else {
+ $UrlAsc = "${Url}.raw.asc"
+ }
+}
+
+# Check option compatibility
+if ($UrlOf -eq "svni") {
+ if ($Insecure) {
+ $Insecure = $false
+ }
+ else {
+ error "'-UrlOf svni' requires '-Insecure'"
+ }
+}
+
+# Check for smartctl
+if ($Smartctl -ne "-") {
+ if (!(Get-Command -Type Application -Name $Smartctl -ErrorAction Ignore)) {
+ error "${Smartctl}: not found ('-Smartctl -' to ignore)"
+ }
+}
+
+# Check for GnuPG
+$gpgconf = ""
+if (!$NoVerify) {
+ $gpgobj = $null
+ if ($gpg) {
+ $gpgobj = Get-Command -Type Application -Name $gpg -ErrorAction Ignore
+ }
+ if (!$gpgobj) {
+ error "GnuPG is not available ('-NoVerify' to ignore)"
+ }
+ $public_key = ""
+ selectkey $brname ([ref]$public_key)
+ # Check for gpgconf in same directory
+ $gpgconf = "gpgconf"
+ $gpgconfobj = Get-Command -Type Application -Name $gpgconf -ErrorAction Ignore;
+ if (!($gpgconfobj -and ((Split-Path $gpgobj.Source) -eq (Split-Path $gpgconfobj.Source)))) {
+ $gpgconf = ""
+ }
+}
+
+# Remove possible garbage from last download
+if (!$DryRun) {
+ rm_f "${Drivedb}.new" "${Drivedb}.new.raw" "${Drivedb}.new.raw.asc"
+}
+
+if ($Url) {
+ # Download
+ vecho "Download drivedb.h"
+ $errmsg = ""
+ download $Url "${Drivedb}.new" ([ref]$errmsg)
+ if ($errmsg) {
+ rm_f "${Drivedb}.new"
+ error "drivedb.h: download failed: $errmsg"
+ }
+
+ if ($UrlAsc) {
+ vecho "Download drivedb.h.raw.asc"
+ download $UrlAsc "${Drivedb}.new.raw.asc" ([ref]$errmsg)
+ if ($errmsg) {
+ rm_f "${Drivedb}.new" "${Drivedb}.new.raw.asc"
+ error "drivedb.h.raw.asc: download failed: $errmsg ('-NoVerify' to ignore)"
+ }
+ }
+} else {
+ # Copy from local file
+ if (!(test_f $File)) {
+ error "${File}: file not found"
+ }
+ if ($FileAsc -and !(test_f $FileAsc)) {
+ error "${FileAsc}: file not found ('-NoVerify' to ignore)"
+ }
+
+ vcopy $File "${Drivedb}.new"
+ if ($FileAsc) {
+ vcopy $FileAsc "${Drivedb}.new.raw.asc"
+ }
+}
+
+if ($DryRun) {
+ exit 0
+}
+
+# Check files and adjust timestamps
+$errmsg = check_file "${Drivedb}.new" '/' 10000 1000000
+if ($errmsg) {
+ rm_f "${Drivedb}.new.raw.asc"
+ mv_f "${Drivedb}.new" "${Drivedb}.error"
+ error "${Drivedb}.error: $errmsg"
+}
+touch "${Drivedb}.new"
+
+if (test_f "${Drivedb}.new.raw.asc") {
+ $errmsg = check_file "${Drivedb}.new.raw.asc" '-' 200 2000
+ if ($errmsg) {
+ rm_f "${Drivedb}.new"
+ mv_f "${Drivedb}.new.raw.asc" "${Drivedb}.error.raw.asc"
+ error "${Drivedb}.error.raw.asc: $errmsg"
+ }
+ touch "${Drivedb}.new.raw.asc"
+}
+
+# Create raw file with unexpanded SVN Id
+# (This assumes newlines are LF and not CR/LF)
+unexpand_svn_id "${Drivedb}.new" "${Drivedb}.new.raw"
+
+# Check whether installed file is identical
+$equal = $false
+if (test_f $Drivedb) {
+ if (!(test_f "${Drivedb}.raw")) {
+ # Create missing raw file
+ unexpand_svn_id "${Drivedb}" "${Drivedb}.raw"
+ }
+ # Ignore missing Id keyword expansion in new file
+ if ( (cmp "${Drivedb}.raw" "${Drivedb}.new.raw") `
+ -and ( (cmp "${Drivedb}" "${Drivedb}.new") `
+ -or (cmp "${Drivedb}.raw" "${Drivedb}.new"))) {
+ $equal = $true
+ }
+}
+
+if (!$NoVerify) {
+ # Verify raw file
+ $ok = $false
+ gpg_verify "${Drivedb}.new.raw.asc" "${Drivedb}.new.raw" ([ref]$ok)
+ if (!$ok) {
+ mv_all $Drivedb ".new" ".error"
+ if ($equal) {
+ warning "${Drivedb}: *** installed file is identical to broken new file ***"
+ }
+ error "${Drivedb}.error.raw: *** BAD signature or outdated key ***"
+ }
+}
+
+# Get version
+$newver = get_db_version "${Drivedb}.new"
+if (!$newver) {
+ if (!$Force) {
+ mv_all $Drivedb ".new" ".error"
+ error "${Drivedb}.error: no VERSION information found ('-Force' to ignore)"
+ }
+ $newver = "?/?"
+} elseif ($newver -match '/\?$') {
+ if (!$Trunk) {
+ mv_all $Drivedb ".new" ".error"
+ error "${Drivedb}.error: VERSION information is incomplete ('-Trunk' to ignore)"
+ }
+}
+
+if ($Smartctl -ne "-") {
+ # Check syntax
+ run_join_out_err $Smartctl -B "${Drivedb}.new" -P showall | Out-Null
+ if (!$?) {
+ mv_all $Drivedb ".new" ".error"
+ error "${Drivedb}.error: rejected by $Smartctl, probably no longer compatible"
+ }
+ vecho "${Smartctl}: syntax OK"
+}
+
+# Always install if missing
+rm_f "${Drivedb}.lastcheck"
+if (!(Test-Path -PathType Leaf -Path $Drivedb)) {
+ mv_all $Drivedb ".new" ""
+ iecho "$Drivedb $newver newly installed$(if ($NoVerify) {" (NOT VERIFIED)"})"
+ exit 0
+}
+
+# Keep old file if identical
+if ($equal) {
+ if (test_f "${Drivedb}.new.raw.asc") {
+ if (!(cmp "${Drivedb}.new.raw.asc" "${Drivedb}.raw.asc")) {
+ mv_f "${Drivedb}.new.raw.asc" "${Drivedb}.raw.asc"
+ iecho "${Drivedb}.raw.asc $newver updated"
+ }
+ }
+ rm_f "${Drivedb}.new" "${Drivedb}.new.raw" "${Drivedb}.new.raw.asc"
+ touch "${Drivedb}.lastcheck"
+ iecho "$Drivedb $newver is already up to date$(if ($NoVerify) {" (NOT VERIFIED)"})"
+ exit 0
+}
+
+# Check branch and file version
+$oldver = $(get_db_version "${Drivedb}")
+if (!$oldver) {
+ $oldver = "?/?"
+}
+
+if ( ($newver -match '/\?$') `
+ -or ($oldver -match '/\?$') `
+ -or (($newver -replace '/.*$','') -ne ($oldver -replace '/.*$',''))) {
+ # Always install from trunk or other branch
+ $updmsg = "replaced with"
+} elseif ((($newver -replace '^.*/','') - ($oldver -replace '^.*/','')) -lt 0) {
+ # Install older file only if '-Force' is used
+ if (!$Force) {
+ rm_f "${Drivedb}.new" "${Drivedb}.new.raw" "${Drivedb}.new.raw.asc"
+ iecho "$Drivedb $oldver not downgraded to $newver ('-Force' to override)"
+ exit 0
+ }
+ $updmsg = "downgraded to"
+} else {
+ $updmsg = "updated to"
+}
+
+mv_all $Drivedb "" ".old"
+mv_all $Drivedb ".new" ""
+iecho "$Drivedb $oldver $updmsg $newver$(if ($NoVerify) {" (NOT VERIFIED)"})"
+exit 0
diff --git a/os_win32/versioninfo.rc.in b/os_win32/versioninfo.rc.in
new file mode 100644
index 0000000..99388e9
--- /dev/null
+++ b/os_win32/versioninfo.rc.in
@@ -0,0 +1,34 @@
+//
+// os_win32/versioninfo.rc.in
+//
+// $Id: versioninfo.rc.in 4519 2017-10-08 15:41:54Z chrfranke $
+//
+
+1 VERSIONINFO
+ FILEVERSION @BINARY_VERSION@
+ PRODUCTVERSION @BINARY_VERSION@
+ FILEFLAGSMASK 0x0
+ FILEFLAGS 0x0
+ FILEOS 0x4 // VOS__WINDOWS32
+ FILETYPE 0x1 // VFT_APP
+ FILESUBTYPE 0x0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "04090000"
+ BEGIN
+ VALUE "CompanyName", "www.smartmontools.org"
+ VALUE "FileDescription", "@DESC@"
+ VALUE "FileVersion", "@TEXT_VERSION@"
+ VALUE "InternalName", "@NAME@"
+ VALUE "LegalCopyright", "(C) 2002-@YY@, Bruce Allen, Christian Franke, www.smartmontools.org"
+ VALUE "OriginalFilename", "@NAME@.exe"
+ VALUE "ProductName", "smartmontools"
+ VALUE "ProductVersion", "@TEXT_VERSION@"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 0x0000
+ END
+END
diff --git a/os_win32/wmiquery.cpp b/os_win32/wmiquery.cpp
new file mode 100644
index 0000000..929b22a
--- /dev/null
+++ b/os_win32/wmiquery.cpp
@@ -0,0 +1,190 @@
+/*
+ * os_win32/wmiquery.cpp
+ *
+ * Home page of code is: http://www.smartmontools.org
+ *
+ * Copyright (C) 2011-13 Christian Franke
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WINVER 0x0400
+#define _WIN32_WINNT WINVER
+
+#include "wmiquery.h"
+
+#include <stdio.h>
+
+const char * wmiquery_cpp_cvsid = "$Id: wmiquery.cpp 4760 2018-08-19 18:45:53Z chrfranke $"
+ WMIQUERY_H_CVSID;
+
+
+/////////////////////////////////////////////////////////////////////////////
+// com_bstr
+
+com_bstr::com_bstr(const char * str)
+: m_bstr(0)
+{
+ int sz = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, (LPWSTR)0, 0);
+ if (sz <= 0)
+ return;
+ m_bstr = SysAllocStringLen((OLECHAR*)0, sz-1);
+ if (!m_bstr)
+ return; // throw std::bad_alloc
+ MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, m_bstr, sz);
+}
+
+bool com_bstr::to_str(const BSTR & bstr, std::string & str)
+{
+ if (!bstr)
+ return false;
+ int sz = WideCharToMultiByte(CP_ACP, 0, bstr, -1, (LPSTR)0, 0, (LPCSTR)0, (LPBOOL)0);
+ if (sz <= 0)
+ return false;
+ char * buf = new char[sz];
+ WideCharToMultiByte(CP_ACP, 0, bstr, -1, buf, sz, (LPCSTR)0, (LPBOOL)0);
+ str = buf;
+ delete [] buf;
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wbem_object
+
+std::string wbem_object::get_str(const char * name) /*const*/
+{
+ std::string s;
+ if (!m_intf)
+ return s;
+
+ VARIANT var; VariantInit(&var);
+ if (m_intf->Get(com_bstr(name), 0L, &var, (CIMTYPE*)0, (LPLONG)0) /* != WBEM_S_NO_ERROR */)
+ return s;
+
+ if (var.vt == VT_BSTR)
+ com_bstr::to_str(var.bstrVal, s);
+ VariantClear(&var);
+ return s;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wbem_enumerator
+
+bool wbem_enumerator::next(wbem_object & obj)
+{
+ if (!m_intf)
+ return false;
+
+ ULONG n = 0;
+ HRESULT rc = m_intf->Next(5000 /*5s*/, 1 /*count*/, obj.m_intf.replace(), &n);
+ if (FAILED(rc) || n != 1)
+ return false;
+ return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wbem_services
+
+const CLSID xCLSID_WbemLocator = {0x4590f811, 0x1d3a, 0x11d0, {0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24}};
+const IID xIID_IWbemLocator = {0xdc12a687, 0x737f, 0x11cf, {0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24}};
+
+bool wbem_services::connect()
+{
+ // Init COM during first call.
+ static HRESULT init_rc = -1;
+ static bool init_tried = false;
+ if (!init_tried) {
+ init_tried = true;
+ init_rc = CoInitialize((LPVOID)0);
+ }
+ if (!(init_rc == S_OK || init_rc == S_FALSE))
+ return false;
+
+ /// Create locator.
+ com_intf_ptr<IWbemLocator> locator;
+ HRESULT rc = CoCreateInstance(xCLSID_WbemLocator, (LPUNKNOWN)0,
+ CLSCTX_INPROC_SERVER, xIID_IWbemLocator, (LPVOID*)locator.replace());
+ if (FAILED(rc))
+ return false;
+
+ // Set timeout flag if supported.
+ long flags = 0;
+ OSVERSIONINFOA ver; ver.dwOSVersionInfoSize = sizeof(ver);
+ if (GetVersionExA(&ver) && ver.dwPlatformId == VER_PLATFORM_WIN32_NT
+ && ( ver.dwMajorVersion >= 6 // Vista
+ || (ver.dwMajorVersion == 5 && ver.dwMinorVersion >= 1))) // XP
+ flags = WBEM_FLAG_CONNECT_USE_MAX_WAIT; // return in 2min or less
+
+ // Connect to local server.
+ rc = locator->ConnectServer(com_bstr("\\\\.\\root\\cimv2"),
+ (BSTR)0, (BSTR)0, (BSTR)0, // User, Password, Locale
+ flags, (BSTR)0, (IWbemContext*)0, m_intf.replace());
+ if (FAILED(rc))
+ return false;
+
+ // Set authentication information,
+ rc = CoSetProxyBlanket(m_intf.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
+ (OLECHAR*)0, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
+ (RPC_AUTH_IDENTITY_HANDLE*)0, EOAC_NONE);
+ if (FAILED(rc)) {
+ m_intf.reset();
+ return false;
+ }
+
+ return true;
+}
+
+bool wbem_services::vquery(wbem_enumerator & result, const char * qstr, va_list args) /*const*/
+{
+ if (!m_intf)
+ return false;
+
+ char qline[1024];
+ vsnprintf(qline, sizeof(qline), qstr, args);
+ qline[sizeof(qline)-1] = 0;
+
+ HRESULT rc = m_intf->ExecQuery(
+ com_bstr("WQL"), com_bstr(qline),
+ WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
+ (IWbemContext*)0, result.m_intf.replace());
+ if (FAILED(rc))
+ return false;
+
+ return true;
+}
+
+bool wbem_services::vquery1(wbem_object & obj, const char * qstr, va_list args) /*const*/
+{
+ wbem_enumerator result;
+ if (!vquery(result, qstr, args))
+ return false;
+
+ if (!result.next(obj))
+ return false;
+
+ wbem_object peek;
+ if (result.next(peek))
+ return false;
+
+ return true;
+}
+
+bool wbem_services::query(wbem_enumerator & result, const char * qstr, ...) /*const*/
+{
+ va_list args; va_start(args, qstr);
+ bool ok = vquery(result, qstr, args);
+ va_end(args);
+ return ok;
+}
+
+bool wbem_services::query1(wbem_object & obj, const char * qstr, ...) /*const*/
+{
+ va_list args; va_start(args, qstr);
+ bool ok = vquery1(obj, qstr, args);
+ va_end(args);
+ return ok;
+}
diff --git a/os_win32/wmiquery.h b/os_win32/wmiquery.h
new file mode 100644
index 0000000..47b6f37
--- /dev/null
+++ b/os_win32/wmiquery.h
@@ -0,0 +1,180 @@
+/*
+ * os_win32/wmiquery.h
+ *
+ * Home page of code is: http://www.smartmontools.org
+ *
+ * Copyright (C) 2011-18 Christian Franke
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef WMIQUERY_H
+#define WMIQUERY_H
+
+#define WMIQUERY_H_CVSID "$Id: wmiquery.h 4760 2018-08-19 18:45:53Z chrfranke $"
+
+#include <wbemcli.h>
+
+#include <string>
+
+#ifndef __GNUC__
+#define __attribute_format_printf(x, y) /**/
+#elif defined(__MINGW32__) && __USE_MINGW_ANSI_STDIO
+// Check format of __mingw_*printf() instead of MSVCRT.DLL:*printf()
+#define __attribute_format_printf(x, y) __attribute__((format (gnu_printf, x, y)))
+#else
+#define __attribute_format_printf(x, y) __attribute__((format (printf, x, y)))
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// com_bstr
+
+/// Wrapper class for COM BSTR
+class com_bstr
+{
+public:
+ /// Construct from string.
+ explicit com_bstr(const char * str);
+
+ /// Destructor frees BSTR.
+ ~com_bstr()
+ { SysFreeString(m_bstr); }
+
+ /// Implicit conversion to BSTR.
+ operator BSTR()
+ { return m_bstr; }
+
+ /// Convert BSTR back to std::string.
+ static bool to_str(const BSTR & bstr, std::string & str);
+
+private:
+ BSTR m_bstr;
+
+ com_bstr(const com_bstr &);
+ void operator=(const com_bstr &);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// com_intf_ptr
+
+/// Wrapper class for COM Interface pointer
+template <class T>
+class com_intf_ptr
+{
+public:
+ /// Construct empty object
+ com_intf_ptr()
+ : m_ptr(0) { }
+
+ /// Destructor releases the interface.
+ ~com_intf_ptr()
+ { reset(); }
+
+ /// Release interface and clear the pointer.
+ void reset()
+ {
+ if (m_ptr) {
+ m_ptr->Release(); m_ptr = 0;
+ }
+ }
+
+ /// Return the pointer.
+ T * get()
+ { return m_ptr; }
+
+ /// Pointer dereferencing.
+ T * operator->()
+ { return m_ptr; }
+
+ /// Return address of pointer for replacement.
+ T * * replace()
+ { reset(); return &m_ptr; }
+
+ /// For (ptr != 0) check.
+ operator bool() const
+ { return !!m_ptr; }
+
+ /// For (ptr == 0) check.
+ bool operator!() const
+ { return !m_ptr; }
+
+private:
+ T * m_ptr;
+
+ com_intf_ptr(const com_intf_ptr &);
+ void operator=(const com_intf_ptr &);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wbem_object
+
+class wbem_enumerator;
+
+/// Wrapper class for IWbemClassObject
+class wbem_object
+{
+public:
+ /// Get string representation.
+ std::string get_str(const char * name) /*const*/;
+
+private:
+ /// Contents is set by wbem_enumerator.
+ friend class wbem_enumerator;
+ com_intf_ptr<IWbemClassObject> m_intf;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wbem_enumerator
+
+class wbem_services;
+
+/// Wrapper class for IEnumWbemClassObject
+class wbem_enumerator
+{
+public:
+ /// Get next object, return false if none or error.
+ bool next(wbem_object & obj);
+
+private:
+ /// Contents is set by wbem_services.
+ friend class wbem_services;
+ com_intf_ptr<IEnumWbemClassObject> m_intf;
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+// wbem_services
+
+/// Wrapper class for IWbemServices
+class wbem_services
+{
+public:
+ /// Connect to service, return false on error.
+ bool connect();
+
+ /// Execute query, get result list.
+ /// Return false on error.
+ bool vquery(wbem_enumerator & result, const char * qstr, va_list args) /*const*/
+ __attribute_format_printf(3, 0);
+
+ /// Execute query, get single result object.
+ /// Return false on error or result size != 1.
+ bool vquery1(wbem_object & obj, const char * qstr, va_list args) /*const*/
+ __attribute_format_printf(3, 0);
+
+ /// Version of vquery() with printf() formatting.
+ bool query(wbem_enumerator & result, const char * qstr, ...) /*const*/
+ __attribute_format_printf(3, 4);
+
+ /// Version of vquery1() with printf() formatting.
+ bool query1(wbem_object & obj, const char * qstr, ...) /*const*/
+ __attribute_format_printf(3, 4);
+
+private:
+ com_intf_ptr<IWbemServices> m_intf;
+};
+
+#endif // WMIQUERY_H
diff --git a/os_win32/wtssendmsg.c b/os_win32/wtssendmsg.c
new file mode 100644
index 0000000..4ee0d3c
--- /dev/null
+++ b/os_win32/wtssendmsg.c
@@ -0,0 +1,179 @@
+/*
+ * WTSSendMessage() command line tool
+ *
+ * Home page of code is: https://www.smartmontools.org
+ *
+ * Copyright (C) 2012-19 Christian Franke
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#define WINVER 0x0501
+#define _WIN32_WINNT WINVER
+
+char svnid[] = "$Id: wtssendmsg.c 4941 2019-08-08 18:56:36Z chrfranke $";
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <wtsapi32.h>
+
+
+static int usage()
+{
+ printf("wtssendmsg $Revision: 4941 $ - Display a message box on client desktops\n"
+ "Copyright (C) 2012-19 Christian Franke, www.smartmontools.org\n\n"
+ "Usage: wtssendmsg [-cas] [-t TIMEOUT] [-w 0..5] [-v] [\"Caption\"] \"Message\"|-\n"
+ " wtssendmsg -v\n\n"
+ " -c Console session [default]\n"
+ " -a Active sessions\n"
+ " -s Connected sessions\n"
+ " -t Remove message box after TIMEOUT seconds\n"
+ " -w Select buttons and wait for response or timeout\n"
+ " -v List sessions\n"
+ );
+ return 1;
+}
+
+static int getnum(const char * s)
+{
+ char * endp;
+ int n = strtol(s, &endp, 10);
+ if (*endp)
+ return -1;
+ return n;
+}
+
+int main(int argc, const char **argv)
+{
+ int mode = 0, timeout = 0, buttons = -1, verbose = 0, i;
+
+ for (i = 1; i < argc && argv[i][0] == '-' && argv[i][1]; i++) {
+ int j;
+ for (j = 1; argv[i][j]; j++) {
+ switch (argv[i][j]) {
+ case 'c': mode = 0; continue;
+ case 'a': mode = 1; continue;
+ case 's': mode = 2; continue;
+ case 't':
+ if (argv[i][j+1] || ++i >= argc)
+ return usage();
+ timeout = getnum(argv[i]);
+ if (timeout < 0)
+ return usage();
+ break;
+ case 'w':
+ if (argv[i][j+1] || ++i >= argc)
+ return usage();
+ buttons = getnum(argv[i]);
+ if (!(MB_OK <= buttons && buttons <= MB_RETRYCANCEL)) // 0..5
+ return usage();
+ break;
+ case 'v': verbose = 1; continue;
+ default: return usage();
+ }
+ break;
+ }
+ }
+
+ const char * message = 0, * caption = "";
+ char msgbuf[1024];
+ if (i < argc) {
+ if (i+1 < argc)
+ caption = argv[i++];
+
+ message = argv[i++];
+ if (i < argc)
+ return usage();
+
+ if (!strcmp(message, "-")) {
+ // Read message from stdin
+ // The message is also written to a Windows event log entry, so
+ // don't convert '\r\n' to '\n' (the MessageBox works with both)
+ i = 0;
+ DWORD size = 0;
+ do
+ if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE),
+ msgbuf+i, sizeof(msgbuf)-1-i, &size, (OVERLAPPED*)0))
+ break; // May fail with ERROR_BROKEN_PIPE instead of EOF
+ while (size > 0 && (i += size) < (int)sizeof(msgbuf)-1);
+ msgbuf[i] = 0;
+ message = msgbuf;
+ }
+ }
+ else {
+ if (!verbose)
+ return usage();
+ }
+
+ // Get session list
+ WTS_SESSION_INFOA * sessions; DWORD count;
+ if (!WTSEnumerateSessionsA(WTS_CURRENT_SERVER_HANDLE, 0, 1, &sessions, &count)) {
+ fprintf(stderr, "WTSEnumerateSessions() failed\n");
+ return 1;
+ }
+
+ int status = 0;
+ for (i = 0; i < (int)count; i++) {
+
+ if (verbose) {
+ printf("Session %d (\"%s\", State=%d)%s",
+ i, sessions[i].pWinStationName, sessions[i].State,
+ (!message ? "\n" : ": "));
+ if (!message)
+ continue; // List sessions only
+ fflush(stdout);
+ }
+
+ // Check session state
+ if (!( !strcmpi(sessions[i].pWinStationName, "Console")
+ || (mode >= 1 && sessions[i].State == WTSActive)
+ || (mode >= 2 && sessions[i].State == WTSConnected))) {
+ if (verbose)
+ printf("ignored\n");
+ continue;
+ }
+
+ // Send Message
+ DWORD response = ~0;
+ if (!WTSSendMessageA(WTS_CURRENT_SERVER_HANDLE, sessions[i].SessionId,
+ (char *)caption, strlen(caption),
+ (char *)message, strlen(message),
+ (buttons <= MB_OK ? MB_OK|MB_ICONEXCLAMATION
+ : buttons|MB_DEFBUTTON2|MB_ICONQUESTION ),
+ timeout, &response, (buttons >= MB_OK) /*Wait?*/ )) {
+ status |= 0x01;
+ if (verbose)
+ printf("WTSSendMessage() failed with error=%d\n", (int)GetLastError());
+ else
+ fprintf(stderr, "Session %d (\"%s\", State=%d): WTSSendMessage() failed with error=%d\n",
+ i, sessions[i].pWinStationName, sessions[i].State, (int)GetLastError());
+ continue;
+ }
+
+ if (buttons >= MB_OK) {
+ switch (response) {
+ case IDOK:
+ case IDYES: case IDABORT: status |= 0x02; break;
+ case IDNO: case IDRETRY: status |= 0x04; break;
+ case IDCANCEL: case IDIGNORE: status |= 0x08; break;
+ case IDTIMEOUT: status |= 0x10; break;
+ default: status |= 0x01; break;
+ }
+ if (verbose)
+ printf("response = %d, status = 0x%02x\n", (int)response, status);
+ }
+ else {
+ // response == IDASYNC
+ if (verbose)
+ printf("message sent\n");
+ }
+ }
+
+ WTSFreeMemory(sessions);
+
+ return status;
+}