diff options
Diffstat (limited to '')
-rw-r--r-- | os_win32.cpp | 4857 | ||||
-rw-r--r-- | os_win32/daemon_win32.cpp | 1107 | ||||
-rw-r--r-- | os_win32/daemon_win32.h | 55 | ||||
-rw-r--r-- | os_win32/default.manifest | 27 | ||||
-rw-r--r-- | os_win32/installer.nsi | 946 | ||||
-rw-r--r-- | os_win32/popen.h | 66 | ||||
-rw-r--r-- | os_win32/popen_win32.cpp | 348 | ||||
-rw-r--r-- | os_win32/runcmd.c | 76 | ||||
-rw-r--r-- | os_win32/smartd_mailer.conf.sample.ps1 | 31 | ||||
-rw-r--r-- | os_win32/smartd_mailer.ps1 | 90 | ||||
-rw-r--r-- | os_win32/smartd_warning.cmd | 210 | ||||
-rw-r--r-- | os_win32/syslog.h | 62 | ||||
-rw-r--r-- | os_win32/syslog_win32.cpp | 375 | ||||
-rw-r--r-- | os_win32/syslogevt.mc | 156 | ||||
-rw-r--r-- | os_win32/update-smart-drivedb.ps1.in | 841 | ||||
-rw-r--r-- | os_win32/versioninfo.rc.in | 34 | ||||
-rw-r--r-- | os_win32/wmiquery.cpp | 190 | ||||
-rw-r--r-- | os_win32/wmiquery.h | 180 | ||||
-rw-r--r-- | os_win32/wtssendmsg.c | 179 |
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(), ®s, 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(), ®s, data, datasize); + id_is_cached = (m_port < 0); + break; + case 'a': + rc = ata_pass_through_ioctl(get_fh(), ®s, + (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(), ®s, 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(), ®s, 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, ®Type, (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; +} |