// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // Copyright (C) LibreHardwareMonitor and Contributors. // Partial Copyright (C) Michael Möller and Contributors. // All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using LibreHardwareMonitor.Interop; namespace LibreHardwareMonitor.Hardware.Storage; internal class WindowsSmart : ISmart { private readonly int _driveNumber; private readonly SafeHandle _handle; public WindowsSmart(int driveNumber) { _driveNumber = driveNumber; _handle = Kernel32.CreateFile(@"\\.\PhysicalDrive" + driveNumber, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); } public bool IsValid => !_handle.IsInvalid && !_handle.IsClosed; public void Dispose() { Close(); } public void Close() { Dispose(true); GC.SuppressFinalize(this); } public bool EnableSmart() { if (_handle.IsClosed) throw new ObjectDisposedException(nameof(WindowsSmart)); var parameter = new Kernel32.SENDCMDINPARAMS { bDriveNumber = (byte)_driveNumber, irDriveRegs = { bFeaturesReg = Kernel32.SMART_FEATURES.ENABLE_SMART, bCylLowReg = Kernel32.SMART_LBA_MID, bCylHighReg = Kernel32.SMART_LBA_HI, bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART} }; return Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_SEND_DRIVE_COMMAND, ref parameter, Marshal.SizeOf(parameter), out Kernel32.SENDCMDOUTPARAMS _, Marshal.SizeOf(), out _, IntPtr.Zero); } public Kernel32.SMART_ATTRIBUTE[] ReadSmartData() { if (_handle.IsClosed) throw new ObjectDisposedException(nameof(WindowsSmart)); var parameter = new Kernel32.SENDCMDINPARAMS { bDriveNumber = (byte)_driveNumber, irDriveRegs = { bFeaturesReg = Kernel32.SMART_FEATURES.SMART_READ_DATA, bCylLowReg = Kernel32.SMART_LBA_MID, bCylHighReg = Kernel32.SMART_LBA_HI, bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART } }; bool isValid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_RECEIVE_DRIVE_DATA, ref parameter, Marshal.SizeOf(parameter), out Kernel32.ATTRIBUTECMDOUTPARAMS result, Marshal.SizeOf(), out _, IntPtr.Zero); return isValid ? result.Attributes : Array.Empty(); } public Kernel32.SMART_THRESHOLD[] ReadSmartThresholds() { if (_handle.IsClosed) throw new ObjectDisposedException(nameof(WindowsSmart)); var parameter = new Kernel32.SENDCMDINPARAMS { bDriveNumber = (byte)_driveNumber, irDriveRegs = { bFeaturesReg = Kernel32.SMART_FEATURES.READ_THRESHOLDS, bCylLowReg = Kernel32.SMART_LBA_MID, bCylHighReg = Kernel32.SMART_LBA_HI, bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART } }; bool isValid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_RECEIVE_DRIVE_DATA, ref parameter, Marshal.SizeOf(parameter), out Kernel32.THRESHOLDCMDOUTPARAMS result, Marshal.SizeOf(), out _, IntPtr.Zero); return isValid ? result.Thresholds : Array.Empty(); } public bool ReadNameAndFirmwareRevision(out string name, out string firmwareRevision) { if (_handle.IsClosed) throw new ObjectDisposedException(nameof(WindowsSmart)); var parameter = new Kernel32.SENDCMDINPARAMS { bDriveNumber = (byte)_driveNumber, irDriveRegs = { bCommandReg = Kernel32.ATA_COMMAND.ATA_IDENTIFY_DEVICE } }; bool valid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_RECEIVE_DRIVE_DATA, ref parameter, Marshal.SizeOf(parameter), out Kernel32.IDENTIFYCMDOUTPARAMS result, Marshal.SizeOf(), out _, IntPtr.Zero); if (!valid) { name = null; firmwareRevision = null; return false; } name = GetString(result.Identify.ModelNumber); firmwareRevision = GetString(result.Identify.FirmwareRevision); return true; } /// /// Reads Smart health status of the drive /// /// True, if drive is healthy; False, if unhealthy; Null, if it cannot be read public bool? ReadSmartHealth() { if (_handle.IsClosed) throw new ObjectDisposedException(nameof(WindowsSmart)); var parameter = new Kernel32.SENDCMDINPARAMS { bDriveNumber = (byte)_driveNumber, irDriveRegs = { bFeaturesReg = Kernel32.SMART_FEATURES.RETURN_SMART_STATUS, bCylLowReg = Kernel32.SMART_LBA_MID, bCylHighReg = Kernel32.SMART_LBA_HI, bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART } }; bool isValid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_SEND_DRIVE_COMMAND, ref parameter, Marshal.SizeOf(parameter), out Kernel32.STATUSCMDOUTPARAMS result, Marshal.SizeOf(), out _, IntPtr.Zero); if (!isValid) { return null; } // reference: https://github.com/smartmontools/smartmontools/blob/master/smartmontools/atacmds.cpp if (Kernel32.SMART_LBA_HI == result.irDriveRegs.bCylHighReg && Kernel32.SMART_LBA_MID == result.irDriveRegs.bCylLowReg) { // high and mid registers are unchanged, which means that the drive is healthy return true; } if (Kernel32.SMART_LBA_HI_EXCEEDED == result.irDriveRegs.bCylHighReg && Kernel32.SMART_LBA_MID_EXCEEDED == result.irDriveRegs.bCylLowReg) { // high and mid registers are exceeded, which means that the drive is unhealthy return false; } // response is not clear return null; } protected void Dispose(bool disposing) { if (disposing && !_handle.IsClosed) { _handle.Close(); } } private static string GetString(IReadOnlyList bytes) { char[] chars = new char[bytes.Count]; for (int i = 0; i < bytes.Count; i += 2) { chars[i] = (char)bytes[i + 1]; chars[i + 1] = (char)bytes[i]; } return new string(chars).Trim(' ', '\0'); } }