├── HardDiskValidator.png ├── HardDiskValidator-WinPE.png ├── HardDiskValidator ├── Icons │ ├── HardDisk.ico │ └── License.txt ├── Enums │ ├── TestName.cs │ └── BlockStatus.cs ├── Properties │ └── HardDiskValidator.exe.manifest ├── ILMerge │ └── ILMerge.bat ├── Utilities │ ├── WinFormsUtils.cs │ ├── ByteUtils │ │ ├── ByteUtils.cs │ │ └── ByteReader.cs │ └── Conversion │ │ ├── BigEndianConverter.cs │ │ └── LittleEndianConverter.cs ├── HardDiskValidator.csproj ├── UIHelper.cs ├── Program.cs ├── DiskReader.cs ├── MainForm.Designer.cs ├── DiskTester.cs └── MainForm.cs ├── .gitattributes ├── HardDiskValidator.sln ├── Readme.md └── .gitignore /HardDiskValidator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAloni/HardDiskValidator/HEAD/HardDiskValidator.png -------------------------------------------------------------------------------- /HardDiskValidator-WinPE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAloni/HardDiskValidator/HEAD/HardDiskValidator-WinPE.png -------------------------------------------------------------------------------- /HardDiskValidator/Icons/HardDisk.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TalAloni/HardDiskValidator/HEAD/HardDiskValidator/Icons/HardDisk.ico -------------------------------------------------------------------------------- /HardDiskValidator/Icons/License.txt: -------------------------------------------------------------------------------- 1 | Hard Disk Icon courtesy of Oliver Scholtz (https://www.iconfinder.com/icons/24564/hard_harddisk_hd_icon) 2 | License: Free for non commercial use 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Unsetting the text attribute on a path tells Git not to attempt any end-of-line conversion upon checkin or checkout 2 | * -text 3 | 4 | # Custom for Visual Studio 5 | *.cs -text diff=csharp 6 | *.csproj -text merge=union 7 | *.sln -text merge=union eol=crlf 8 | -------------------------------------------------------------------------------- /HardDiskValidator/Enums/TestName.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace HardDiskValidator 3 | { 4 | public enum TestName 5 | { 6 | Read, 7 | ReadWipeDamagedRead, 8 | ReadWriteVerifyRestore, 9 | WriteVerify, 10 | Write, // The first part of WriteVerify 11 | Verify, // The second part of WriteVerify 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HardDiskValidator/Properties/HardDiskValidator.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /HardDiskValidator/Enums/BlockStatus.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | namespace HardDiskValidator 8 | { 9 | public enum BlockStatus 10 | { 11 | Untested, 12 | OK, 13 | OverwriteOK, 14 | Damaged, 15 | IOError, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HardDiskValidator/ILMerge/ILMerge.bat: -------------------------------------------------------------------------------- 1 | IF ["%programfiles(x86)%"]==[""] SET ilmergePath="%programfiles%\Microsoft\ILMerge" 2 | IF NOT ["%programfiles(x86)%"]==[""] SET ilmergePath="%programfiles(x86)%\Microsoft\ILMerge" 3 | 4 | set TargetFramework=%1 5 | if %TargetFramework%==net20 set TargetPlaform=2.0 6 | if %TargetFramework%==net40 set TargetPlaform=4.0 7 | set binaryPath=%CD%\..\bin\Release\%TargetFramework% 8 | set outputPath=%CD%\..\bin\ILMerge\%TargetFramework% 9 | IF NOT EXIST "%outputPath%" MKDIR "%outputPath%" 10 | %ilmergePath%\ilmerge /targetplatform=%TargetPlaform% /ndebug /target:winexe /out:"%outputPath%\HardDiskValidator.exe" "%binaryPath%\HardDiskValidator.exe" "%binaryPath%\DiskAccessLibrary.dll" "%binaryPath%\DiskAccessLibrary.Win32.dll" -------------------------------------------------------------------------------- /HardDiskValidator/Utilities/WinFormsUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | namespace Utilities 8 | { 9 | public partial class WinFormsUtils 10 | { 11 | /// 12 | /// Use this method to prevent Windows themes from having a different inner-form size 13 | /// 14 | public static void SetFixedClientSize(Form form, int width, int height) 15 | { 16 | form.MaximumSize = Size.Empty; // Give some room for the form to expand 17 | form.MinimumSize = Size.Empty; 18 | form.ClientSize = new Size(width, height); 19 | form.MinimumSize = form.Size; 20 | form.MaximumSize = form.Size; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HardDiskValidator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HardDiskValidator", "HardDiskValidator\HardDiskValidator.csproj", "{3D440C05-557B-4CAC-920D-1D11551FD8CD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {3D440C05-557B-4CAC-920D-1D11551FD8CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {3D440C05-557B-4CAC-920D-1D11551FD8CD}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {3D440C05-557B-4CAC-920D-1D11551FD8CD}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {3D440C05-557B-4CAC-920D-1D11551FD8CD}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {4B34D27F-4CD5-4314-B369-05EB0E9E54E3} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /HardDiskValidator/HardDiskValidator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net20;net40;netcoreapp3.1 6 | Hard Disk Validator 7 | Hard Disk Validator 8 | 1.1.2 9 | true 10 | Icons\HardDisk.ico 11 | Tal Aloni 12 | Copyright © Tal Aloni 2016-2024 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /HardDiskValidator/UIHelper.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Drawing; 10 | using System.Text; 11 | 12 | namespace HardDiskValidator 13 | { 14 | public class UIHelper 15 | { 16 | public static Color GetColor(BlockStatus status) 17 | { 18 | switch (status) 19 | { 20 | case BlockStatus.Untested: 21 | return Color.DarkGray; 22 | case BlockStatus.OK: 23 | return Color.LightGreen; 24 | case BlockStatus.OverwriteOK: 25 | return Color.White; 26 | case BlockStatus.Damaged: 27 | return Color.Red; 28 | default: 29 | return Color.Maroon; 30 | } 31 | } 32 | 33 | public static string GetSizeString(long value) 34 | { 35 | string[] suffixes = { " B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; 36 | int suffixIndex = 0; 37 | while (value > 9999 && suffixIndex < suffixes.Length - 1) 38 | { 39 | value = value / 1024; 40 | suffixIndex++; 41 | } 42 | 43 | return String.Format("{0} {1}", value.ToString(), suffixes[suffixIndex]); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /HardDiskValidator/Program.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Security.Principal; 10 | using System.Threading; 11 | using System.Windows.Forms; 12 | 13 | namespace HardDiskValidator 14 | { 15 | static class Program 16 | { 17 | /// 18 | /// The main entry point for the application. 19 | /// 20 | [STAThread] 21 | static void Main() 22 | { 23 | Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); 24 | AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); 25 | 26 | WindowsIdentity windowsIdentity = null; 27 | try 28 | { 29 | windowsIdentity = WindowsIdentity.GetCurrent(); 30 | } 31 | catch 32 | { 33 | MessageBox.Show("This program requires execution privileges", "Hard Disk Validator"); 34 | return; 35 | } 36 | WindowsPrincipal windowsPrincipal = new WindowsPrincipal(windowsIdentity); 37 | bool isAdministrator = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); 38 | if (!isAdministrator) 39 | { 40 | MessageBox.Show("This program requires administrator privileges!", "Hard Disk Validator"); 41 | return; 42 | } 43 | 44 | Application.EnableVisualStyles(); 45 | Application.SetCompatibleTextRenderingDefault(false); 46 | Application.Run(new MainForm()); 47 | } 48 | 49 | public static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) 50 | { 51 | HandleUnhandledException(e.Exception); 52 | } 53 | 54 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 55 | { 56 | if (e.ExceptionObject != null) 57 | { 58 | Exception ex = (Exception)e.ExceptionObject; 59 | HandleUnhandledException(ex); 60 | } 61 | } 62 | 63 | private static void HandleUnhandledException(Exception ex) 64 | { 65 | string message = String.Format("Exception: {0}: {1} Source: {2} {3}", ex.GetType(), ex.Message, ex.Source, ex.StackTrace); 66 | MessageBox.Show(message, "Error"); 67 | Application.Exit(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | About Hard Disk Validator: 2 | ========================== 3 | This simple utility was designed to help you find out if your hard drive has reached its end of life. 4 | 5 | ##### Q: What is a bad sector? 6 | Hard drives write data in block units (sectors), every time a hard drive update a sector, it also updates a checksum (stored immediately after the sector data). When a sector is read from your hard drive, it's expected that the sector checksum will match the sector data, if that is not a case, the hard disk knows something went wrong during the write operation, that's called a bad sector. 7 | 8 | ##### Q: What causes bad sectors to happen? 9 | Power failure during write is one common reason, hard drive malfunction is another common reason. 10 | 11 | ##### Q: Can I repair a bad sector? 12 | Well, the data stored on the sector is invalid, but if your hard drive is functioning properly, you can overwrite the bad sector (and now it won't be "bad" anymore since the sector checksum will be updated as well). 13 | 14 | ###### This is my hard drive after wiping out a bad sector that was created during a power failure: 15 | ![HardDiskValidator](HardDiskValidator.png) 16 | 17 | ##### Q: Which test should I use? 18 | If you're recovering from a power failure, then "Read + Wipe Damaged + Read" is the fastest way to wipe out the bad sectors and avoid any issue with software that does not respond well to bad sectors. 19 | If something seems to be wrong with the drive, it's better to back up the data and use the "Write + Verify" test, which will erase all of the data on the disk. 20 | 21 | ##### Q: What's the difference between the tests? 22 | * **Read:** Will scan the entire hard drive surface to find bad sectors. 23 | * **Read + Wipe Damaged + Read:** Will scan the entire hard drive surface to find bad sectors, if bad sectors are found, they will be overwritten, and read again to make sure they were written successfully this time. 24 | * **Read + Write + Verify + Restore:** The program will write a test pattern to the disk, verify the pattern was written successfully, and then restore the original data. 25 | * **Write + Verify:** The program will write a test pattern to the disk and verify the pattern was written successfully. (the original data will be lost). 26 | 27 | ##### Q: I only have a single hard disk that is used to boot my OS, how can I test it? 28 | You should have no problem performing a read test, for any other test it's recommended that you avoid testing the disk that hosts the currently running operating system. so you have two options: 29 |    **1.** Connect your disk (as a secondary disk) to another PC to test it there. 30 |    **2.** Boot your PC from Windows PE (from a CD, DVD or USB) and use Mono to launch Hard Disk Validator. I have packaged the necessary Mono files [here](https://drive.google.com/file/d/1cHBupD6LfuFeatFgncX9CMu3HQPkaXlZ/view?usp=sharing). You can find a ready-to-use WinPE 5.1 ISO [here](https://drive.google.com/file/d/0B1wrdynUizpMOWEwWFBnMlBkRUU/view?usp=sharing&resourcekey=0--E0DTpPa7Z8LhGSD3M5XXw). 31 | ![HardDiskValidator on WinPE](HardDiskValidator-WinPE.png) 32 | 33 | Contact: 34 | ======== 35 | If you have any question, feel free to contact me. 36 | Tal Aloni 37 | -------------------------------------------------------------------------------- /HardDiskValidator/Utilities/ByteUtils/ByteUtils.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012-2020 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.IO; 9 | 10 | namespace Utilities 11 | { 12 | public class ByteUtils 13 | { 14 | public static byte[] Concatenate(byte[] a, byte[] b) 15 | { 16 | byte[] result = new byte[a.Length + b.Length]; 17 | Array.Copy(a, 0, result, 0, a.Length); 18 | Array.Copy(b, 0, result, a.Length, b.Length); 19 | return result; 20 | } 21 | 22 | public static bool AreByteArraysEqual(byte[] array1, byte[] array2) 23 | { 24 | if (array1.Length != array2.Length) 25 | { 26 | return false; 27 | } 28 | 29 | for (int index = 0; index < array1.Length; index++) 30 | { 31 | if (array1[index] != array2[index]) 32 | { 33 | return false; 34 | } 35 | } 36 | 37 | return true; 38 | } 39 | 40 | public static byte[] XOR(byte[] array1, byte[] array2) 41 | { 42 | if (array1.Length == array2.Length) 43 | { 44 | return XOR(array1, 0, array2, 0, array1.Length); 45 | } 46 | else 47 | { 48 | throw new ArgumentException("Arrays must be of equal length"); 49 | } 50 | } 51 | 52 | public static byte[] XOR(byte[] array1, int offset1, byte[] array2, int offset2, int length) 53 | { 54 | if (offset1 + length <= array1.Length && offset2 + length <= array2.Length) 55 | { 56 | byte[] result = new byte[length]; 57 | for (int index = 0; index < length; index++) 58 | { 59 | result[index] = (byte)(array1[offset1 + index] ^ array2[offset2 + index]); 60 | } 61 | return result; 62 | } 63 | else 64 | { 65 | throw new ArgumentOutOfRangeException(); 66 | } 67 | } 68 | 69 | public static long CopyStream(Stream input, Stream output) 70 | { 71 | // input may not support seeking, so don't use input.Position 72 | return CopyStream(input, output, Int64.MaxValue); 73 | } 74 | 75 | public static long CopyStream(Stream input, Stream output, long count) 76 | { 77 | const int MaxBufferSize = 1048576; // 1 MB 78 | int bufferSize = (int)Math.Min(MaxBufferSize, count); 79 | byte[] buffer = new byte[bufferSize]; 80 | long totalBytesRead = 0; 81 | while (totalBytesRead < count) 82 | { 83 | int numberOfBytesToRead = (int)Math.Min(bufferSize, count - totalBytesRead); 84 | int bytesRead = input.Read(buffer, 0, numberOfBytesToRead); 85 | totalBytesRead += bytesRead; 86 | output.Write(buffer, 0, bytesRead); 87 | if (bytesRead == 0) // no more bytes to read from input stream 88 | { 89 | return totalBytesRead; 90 | } 91 | } 92 | return totalBytesRead; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | build/ 18 | bld/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | 22 | # Roslyn cache directories 23 | *.ide/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | #NUNIT 30 | *.VisualState.xml 31 | TestResult.xml 32 | 33 | # Build Results of an ATL Project 34 | [Dd]ebugPS/ 35 | [Rr]eleasePS/ 36 | dlldata.c 37 | 38 | *_i.c 39 | *_p.c 40 | *_i.h 41 | *.ilk 42 | *.meta 43 | *.obj 44 | *.pch 45 | *.pdb 46 | *.pgc 47 | *.pgd 48 | *.rsp 49 | *.sbr 50 | *.tlb 51 | *.tli 52 | *.tlh 53 | *.tmp 54 | *.tmp_proj 55 | *.log 56 | *.vspscc 57 | *.vssscc 58 | .builds 59 | *.pidb 60 | *.svclog 61 | *.scc 62 | 63 | # Chutzpah Test files 64 | _Chutzpah* 65 | 66 | # Visual C++ cache files 67 | ipch/ 68 | *.aps 69 | *.ncb 70 | *.opensdf 71 | *.sdf 72 | *.cachefile 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | *.vspx 78 | 79 | # TFS 2012 Local Workspace 80 | $tf/ 81 | 82 | # Guidance Automation Toolkit 83 | *.gpState 84 | 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper*/ 87 | *.[Rr]e[Ss]harper 88 | *.DotSettings.user 89 | 90 | # JustCode is a .NET coding addin-in 91 | .JustCode 92 | 93 | # TeamCity is a build add-in 94 | _TeamCity* 95 | 96 | # DotCover is a Code Coverage Tool 97 | *.dotCover 98 | 99 | # NCrunch 100 | _NCrunch_* 101 | .*crunch*.local.xml 102 | 103 | # MightyMoose 104 | *.mm.* 105 | AutoTest.Net/ 106 | 107 | # Web workbench (sass) 108 | .sass-cache/ 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.[Pp]ublish.xml 128 | *.azurePubxml 129 | # TODO: Comment the next line if you want to checkin your web deploy settings 130 | # but database connection strings (with potential passwords) will be unencrypted 131 | *.pubxml 132 | *.publishproj 133 | 134 | # NuGet Packages 135 | *.nupkg 136 | # The packages folder can be ignored because of Package Restore 137 | **/packages/* 138 | # except build/, which is used as an MSBuild target. 139 | !**/packages/build/ 140 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 141 | #!**/packages/repositories.config 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # ========================= 187 | # Operating System Files 188 | # ========================= 189 | 190 | # OSX 191 | # ========================= 192 | 193 | .DS_Store 194 | .AppleDouble 195 | .LSOverride 196 | 197 | # Thumbnails 198 | ._* 199 | 200 | # Files that might appear on external disk 201 | .Spotlight-V100 202 | .Trashes 203 | 204 | # Directories potentially created on remote AFP share 205 | .AppleDB 206 | .AppleDesktop 207 | Network Trash Folder 208 | Temporary Items 209 | .apdisk 210 | 211 | # Windows 212 | # ========================= 213 | 214 | # Windows image file caches 215 | Thumbs.db 216 | ehthumbs.db 217 | 218 | # Folder config file 219 | Desktop.ini 220 | 221 | # Recycle Bin used on file shares 222 | $RECYCLE.BIN/ 223 | 224 | # Windows Installer files 225 | *.cab 226 | *.msi 227 | *.msm 228 | *.msp 229 | 230 | # Windows shortcuts 231 | *.lnk 232 | -------------------------------------------------------------------------------- /HardDiskValidator/Utilities/Conversion/BigEndianConverter.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012-2020 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Utilities 11 | { 12 | public class BigEndianConverter 13 | { 14 | public static ushort ToUInt16(byte[] buffer, int offset) 15 | { 16 | return (ushort)((buffer[offset + 0] << 8) | (buffer[offset + 1] << 0)); 17 | } 18 | 19 | public static short ToInt16(byte[] buffer, int offset) 20 | { 21 | return (short)ToUInt16(buffer, offset); 22 | } 23 | 24 | public static uint ToUInt32(byte[] buffer, int offset) 25 | { 26 | return (uint)((buffer[offset + 0] << 24) | (buffer[offset + 1] << 16) 27 | | (buffer[offset + 2] << 8) | (buffer[offset + 3] << 0)); 28 | } 29 | 30 | public static int ToInt32(byte[] buffer, int offset) 31 | { 32 | return (int)ToUInt32(buffer, offset); 33 | } 34 | 35 | public static ulong ToUInt64(byte[] buffer, int offset) 36 | { 37 | return (((ulong)ToUInt32(buffer, offset + 0)) << 32) | ToUInt32(buffer, offset + 4); 38 | } 39 | 40 | public static long ToInt64(byte[] buffer, int offset) 41 | { 42 | return (long)ToUInt64(buffer, offset); 43 | } 44 | 45 | public static Guid ToGuid(byte[] buffer, int offset) 46 | { 47 | return new Guid( 48 | ToUInt32(buffer, offset + 0), 49 | ToUInt16(buffer, offset + 4), 50 | ToUInt16(buffer, offset + 6), 51 | buffer[offset + 8], 52 | buffer[offset + 9], 53 | buffer[offset + 10], 54 | buffer[offset + 11], 55 | buffer[offset + 12], 56 | buffer[offset + 13], 57 | buffer[offset + 14], 58 | buffer[offset + 15]); 59 | } 60 | 61 | public static byte[] GetBytes(ushort value) 62 | { 63 | byte[] result = new byte[2]; 64 | result[0] = (byte)((value >> 8) & 0xFF); 65 | result[1] = (byte)((value >> 0) & 0xFF); 66 | return result; 67 | } 68 | 69 | public static byte[] GetBytes(short value) 70 | { 71 | return GetBytes((ushort)value); 72 | } 73 | 74 | public static byte[] GetBytes(uint value) 75 | { 76 | byte[] result = new byte[4]; 77 | result[0] = (byte)((value >> 24) & 0xFF); 78 | result[1] = (byte)((value >> 16) & 0xFF); 79 | result[2] = (byte)((value >> 8) & 0xFF); 80 | result[3] = (byte)((value >> 0) & 0xFF); 81 | 82 | return result; 83 | } 84 | 85 | public static byte[] GetBytes(int value) 86 | { 87 | return GetBytes((uint)value); 88 | } 89 | 90 | public static byte[] GetBytes(ulong value) 91 | { 92 | byte[] result = new byte[8]; 93 | Array.Copy(GetBytes((uint)(value >> 32)), 0, result, 0, 4); 94 | Array.Copy(GetBytes((uint)(value & 0xFFFFFFFF)), 0, result, 4, 4); 95 | 96 | return result; 97 | } 98 | 99 | public static byte[] GetBytes(long value) 100 | { 101 | return GetBytes((ulong)value); 102 | } 103 | 104 | public static byte[] GetBytes(Guid value) 105 | { 106 | byte[] result = value.ToByteArray(); 107 | if (BitConverter.IsLittleEndian) 108 | { 109 | // reverse first 4 bytes 110 | byte temp = result[0]; 111 | result[0] = result[3]; 112 | result[3] = temp; 113 | 114 | temp = result[1]; 115 | result[1] = result[2]; 116 | result[2] = temp; 117 | 118 | // reverse next 2 bytes 119 | temp = result[4]; 120 | result[4] = result[5]; 121 | result[5] = temp; 122 | 123 | // reverse next 2 bytes 124 | temp = result[6]; 125 | result[6] = result[7]; 126 | result[7] = temp; 127 | } 128 | return result; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /HardDiskValidator/Utilities/ByteUtils/ByteReader.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012-2020 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.IO; 9 | using System.Text; 10 | 11 | namespace Utilities 12 | { 13 | public class ByteReader 14 | { 15 | public static byte ReadByte(byte[] buffer, int offset) 16 | { 17 | return buffer[offset]; 18 | } 19 | 20 | public static byte ReadByte(byte[] buffer, ref int offset) 21 | { 22 | offset++; 23 | return buffer[offset - 1]; 24 | } 25 | 26 | public static byte[] ReadBytes(byte[] buffer, int offset, int length) 27 | { 28 | byte[] result = new byte[length]; 29 | Array.Copy(buffer, offset, result, 0, length); 30 | return result; 31 | } 32 | 33 | public static byte[] ReadBytes(byte[] buffer, ref int offset, int length) 34 | { 35 | offset += length; 36 | return ReadBytes(buffer, offset - length, length); 37 | } 38 | 39 | /// 40 | /// Will return the ANSI string stored in the buffer 41 | /// 42 | public static string ReadAnsiString(byte[] buffer, int offset, int count) 43 | { 44 | // ASCIIEncoding.ASCII.GetString will convert some values to '?' (byte value of 63) 45 | // Any codepage will do, but the only one that Mono supports is 28591. 46 | return ASCIIEncoding.GetEncoding(28591).GetString(buffer, offset, count); 47 | } 48 | 49 | public static string ReadAnsiString(byte[] buffer, ref int offset, int count) 50 | { 51 | offset += count; 52 | return ReadAnsiString(buffer, offset - count, count); 53 | } 54 | 55 | public static string ReadUTF16String(byte[] buffer, int offset, int numberOfCharacters) 56 | { 57 | int numberOfBytes = numberOfCharacters * 2; 58 | return Encoding.Unicode.GetString(buffer, offset, numberOfBytes); 59 | } 60 | 61 | public static string ReadUTF16String(byte[] buffer, ref int offset, int numberOfCharacters) 62 | { 63 | int numberOfBytes = numberOfCharacters * 2; 64 | offset += numberOfBytes; 65 | return ReadUTF16String(buffer, offset - numberOfBytes, numberOfCharacters); 66 | } 67 | 68 | public static string ReadNullTerminatedAnsiString(byte[] buffer, int offset) 69 | { 70 | StringBuilder builder = new StringBuilder(); 71 | char c = (char)ByteReader.ReadByte(buffer, offset); 72 | while (c != '\0') 73 | { 74 | builder.Append(c); 75 | offset++; 76 | c = (char)ByteReader.ReadByte(buffer, offset); 77 | } 78 | return builder.ToString(); 79 | } 80 | 81 | public static string ReadNullTerminatedUTF16String(byte[] buffer, int offset) 82 | { 83 | StringBuilder builder = new StringBuilder(); 84 | char c = (char)LittleEndianConverter.ToUInt16(buffer, offset); 85 | while (c != 0) 86 | { 87 | builder.Append(c); 88 | offset += 2; 89 | c = (char)LittleEndianConverter.ToUInt16(buffer, offset); 90 | } 91 | return builder.ToString(); 92 | } 93 | 94 | public static string ReadNullTerminatedAnsiString(byte[] buffer, ref int offset) 95 | { 96 | string result = ReadNullTerminatedAnsiString(buffer, offset); 97 | offset += result.Length + 1; 98 | return result; 99 | } 100 | 101 | public static string ReadNullTerminatedUTF16String(byte[] buffer, ref int offset) 102 | { 103 | string result = ReadNullTerminatedUTF16String(buffer, offset); 104 | offset += result.Length * 2 + 2; 105 | return result; 106 | } 107 | 108 | public static byte[] ReadBytes(Stream stream, int count) 109 | { 110 | MemoryStream temp = new MemoryStream(); 111 | ByteUtils.CopyStream(stream, temp, count); 112 | return temp.ToArray(); 113 | } 114 | 115 | /// 116 | /// Return all bytes from current stream position to the end of the stream 117 | /// 118 | public static byte[] ReadAllBytes(Stream stream) 119 | { 120 | MemoryStream temp = new MemoryStream(); 121 | ByteUtils.CopyStream(stream, temp); 122 | return temp.ToArray(); 123 | } 124 | 125 | public static string ReadAnsiString(Stream stream, int length) 126 | { 127 | byte[] buffer = ReadBytes(stream, length); 128 | return ASCIIEncoding.GetEncoding(28591).GetString(buffer); 129 | } 130 | 131 | public static string ReadUTF16String(Stream stream, int numberOfCharacters) 132 | { 133 | byte[] buffer = ReadBytes(stream, numberOfCharacters * 2); 134 | return Encoding.Unicode.GetString(buffer); 135 | } 136 | 137 | public static string ReadNullTerminatedAnsiString(Stream stream) 138 | { 139 | StringBuilder builder = new StringBuilder(); 140 | char c = (char)stream.ReadByte(); 141 | while (c != '\0') 142 | { 143 | builder.Append(c); 144 | c = (char)stream.ReadByte(); 145 | } 146 | return builder.ToString(); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /HardDiskValidator/Utilities/Conversion/LittleEndianConverter.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2012-2020 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Utilities 11 | { 12 | public class LittleEndianConverter 13 | { 14 | public static ushort ToUInt16(byte[] buffer, int offset) 15 | { 16 | return (ushort)((buffer[offset + 1] << 8) | (buffer[offset + 0] << 0)); 17 | } 18 | 19 | public static short ToInt16(byte[] buffer, int offset) 20 | { 21 | return (short)ToUInt16(buffer, offset); 22 | } 23 | 24 | public static uint ToUInt32(byte[] buffer, int offset) 25 | { 26 | return (uint)((buffer[offset + 3] << 24) | (buffer[offset + 2] << 16) 27 | | (buffer[offset + 1] << 8) | (buffer[offset + 0] << 0)); 28 | } 29 | 30 | public static int ToInt32(byte[] buffer, int offset) 31 | { 32 | return (int)ToUInt32(buffer, offset); 33 | } 34 | 35 | public static ulong ToUInt64(byte[] buffer, int offset) 36 | { 37 | return (((ulong)ToUInt32(buffer, offset + 4)) << 32) | ToUInt32(buffer, offset + 0); 38 | } 39 | 40 | public static long ToInt64(byte[] buffer, int offset) 41 | { 42 | return (long)ToUInt64(buffer, offset); 43 | } 44 | 45 | public static float ToFloat32(byte[] buffer, int offset) 46 | { 47 | byte[] bytes = new byte[4]; 48 | Array.Copy(buffer, offset, bytes, 0, 4); 49 | if (!BitConverter.IsLittleEndian) 50 | { 51 | // reverse the order of 'bytes' 52 | for (int index = 0; index < 2; index++) 53 | { 54 | byte temp = bytes[index]; 55 | bytes[index] = bytes[3 - index]; 56 | bytes[3 - index] = temp; 57 | } 58 | } 59 | return BitConverter.ToSingle(bytes, 0); 60 | } 61 | 62 | public static double ToFloat64(byte[] buffer, int offset) 63 | { 64 | byte[] bytes = new byte[8]; 65 | Array.Copy(buffer, offset, bytes, 0, 8); 66 | if (!BitConverter.IsLittleEndian) 67 | { 68 | // reverse the order of 'bytes' 69 | for(int index = 0; index < 4; index++) 70 | { 71 | byte temp = bytes[index]; 72 | bytes[index] = bytes[7 - index]; 73 | bytes[7 - index] = temp; 74 | } 75 | } 76 | return BitConverter.ToDouble(bytes, 0); 77 | } 78 | 79 | public static Guid ToGuid(byte[] buffer, int offset) 80 | { 81 | return new Guid( 82 | ToUInt32(buffer, offset + 0), 83 | ToUInt16(buffer, offset + 4), 84 | ToUInt16(buffer, offset + 6), 85 | buffer[offset + 8], 86 | buffer[offset + 9], 87 | buffer[offset + 10], 88 | buffer[offset + 11], 89 | buffer[offset + 12], 90 | buffer[offset + 13], 91 | buffer[offset + 14], 92 | buffer[offset + 15]); 93 | } 94 | 95 | public static byte[] GetBytes(ushort value) 96 | { 97 | byte[] result = new byte[2]; 98 | result[0] = (byte)((value >> 0) & 0xFF); 99 | result[1] = (byte)((value >> 8) & 0xFF); 100 | return result; 101 | } 102 | 103 | public static byte[] GetBytes(short value) 104 | { 105 | return GetBytes((ushort)value); 106 | } 107 | 108 | public static byte[] GetBytes(uint value) 109 | { 110 | byte[] result = new byte[4]; 111 | result[0] = (byte)((value >> 0) & 0xFF); 112 | result[1] = (byte)((value >> 8) & 0xFF); 113 | result[2] = (byte)((value >> 16) & 0xFF); 114 | result[3] = (byte)((value >> 24) & 0xFF); 115 | 116 | return result; 117 | } 118 | 119 | public static byte[] GetBytes(int value) 120 | { 121 | return GetBytes((uint)value); 122 | } 123 | 124 | public static byte[] GetBytes(ulong value) 125 | { 126 | byte[] result = new byte[8]; 127 | Array.Copy(GetBytes((uint)(value & 0xFFFFFFFF)), 0, result, 0, 4); 128 | Array.Copy(GetBytes((uint)(value >> 32)), 0, result, 4, 4); 129 | 130 | return result; 131 | } 132 | 133 | public static byte[] GetBytes(long value) 134 | { 135 | return GetBytes((ulong)value); 136 | } 137 | 138 | public static byte[] GetBytes(Guid value) 139 | { 140 | byte[] result = value.ToByteArray(); 141 | if (!BitConverter.IsLittleEndian) 142 | { 143 | // reverse first 4 bytes 144 | byte temp = result[0]; 145 | result[0] = result[3]; 146 | result[3] = temp; 147 | 148 | temp = result[1]; 149 | result[1] = result[2]; 150 | result[2] = temp; 151 | 152 | // reverse next 2 bytes 153 | temp = result[4]; 154 | result[4] = result[5]; 155 | result[5] = temp; 156 | 157 | // reverse next 2 bytes 158 | temp = result[6]; 159 | result[6] = result[7]; 160 | result[7] = temp; 161 | } 162 | return result; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /HardDiskValidator/DiskReader.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016-2018 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using DiskAccessLibrary; 11 | using DiskAccessLibrary.Win32; 12 | using Utilities; 13 | 14 | namespace HardDiskValidator 15 | { 16 | public class DiskReader 17 | { 18 | private Disk m_disk; 19 | private bool m_abort; 20 | 21 | public delegate void UpdateStatusHandler(long currentPosition); 22 | public event UpdateStatusHandler OnStatusUpdate; 23 | public delegate void AddToLogHandler(string format, params object[] args); 24 | public event AddToLogHandler OnLogUpdate; 25 | 26 | public DiskReader(Disk disk) 27 | { 28 | m_disk = disk; 29 | } 30 | 31 | /// Will return null if an error has occured or read is aborted 32 | public byte[] ReadSectors(long sectorIndex, long sectorCount, out bool ioErrorOccured) 33 | { 34 | ioErrorOccured = false; 35 | if (sectorCount > PhysicalDisk.MaximumDirectTransferSizeLBA) 36 | { 37 | // We must read one segment at a time, and copy the segments to a big bufffer 38 | byte[] buffer = new byte[sectorCount * m_disk.BytesPerSector]; 39 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 40 | { 41 | long leftToRead = sectorCount - sectorOffset; 42 | int sectorsToRead = (int)Math.Min(leftToRead, PhysicalDisk.MaximumDirectTransferSizeLBA); 43 | byte[] segment = ReadSectorsUnbuffered(sectorIndex + sectorOffset, sectorsToRead, out ioErrorOccured); 44 | if (m_abort || ioErrorOccured || segment == null) 45 | { 46 | long nextOffset = sectorOffset + sectorsToRead; 47 | if ((ioErrorOccured || segment == null) && (sectorCount - nextOffset > 0)) 48 | { 49 | AddToLog("Skipped {0:###,###,###,###,##0}-{1:###,###,###,###,##0}", sectorIndex + nextOffset, sectorIndex + sectorCount - 1); 50 | } 51 | return null; 52 | } 53 | Array.Copy(segment, 0, buffer, sectorOffset * m_disk.BytesPerSector, segment.Length); 54 | } 55 | return buffer; 56 | } 57 | else 58 | { 59 | return ReadSectorsUnbuffered(sectorIndex, (int)sectorCount, out ioErrorOccured); 60 | } 61 | } 62 | 63 | /// Will return null if an error has occured 64 | public byte[] ReadSectorsUnbuffered(long sectorIndex, int sectorCount, out bool ioErrorOccured) 65 | { 66 | ioErrorOccured = false; 67 | UpdateStatus(sectorIndex * m_disk.BytesPerSector); 68 | try 69 | { 70 | return m_disk.ReadSectors(sectorIndex, sectorCount); 71 | } 72 | catch (IOException ex) 73 | { 74 | int errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); 75 | AddToLog("Read failure (Win32 error: {0}) at {1:###,###,###,###,##0}-{2:###,###,###,###,##0}", errorCode, sectorIndex, sectorIndex + sectorCount - 1); 76 | if (errorCode != (int)Win32Error.ERROR_IO_DEVICE && errorCode != (int)Win32Error.ERROR_CRC) 77 | { 78 | ioErrorOccured = true; 79 | } 80 | return null; 81 | } 82 | } 83 | 84 | /// Will return null if an unrecoverable IO Error has occured or read is aborted 85 | public byte[] ReadEverySector(long sectorIndex, int sectorCount, out List damagedSectors, out bool ioErrorOccured) 86 | { 87 | if (sectorCount > PhysicalDisk.MaximumDirectTransferSizeLBA) 88 | { 89 | damagedSectors = new List(); 90 | ioErrorOccured = false; 91 | // we must read one segment at the time, and copy the segments to a big bufffer 92 | byte[] buffer = new byte[sectorCount * m_disk.BytesPerSector]; 93 | for (int sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 94 | { 95 | int leftToRead = sectorCount - sectorOffset; 96 | int sectorsToRead = (int)Math.Min(leftToRead, PhysicalDisk.MaximumDirectTransferSizeLBA); 97 | List damagedSectorsInSegment; 98 | bool ioErrorOccuredInSegment; 99 | byte[] segment = ReadEverySectorUnbuffered(sectorIndex + sectorOffset, sectorsToRead, out damagedSectorsInSegment, out ioErrorOccuredInSegment); 100 | damagedSectors.AddRange(damagedSectorsInSegment); 101 | if (ioErrorOccuredInSegment) 102 | { 103 | ioErrorOccured = true; 104 | return null; 105 | } 106 | if (m_abort) 107 | { 108 | return null; 109 | } 110 | Array.Copy(segment, 0, buffer, sectorOffset * m_disk.BytesPerSector, segment.Length); 111 | } 112 | return buffer; 113 | } 114 | else 115 | { 116 | return ReadEverySectorUnbuffered(sectorIndex, sectorCount, out damagedSectors, out ioErrorOccured); 117 | } 118 | } 119 | 120 | /// Will return null if an unrecoverable IO Error has occured or read is aborted 121 | public byte[] ReadEverySectorUnbuffered(long sectorIndex, int sectorCount, out List damagedSectors, out bool ioErrorOccured) 122 | { 123 | damagedSectors = new List(); 124 | ioErrorOccured = false; 125 | try 126 | { 127 | UpdateStatus(sectorIndex * m_disk.BytesPerSector); 128 | return m_disk.ReadSectors(sectorIndex, sectorCount); 129 | } 130 | catch (IOException ex1) 131 | { 132 | int errorCode1 = IOExceptionHelper.GetWin32ErrorCode(ex1); 133 | AddToLog("Read failure (Win32 error: {0}) at {1:###,###,###,###,##0}-{2:###,###,###,###,##0}", errorCode1, sectorIndex, sectorIndex + sectorCount - 1); 134 | if (errorCode1 != (int)Win32Error.ERROR_IO_DEVICE && errorCode1 != (int)Win32Error.ERROR_CRC) 135 | { 136 | ioErrorOccured = true; 137 | return null; 138 | } 139 | 140 | byte[] data = new byte[sectorCount * m_disk.BytesPerSector]; 141 | // Try to read sector by sector (to maximize data recovery) 142 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset++) 143 | { 144 | long currentPosition = (sectorIndex + sectorOffset) * m_disk.BytesPerSector; 145 | UpdateStatus(currentPosition); 146 | try 147 | { 148 | byte[] sectorBytes = m_disk.ReadSector(sectorIndex + sectorOffset); 149 | Array.Copy(sectorBytes, 0, data, sectorOffset * m_disk.BytesPerSector, m_disk.BytesPerSector); 150 | } 151 | catch (IOException ex2) 152 | { 153 | int errorCode2 = IOExceptionHelper.GetWin32ErrorCode(ex2); 154 | AddToLog("Read failure (Win32 error: {0}) at sector {1:###,###,###,###,##0}", errorCode2, sectorIndex + sectorOffset); 155 | if (errorCode2 == (int)Win32Error.ERROR_IO_DEVICE || errorCode2 == (int)Win32Error.ERROR_CRC) 156 | { 157 | damagedSectors.Add(sectorIndex + sectorOffset); 158 | } 159 | else // ERROR_FILE_NOT_FOUND, ERROR_DEVICE_NOT_CONNECTED, ERROR_DEV_NOT_EXIST etc. 160 | { 161 | ioErrorOccured = true; 162 | return null; 163 | } 164 | } 165 | if (m_abort) 166 | { 167 | return null; 168 | } 169 | } 170 | 171 | if (damagedSectors.Count == 0) 172 | { 173 | // Sometimes the bulk read will raise an exception but all sector by sector reads will succeed. 174 | // This means that there is some serious (and most likely unrecoverable) issue with the disk 175 | // so we report this as an unrecoverable IO error even though we managed to retrieve all of the data. 176 | ioErrorOccured = true; 177 | } 178 | return data; 179 | } 180 | } 181 | 182 | protected void UpdateStatus(long position) 183 | { 184 | if (OnStatusUpdate != null) 185 | { 186 | OnStatusUpdate(position); 187 | } 188 | } 189 | 190 | protected void AddToLog(string format, params object[] args) 191 | { 192 | if (OnLogUpdate != null) 193 | { 194 | OnLogUpdate(format, args); 195 | } 196 | } 197 | 198 | public Disk Disk 199 | { 200 | get 201 | { 202 | return m_disk; 203 | } 204 | } 205 | 206 | public bool Abort 207 | { 208 | get 209 | { 210 | return m_abort; 211 | } 212 | set 213 | { 214 | if (value) 215 | { 216 | m_abort = true; 217 | } 218 | else 219 | { 220 | throw new ArgumentException("Abort cannot be set to false"); 221 | } 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /HardDiskValidator/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace HardDiskValidator 2 | { 3 | partial class MainForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 32 | this.pictureBoxMap = new System.Windows.Forms.PictureBox(); 33 | this.btnStart = new System.Windows.Forms.Button(); 34 | this.comboDisks = new System.Windows.Forms.ComboBox(); 35 | this.chkRead = new System.Windows.Forms.RadioButton(); 36 | this.groupBox1 = new System.Windows.Forms.GroupBox(); 37 | this.chkWriteVerify = new System.Windows.Forms.RadioButton(); 38 | this.chkReadWriteVerifyRestore = new System.Windows.Forms.RadioButton(); 39 | this.chkReadRewriteVerify = new System.Windows.Forms.RadioButton(); 40 | this.lblSerialNumber = new System.Windows.Forms.Label(); 41 | this.btnCopyLog = new System.Windows.Forms.Button(); 42 | this.lblPosition = new System.Windows.Forms.Label(); 43 | this.lblSpeed = new System.Windows.Forms.Label(); 44 | this.groupProgress = new System.Windows.Forms.GroupBox(); 45 | this.pictureBoxLegend = new System.Windows.Forms.PictureBox(); 46 | this.groupBox2 = new System.Windows.Forms.GroupBox(); 47 | ((System.ComponentModel.ISupportInitialize)(this.pictureBoxMap)).BeginInit(); 48 | this.groupBox1.SuspendLayout(); 49 | this.groupProgress.SuspendLayout(); 50 | ((System.ComponentModel.ISupportInitialize)(this.pictureBoxLegend)).BeginInit(); 51 | this.groupBox2.SuspendLayout(); 52 | this.SuspendLayout(); 53 | // 54 | // pictureBoxMap 55 | // 56 | this.pictureBoxMap.Location = new System.Drawing.Point(8, 35); 57 | this.pictureBoxMap.Name = "pictureBoxMap"; 58 | this.pictureBoxMap.Size = new System.Drawing.Size(351, 301); 59 | this.pictureBoxMap.TabIndex = 0; 60 | this.pictureBoxMap.TabStop = false; 61 | this.pictureBoxMap.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBoxMap_Paint); 62 | // 63 | // btnStart 64 | // 65 | this.btnStart.Location = new System.Drawing.Point(370, 159); 66 | this.btnStart.Name = "btnStart"; 67 | this.btnStart.Size = new System.Drawing.Size(75, 23); 68 | this.btnStart.TabIndex = 6; 69 | this.btnStart.Text = "Start"; 70 | this.btnStart.UseVisualStyleBackColor = true; 71 | this.btnStart.Click += new System.EventHandler(this.btnStart_Click); 72 | // 73 | // comboDisks 74 | // 75 | this.comboDisks.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 76 | this.comboDisks.Enabled = false; 77 | this.comboDisks.FormattingEnabled = true; 78 | this.comboDisks.Location = new System.Drawing.Point(8, 8); 79 | this.comboDisks.Name = "comboDisks"; 80 | this.comboDisks.Size = new System.Drawing.Size(351, 21); 81 | this.comboDisks.TabIndex = 2; 82 | this.comboDisks.SelectedIndexChanged += new System.EventHandler(this.comboDisks_SelectedIndexChanged); 83 | // 84 | // chkRead 85 | // 86 | this.chkRead.AutoSize = true; 87 | this.chkRead.Checked = true; 88 | this.chkRead.Location = new System.Drawing.Point(6, 20); 89 | this.chkRead.Name = "chkRead"; 90 | this.chkRead.Size = new System.Drawing.Size(51, 17); 91 | this.chkRead.TabIndex = 3; 92 | this.chkRead.TabStop = true; 93 | this.chkRead.Text = "Read"; 94 | this.chkRead.UseVisualStyleBackColor = true; 95 | // 96 | // groupBox1 97 | // 98 | this.groupBox1.Controls.Add(this.chkWriteVerify); 99 | this.groupBox1.Controls.Add(this.chkReadWriteVerifyRestore); 100 | this.groupBox1.Controls.Add(this.chkReadRewriteVerify); 101 | this.groupBox1.Controls.Add(this.chkRead); 102 | this.groupBox1.Location = new System.Drawing.Point(370, 30); 103 | this.groupBox1.Name = "groupBox1"; 104 | this.groupBox1.Size = new System.Drawing.Size(185, 122); 105 | this.groupBox1.TabIndex = 4; 106 | this.groupBox1.TabStop = false; 107 | this.groupBox1.Text = "Select Test:"; 108 | // 109 | // chkWriteVerify 110 | // 111 | this.chkWriteVerify.AutoSize = true; 112 | this.chkWriteVerify.Location = new System.Drawing.Point(6, 92); 113 | this.chkWriteVerify.Name = "chkWriteVerify"; 114 | this.chkWriteVerify.Size = new System.Drawing.Size(88, 17); 115 | this.chkWriteVerify.TabIndex = 7; 116 | this.chkWriteVerify.TabStop = true; 117 | this.chkWriteVerify.Text = "Write + Verify"; 118 | this.chkWriteVerify.UseVisualStyleBackColor = true; 119 | // 120 | // chkReadWriteVerifyRestore 121 | // 122 | this.chkReadWriteVerifyRestore.AutoSize = true; 123 | this.chkReadWriteVerifyRestore.Location = new System.Drawing.Point(6, 68); 124 | this.chkReadWriteVerifyRestore.Name = "chkReadWriteVerifyRestore"; 125 | this.chkReadWriteVerifyRestore.Size = new System.Drawing.Size(175, 17); 126 | this.chkReadWriteVerifyRestore.TabIndex = 5; 127 | this.chkReadWriteVerifyRestore.TabStop = true; 128 | this.chkReadWriteVerifyRestore.Text = "Read + Write + Verify + Restore"; 129 | this.chkReadWriteVerifyRestore.UseVisualStyleBackColor = true; 130 | // 131 | // chkReadRewriteVerify 132 | // 133 | this.chkReadRewriteVerify.AutoSize = true; 134 | this.chkReadRewriteVerify.Location = new System.Drawing.Point(6, 44); 135 | this.chkReadRewriteVerify.Name = "chkReadRewriteVerify"; 136 | this.chkReadRewriteVerify.Size = new System.Drawing.Size(175, 17); 137 | this.chkReadRewriteVerify.TabIndex = 4; 138 | this.chkReadRewriteVerify.TabStop = true; 139 | this.chkReadRewriteVerify.Text = "Read + Wipe Damaged + Read"; 140 | this.chkReadRewriteVerify.UseVisualStyleBackColor = true; 141 | // 142 | // lblSerialNumber 143 | // 144 | this.lblSerialNumber.AutoSize = true; 145 | this.lblSerialNumber.Location = new System.Drawing.Point(367, 11); 146 | this.lblSerialNumber.Name = "lblSerialNumber"; 147 | this.lblSerialNumber.Size = new System.Drawing.Size(30, 13); 148 | this.lblSerialNumber.TabIndex = 5; 149 | this.lblSerialNumber.Text = "S/N:"; 150 | // 151 | // btnCopyLog 152 | // 153 | this.btnCopyLog.Location = new System.Drawing.Point(488, 158); 154 | this.btnCopyLog.Name = "btnCopyLog"; 155 | this.btnCopyLog.Size = new System.Drawing.Size(67, 24); 156 | this.btnCopyLog.TabIndex = 10; 157 | this.btnCopyLog.Text = "Copy Log"; 158 | this.btnCopyLog.UseVisualStyleBackColor = true; 159 | this.btnCopyLog.Click += new System.EventHandler(this.btnCopyLog_Click); 160 | // 161 | // lblPosition 162 | // 163 | this.lblPosition.AutoSize = true; 164 | this.lblPosition.Location = new System.Drawing.Point(3, 38); 165 | this.lblPosition.Name = "lblPosition"; 166 | this.lblPosition.Size = new System.Drawing.Size(47, 13); 167 | this.lblPosition.TabIndex = 7; 168 | this.lblPosition.Text = "Position:"; 169 | // 170 | // lblSpeed 171 | // 172 | this.lblSpeed.AutoSize = true; 173 | this.lblSpeed.Location = new System.Drawing.Point(3, 16); 174 | this.lblSpeed.Name = "lblSpeed"; 175 | this.lblSpeed.Size = new System.Drawing.Size(41, 13); 176 | this.lblSpeed.TabIndex = 8; 177 | this.lblSpeed.Text = "Speed:"; 178 | // 179 | // groupProgress 180 | // 181 | this.groupProgress.Controls.Add(this.lblSpeed); 182 | this.groupProgress.Controls.Add(this.lblPosition); 183 | this.groupProgress.Location = new System.Drawing.Point(370, 183); 184 | this.groupProgress.Name = "groupProgress"; 185 | this.groupProgress.Size = new System.Drawing.Size(185, 64); 186 | this.groupProgress.TabIndex = 9; 187 | this.groupProgress.TabStop = false; 188 | // 189 | // pictureBoxLegend 190 | // 191 | this.pictureBoxLegend.Location = new System.Drawing.Point(6, 13); 192 | this.pictureBoxLegend.Name = "pictureBoxLegend"; 193 | this.pictureBoxLegend.Size = new System.Drawing.Size(172, 66); 194 | this.pictureBoxLegend.TabIndex = 10; 195 | this.pictureBoxLegend.TabStop = false; 196 | this.pictureBoxLegend.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBoxLegend_Paint); 197 | // 198 | // groupBox2 199 | // 200 | this.groupBox2.Controls.Add(this.pictureBoxLegend); 201 | this.groupBox2.Location = new System.Drawing.Point(370, 251); 202 | this.groupBox2.Name = "groupBox2"; 203 | this.groupBox2.Size = new System.Drawing.Size(184, 86); 204 | this.groupBox2.TabIndex = 11; 205 | this.groupBox2.TabStop = false; 206 | this.groupBox2.Text = "Legend"; 207 | // 208 | // MainForm 209 | // 210 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 211 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 212 | this.ClientSize = new System.Drawing.Size(562, 344); 213 | this.Controls.Add(this.groupBox2); 214 | this.Controls.Add(this.btnCopyLog); 215 | this.Controls.Add(this.groupProgress); 216 | this.Controls.Add(this.btnStart); 217 | this.Controls.Add(this.lblSerialNumber); 218 | this.Controls.Add(this.groupBox1); 219 | this.Controls.Add(this.comboDisks); 220 | this.Controls.Add(this.pictureBoxMap); 221 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 222 | this.KeyPreview = true; 223 | this.MaximizeBox = false; 224 | this.MaximumSize = new System.Drawing.Size(570, 371); 225 | this.MinimumSize = new System.Drawing.Size(570, 371); 226 | this.Name = "MainForm"; 227 | this.Text = "Hard Disk Validator"; 228 | this.Deactivate += new System.EventHandler(this.MainForm_Deactivate); 229 | this.Load += new System.EventHandler(this.MainForm_Load); 230 | this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.MainForm_KeyUp); 231 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); 232 | this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.MainForm_KeyDown); 233 | ((System.ComponentModel.ISupportInitialize)(this.pictureBoxMap)).EndInit(); 234 | this.groupBox1.ResumeLayout(false); 235 | this.groupBox1.PerformLayout(); 236 | this.groupProgress.ResumeLayout(false); 237 | this.groupProgress.PerformLayout(); 238 | ((System.ComponentModel.ISupportInitialize)(this.pictureBoxLegend)).EndInit(); 239 | this.groupBox2.ResumeLayout(false); 240 | this.ResumeLayout(false); 241 | this.PerformLayout(); 242 | 243 | } 244 | 245 | #endregion 246 | 247 | private System.Windows.Forms.PictureBox pictureBoxMap; 248 | private System.Windows.Forms.Button btnStart; 249 | private System.Windows.Forms.ComboBox comboDisks; 250 | private System.Windows.Forms.RadioButton chkRead; 251 | private System.Windows.Forms.GroupBox groupBox1; 252 | private System.Windows.Forms.RadioButton chkReadRewriteVerify; 253 | private System.Windows.Forms.Label lblSerialNumber; 254 | private System.Windows.Forms.Button btnCopyLog; 255 | private System.Windows.Forms.Label lblPosition; 256 | private System.Windows.Forms.Label lblSpeed; 257 | private System.Windows.Forms.GroupBox groupProgress; 258 | private System.Windows.Forms.RadioButton chkReadWriteVerifyRestore; 259 | private System.Windows.Forms.PictureBox pictureBoxLegend; 260 | private System.Windows.Forms.GroupBox groupBox2; 261 | private System.Windows.Forms.RadioButton chkWriteVerify; 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /HardDiskValidator/DiskTester.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016-2018 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Text; 11 | using DiskAccessLibrary; 12 | using DiskAccessLibrary.Win32; 13 | using Utilities; 14 | 15 | namespace HardDiskValidator 16 | { 17 | public class DiskTester : DiskReader 18 | { 19 | const int TransferSizeLBA = 131072; // 64 MB (assuming 512-byte sectors) 20 | 21 | private TestName m_testName; 22 | 23 | public DiskTester(TestName testName, Disk disk) : base(disk) 24 | { 25 | m_testName = testName; 26 | } 27 | 28 | public BlockStatus PerformTest(long sectorIndex, long sectorCount) 29 | { 30 | if (m_testName == TestName.Read) 31 | { 32 | return PerformReadTest(sectorIndex, sectorCount); 33 | } 34 | else if (m_testName == TestName.ReadWipeDamagedRead) 35 | { 36 | return PerformReadWipeDamagedReadTest(sectorIndex, sectorCount); 37 | } 38 | else if (m_testName == TestName.ReadWriteVerifyRestore) 39 | { 40 | return PerformReadWriteVerifyRestoreTest(sectorIndex, sectorCount); 41 | } 42 | else if (m_testName == TestName.WriteVerify) 43 | { 44 | return PerformWriteVerifyTest(sectorIndex, sectorCount); 45 | } 46 | else if (m_testName == TestName.Write) 47 | { 48 | return PerformWriteTest(sectorIndex, sectorCount); 49 | } 50 | else 51 | { 52 | return PerformVerifyTest(sectorIndex, sectorCount); 53 | } 54 | } 55 | 56 | private BlockStatus PerformReadTest(long sectorIndex, long sectorCount) 57 | { 58 | // The only reason to read every sector separately is to maximize data recovery 59 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 60 | { 61 | long leftToRead = sectorCount - sectorOffset; 62 | int sectorsToRead = (int)Math.Min(leftToRead, PhysicalDisk.MaximumDirectTransferSizeLBA); 63 | bool ioErrorOccured; 64 | byte[] segment = ReadSectors(sectorIndex + sectorOffset, sectorsToRead, out ioErrorOccured); 65 | if (Abort) 66 | { 67 | return BlockStatus.Untested; 68 | } 69 | 70 | if (ioErrorOccured) 71 | { 72 | return BlockStatus.IOError; 73 | } 74 | 75 | if (segment == null) 76 | { 77 | return BlockStatus.Damaged; 78 | } 79 | } 80 | return BlockStatus.OK; 81 | } 82 | 83 | private BlockStatus PerformReadWipeDamagedReadTest(long sectorIndex, long sectorCount) 84 | { 85 | bool crcErrorOccuredInFirstPass = false; 86 | // We use a large TransferSizeLBA to circumvent the disk caching mechanism 87 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += TransferSizeLBA) 88 | { 89 | long leftToRead = sectorCount - sectorOffset; 90 | int sectorsToRead = (int)Math.Min(leftToRead, TransferSizeLBA); 91 | List damagedSectors; 92 | bool ioErrorOccured; 93 | // Clear allocations from previous iteration 94 | GC.Collect(); 95 | GC.WaitForPendingFinalizers(); 96 | ReadEverySector(sectorIndex + sectorOffset, sectorsToRead, out damagedSectors, out ioErrorOccured); 97 | if (Abort) 98 | { 99 | return BlockStatus.Untested; 100 | } 101 | 102 | if (ioErrorOccured) 103 | { 104 | return BlockStatus.IOError; 105 | } 106 | 107 | if (damagedSectors.Count > 0) 108 | { 109 | crcErrorOccuredInFirstPass = true; 110 | foreach (long damagedSectorIndex in damagedSectors) 111 | { 112 | byte[] sectorBytes = new byte[Disk.BytesPerSector]; 113 | try 114 | { 115 | WriteSectors(damagedSectorIndex, sectorBytes); 116 | } 117 | catch (IOException ex) 118 | { 119 | int errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(ex); 120 | AddToLog("Write failure (Win32 error: {0}) at sector {1:###,###,###,###,##0}", errorCode, damagedSectorIndex); 121 | return BlockStatus.IOError; 122 | } 123 | AddToLog("Sector {0:###,###,###,###,##0} has been overwritten", damagedSectorIndex); 124 | } 125 | 126 | // TODO: We should make sure that the writes are flushed to disk 127 | byte[] segment = ReadSectors(sectorIndex + sectorOffset, sectorsToRead, out ioErrorOccured); 128 | 129 | if (Abort) 130 | { 131 | return BlockStatus.Untested; 132 | } 133 | 134 | if (ioErrorOccured) 135 | { 136 | return BlockStatus.IOError; 137 | } 138 | 139 | if (segment == null) 140 | { 141 | return BlockStatus.Damaged; 142 | } 143 | } 144 | if (Abort) 145 | { 146 | return BlockStatus.Untested; 147 | } 148 | } 149 | 150 | if (!crcErrorOccuredInFirstPass) 151 | { 152 | return BlockStatus.OK; 153 | } 154 | else 155 | { 156 | return BlockStatus.OverwriteOK; 157 | } 158 | } 159 | 160 | private BlockStatus PerformReadWriteVerifyRestoreTest(long sectorIndex, long sectorCount) 161 | { 162 | bool crcErrorOccuredInFirstPass = false; 163 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += TransferSizeLBA) 164 | { 165 | long leftToRead = sectorCount - sectorOffset; 166 | int sectorsToRead = (int)Math.Min(leftToRead, TransferSizeLBA); 167 | List damagedSectors; 168 | bool ioErrorOccured; 169 | // Clear allocations from previous iteration 170 | GC.Collect(); 171 | GC.WaitForPendingFinalizers(); 172 | byte[] data = ReadEverySector(sectorIndex + sectorOffset, sectorsToRead, out damagedSectors, out ioErrorOccured); 173 | if (Abort) 174 | { 175 | return BlockStatus.Untested; 176 | } 177 | 178 | if (ioErrorOccured) 179 | { 180 | return BlockStatus.IOError; 181 | } 182 | 183 | if (damagedSectors.Count > 0) 184 | { 185 | crcErrorOccuredInFirstPass = true; 186 | } 187 | 188 | // TODO: generate dummy read operation if leftToRead < TransferSizeLBA 189 | BlockStatus writeVerifyStatus = PerformWriteVerifyTest(sectorIndex + sectorOffset, sectorsToRead); 190 | 191 | // restore the original data 192 | try 193 | { 194 | WriteSectors(sectorIndex + sectorOffset, data); 195 | } 196 | catch (Exception ex) 197 | { 198 | int errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(ex); 199 | AddToLog("Restore failure (Win32 error: {0}) at sectors {1:###,###,###,###,##0}-{2:###,###,###,###,##0}", errorCode, sectorIndex, sectorIndex + sectorsToRead - 1); 200 | return BlockStatus.IOError; 201 | } 202 | 203 | if (writeVerifyStatus != BlockStatus.OK) 204 | { 205 | return writeVerifyStatus; 206 | } 207 | 208 | if (Abort) 209 | { 210 | return BlockStatus.Untested; 211 | } 212 | } 213 | 214 | if (crcErrorOccuredInFirstPass) 215 | { 216 | return BlockStatus.OverwriteOK; 217 | } 218 | else 219 | { 220 | return BlockStatus.OK; 221 | } 222 | } 223 | 224 | private BlockStatus PerformWriteVerifyTest(long sectorIndex, long sectorCount) 225 | { 226 | // When we perform a write operation and then immediately read it, we may get the result from the disk buffer and not from the disk surface (confirmed with a broken WD15EADS) 227 | // To be sure that we read from the disk surface, we first write the entire UI block, and only then read from it. 228 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 229 | { 230 | long leftToRead = sectorCount - sectorOffset; 231 | int sectorsToRead = (int)Math.Min(leftToRead, PhysicalDisk.MaximumDirectTransferSizeLBA); 232 | // Clear allocations from previous iteration 233 | GC.Collect(); 234 | GC.WaitForPendingFinalizers(); 235 | byte[] pattern = GetTestPattern(sectorIndex + sectorOffset, sectorsToRead, Disk.BytesPerSector); 236 | try 237 | { 238 | WriteSectors(sectorIndex + sectorOffset, pattern); 239 | } 240 | catch (IOException ex) 241 | { 242 | int errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(ex); 243 | AddToLog("Write failure (Win32 error: {0}) at sectors {1:###,###,###,###,##0}-{2:###,###,###,###,##0}", errorCode, sectorIndex, sectorIndex + sectorsToRead - 1); 244 | return BlockStatus.IOError; 245 | } 246 | 247 | if (Abort) 248 | { 249 | return BlockStatus.Untested; 250 | } 251 | } 252 | 253 | return PerformVerifyTest(sectorIndex, sectorCount); 254 | } 255 | 256 | private BlockStatus PerformWriteTest(long sectorIndex, long sectorCount) 257 | { 258 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 259 | { 260 | int leftToWrite = (int)(sectorCount - sectorOffset); 261 | int sectorsToWrite = (int)Math.Min(leftToWrite, PhysicalDisk.MaximumDirectTransferSizeLBA); 262 | // Clear allocations from previous iteration 263 | GC.Collect(); 264 | GC.WaitForPendingFinalizers(); 265 | byte[] pattern = GetTestPattern(sectorIndex + sectorOffset, sectorsToWrite, Disk.BytesPerSector); 266 | try 267 | { 268 | WriteSectors(sectorIndex + sectorOffset, pattern); 269 | } 270 | catch (IOException ex) 271 | { 272 | int errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(ex); 273 | AddToLog("Write failure (Win32 error: {0}) at sectors {1:###,###,###,###,##0}-{2:###,###,###,###,##0}", errorCode, sectorIndex, sectorIndex + sectorsToWrite - 1); 274 | return BlockStatus.IOError; 275 | } 276 | 277 | if (Abort) 278 | { 279 | return BlockStatus.Untested; 280 | } 281 | } 282 | 283 | return BlockStatus.OK; 284 | } 285 | 286 | private BlockStatus PerformVerifyTest(long sectorIndex, long sectorCount) 287 | { 288 | bool verificationMismatch = false; 289 | for (long sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 290 | { 291 | long leftToRead = sectorCount - sectorOffset; 292 | int sectorsToRead = (int)Math.Min(leftToRead, PhysicalDisk.MaximumDirectTransferSizeLBA); 293 | // Clear allocations from previous iteration 294 | GC.Collect(); 295 | GC.WaitForPendingFinalizers(); 296 | 297 | bool ioErrorOccured; 298 | byte[] buffer = ReadSectors(sectorIndex + sectorOffset, sectorsToRead, out ioErrorOccured); 299 | 300 | if (Abort) 301 | { 302 | return BlockStatus.Untested; 303 | } 304 | 305 | if (ioErrorOccured) 306 | { 307 | return BlockStatus.IOError; 308 | } 309 | 310 | if (buffer == null) 311 | { 312 | return BlockStatus.Damaged; 313 | } 314 | 315 | for (int position = 0; position < sectorsToRead; position++) 316 | { 317 | byte[] pattern = GetTestPattern(sectorIndex + sectorOffset + position, Disk.BytesPerSector); 318 | byte[] sectorBytes = ByteReader.ReadBytes(buffer, position * Disk.BytesPerSector, Disk.BytesPerSector); 319 | if (!ByteUtils.AreByteArraysEqual(pattern, sectorBytes)) 320 | { 321 | verificationMismatch = true; 322 | AddToLog("Verification mismatch at sector {0:###,###,###,###,##0}", sectorIndex + sectorOffset + position); 323 | } 324 | } 325 | } 326 | 327 | if (verificationMismatch) 328 | { 329 | return BlockStatus.Damaged; 330 | } 331 | else 332 | { 333 | return BlockStatus.OK; 334 | } 335 | } 336 | 337 | public void WriteSectors(long sectorIndex, byte[] data) 338 | { 339 | int sectorCount = data.Length / Disk.BytesPerSector; 340 | if (sectorCount > PhysicalDisk.MaximumDirectTransferSizeLBA) 341 | { 342 | // we must write one segment at the time 343 | for (int sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += PhysicalDisk.MaximumDirectTransferSizeLBA) 344 | { 345 | int leftToWrite = sectorCount - sectorOffset; 346 | int sectorsToWrite = (int)Math.Min(leftToWrite, PhysicalDisk.MaximumDirectTransferSizeLBA); 347 | byte[] segment = new byte[sectorsToWrite * Disk.BytesPerSector]; 348 | Array.Copy(data, sectorOffset * Disk.BytesPerSector, segment, 0, sectorsToWrite * Disk.BytesPerSector); 349 | long currentPosition = (sectorIndex + sectorOffset) * Disk.BytesPerSector; 350 | UpdateStatus(currentPosition); 351 | Disk.WriteSectors(sectorIndex + sectorOffset, segment); 352 | } 353 | } 354 | else 355 | { 356 | UpdateStatus(sectorIndex * Disk.BytesPerSector); 357 | Disk.WriteSectors(sectorIndex, data); 358 | } 359 | } 360 | 361 | private static byte[] GetTestPattern(long sectorIndex, int bytesPerSector) 362 | { 363 | return GetTestPattern(sectorIndex, 1, bytesPerSector); 364 | } 365 | 366 | private static byte[] GetTestPattern(long sectorIndex, int sectorCount, int bytesPerSector) 367 | { 368 | byte[] buffer = new byte[sectorCount * bytesPerSector]; 369 | for (int sectorOffset = 0; sectorOffset < sectorCount; sectorOffset++) 370 | { 371 | byte[] pattern = BigEndianConverter.GetBytes(sectorIndex + sectorOffset); 372 | for (int offsetInSector = 0; offsetInSector <= bytesPerSector - 8; offsetInSector += 8) 373 | { 374 | Array.Copy(pattern, 0, buffer, sectorOffset * bytesPerSector + offsetInSector, 8); 375 | } 376 | } 377 | return buffer; 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /HardDiskValidator/MainForm.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016-2020 Tal Aloni . All rights reserved. 2 | * 3 | * You can redistribute this program and/or modify it under the terms of 4 | * the GNU Lesser Public License as published by the Free Software Foundation, 5 | * either version 3 of the License, or (at your option) any later version. 6 | */ 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Drawing; 10 | using System.Text; 11 | using System.Threading; 12 | using System.Windows.Forms; 13 | using DiskAccessLibrary.Win32; 14 | using Utilities; 15 | 16 | namespace HardDiskValidator 17 | { 18 | public partial class MainForm : Form 19 | { 20 | public const int HorizontalBlocks = 50; 21 | public const int VerticalBlocks = 50; 22 | 23 | private DateTime m_startTime; 24 | private BlockStatus[] m_blocks = new BlockStatus[HorizontalBlocks * VerticalBlocks]; 25 | private bool m_isBusy = false; 26 | private DiskTester m_diskTester; 27 | private bool m_isClosing = false; 28 | private List m_log = new List(); 29 | 30 | public MainForm() 31 | { 32 | // Make the GUI ignore the DPI setting 33 | Font = new Font(Font.Name, 8.25f * 96f / CreateGraphics().DpiX, Font.Style, Font.Unit, Font.GdiCharSet, Font.GdiVerticalFont); 34 | InitializeComponent(); 35 | WinFormsUtils.SetFixedClientSize(this, 562, 344); 36 | this.Text += " " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3); 37 | } 38 | 39 | private void MainForm_Load(object sender, EventArgs e) 40 | { 41 | PopulateDiskList(comboDisks); 42 | } 43 | 44 | private void pictureBoxMap_Paint(object sender, PaintEventArgs e) 45 | { 46 | DrawDiskMap(e.Graphics); 47 | } 48 | 49 | private void pictureBoxLegend_Paint(object sender, PaintEventArgs e) 50 | { 51 | DrawLegend(e.Graphics); 52 | } 53 | 54 | private void DrawDiskMap(Graphics graphics) 55 | { 56 | const int BlockWidth = 6; 57 | const int BlockHeight = 5; 58 | 59 | Pen pen = new Pen(Color.Black, 1); 60 | int width = HorizontalBlocks * (BlockWidth + 1); 61 | int height = VerticalBlocks * (BlockHeight + 1); 62 | for (int index = 0; index <= HorizontalBlocks; index++) 63 | { 64 | int x = (BlockWidth + 1) * index; 65 | graphics.DrawLine(pen, x, 0, x, height); 66 | } 67 | 68 | for (int index = 0; index <= VerticalBlocks; index++) 69 | { 70 | int y = (BlockHeight + 1) * index; 71 | graphics.DrawLine(pen, 0, y, width, y); 72 | } 73 | 74 | for (int index = 0; index < m_blocks.Length; index++) 75 | { 76 | int hIndex = index % HorizontalBlocks; 77 | int vIndex = index / HorizontalBlocks; 78 | 79 | int x = 1 + hIndex * (1 + BlockWidth); 80 | int y = 1 + vIndex * (1 + BlockHeight); 81 | SolidBrush brush = new SolidBrush(UIHelper.GetColor(m_blocks[index])); 82 | graphics.FillRectangle(brush, x, y, BlockWidth, BlockHeight); 83 | } 84 | } 85 | 86 | private void DrawLegend(Graphics graphics) 87 | { 88 | DrawLegendEntry(graphics, 1, 8, BlockStatus.OK, "OK"); 89 | DrawLegendEntry(graphics, 1, 24, BlockStatus.OverwriteOK, "Overwrite OK"); 90 | DrawLegendEntry(graphics, 1, 40, BlockStatus.Damaged, "Damaged"); 91 | DrawLegendEntry(graphics, 1, 56, BlockStatus.IOError, "IO Error"); 92 | } 93 | 94 | private void DrawLegendEntry(Graphics graphics, float x, float y, BlockStatus status, string text) 95 | { 96 | const int BlockWidth = 6; 97 | const int BlockHeight = 5; 98 | 99 | Pen pen = new Pen(Color.Black, 1); 100 | SolidBrush brush = new SolidBrush(UIHelper.GetColor(status)); 101 | graphics.DrawRectangle(pen, x, y, BlockWidth + 1, BlockHeight + 1); 102 | graphics.FillRectangle(brush, x + 1, y + 1, BlockWidth, BlockHeight); 103 | Font font = new Font(FontFamily.GenericSansSerif, 8.25f * 96f / graphics.DpiX); 104 | graphics.DrawString(text, font, Brushes.Black, x + 12, y - 3); 105 | } 106 | 107 | private void comboDisks_SelectedIndexChanged(object sender, EventArgs e) 108 | { 109 | int physicalDiskIndex = ((KeyValuePair)comboDisks.SelectedItem).Key; 110 | PhysicalDisk disk = new PhysicalDisk(physicalDiskIndex); 111 | lblSerialNumber.Text = "S/N: " + disk.SerialNumber; 112 | } 113 | 114 | private void MainForm_FormClosing(object sender, FormClosingEventArgs e) 115 | { 116 | if (m_isBusy) 117 | { 118 | e.Cancel = true; 119 | m_diskTester.Abort = true; 120 | m_isClosing = true; 121 | } 122 | } 123 | 124 | private void btnCopyLog_Click(object sender, EventArgs e) 125 | { 126 | if (m_log.Count > 0) 127 | { 128 | StringBuilder builder = new StringBuilder(); 129 | foreach (string message in m_log) 130 | { 131 | builder.AppendLine(message); 132 | } 133 | Clipboard.SetText(builder.ToString()); 134 | } 135 | } 136 | 137 | private void PopulateDiskList(ComboBox comboBox) 138 | { 139 | Thread thread = new Thread(delegate() 140 | { 141 | List disks = PhysicalDiskHelper.GetPhysicalDisks(); 142 | this.Invoke((MethodInvoker)delegate 143 | { 144 | comboBox.Items.Clear(); 145 | comboBox.DisplayMember = "Value"; 146 | comboBox.ValueMember = "Key"; 147 | foreach (PhysicalDisk disk in disks) 148 | { 149 | string title = String.Format("[{0}] {1} ({2})", disk.PhysicalDiskIndex, disk.Description, UIHelper.GetSizeString(disk.Size)); 150 | comboBox.Items.Add(new KeyValuePair(disk.PhysicalDiskIndex, title)); 151 | } 152 | comboBox.SelectedIndex = 0; 153 | comboBox.Enabled = true; 154 | }); 155 | }); 156 | thread.Start(); 157 | } 158 | 159 | private void btnStart_Click(object sender, EventArgs e) 160 | { 161 | if (m_isBusy) 162 | { 163 | btnStart.Enabled = false; 164 | btnStart.Text = "Stopping"; 165 | Thread thread = new Thread(delegate() 166 | { 167 | m_diskTester.Abort = true; 168 | while (m_isBusy) 169 | { 170 | Thread.Sleep(100); 171 | } 172 | if (!m_isClosing) 173 | { 174 | this.Invoke((MethodInvoker)delegate 175 | { 176 | btnStart.Text = "Start"; 177 | btnStart.Enabled = true; 178 | comboDisks.Enabled = true; 179 | chkRead.Enabled = true; 180 | chkReadRewriteVerify.Enabled = true; 181 | chkReadWriteVerifyRestore.Enabled = true; 182 | chkWriteVerify.Enabled = true; 183 | }); 184 | } 185 | }); 186 | thread.Start(); 187 | } 188 | else 189 | { 190 | if (comboDisks.SelectedItem == null) 191 | { 192 | return; 193 | } 194 | int physicalDiskIndex = ((KeyValuePair)comboDisks.SelectedItem).Key; 195 | TestName testName = TestName.Read; 196 | if (chkReadRewriteVerify.Checked) 197 | { 198 | testName = TestName.ReadWipeDamagedRead; 199 | } 200 | else if (chkReadWriteVerifyRestore.Checked) 201 | { 202 | testName = TestName.ReadWriteVerifyRestore; 203 | } 204 | else if (chkWriteVerify.Checked) 205 | { 206 | if ((Control.ModifierKeys & (Keys.Control | Keys.Shift)) == Keys.Control) 207 | { 208 | testName = TestName.Verify; 209 | } 210 | else 211 | { 212 | if ((Control.ModifierKeys & (Keys.Control | Keys.Shift)) == (Keys.Control | Keys.Shift)) 213 | { 214 | testName = TestName.Write; 215 | } 216 | else 217 | { 218 | testName = TestName.WriteVerify; 219 | } 220 | DialogResult dialogResult = MessageBox.Show("This test will erase all existing data on the selected disk, Are you sure?", "Warning", MessageBoxButtons.YesNo); 221 | if (dialogResult == DialogResult.No) 222 | { 223 | return; 224 | } 225 | } 226 | } 227 | btnStart.Text = "Stop"; 228 | comboDisks.Enabled = false; 229 | chkRead.Enabled = false; 230 | chkReadRewriteVerify.Enabled = false; 231 | chkReadWriteVerifyRestore.Enabled = false; 232 | chkWriteVerify.Enabled = false; 233 | Thread thread = new Thread(delegate() 234 | { 235 | m_isBusy = true; 236 | PerformTest(physicalDiskIndex, testName); 237 | m_isBusy = false; 238 | if (m_isClosing) 239 | { 240 | this.Invoke((MethodInvoker)delegate 241 | { 242 | this.Close(); 243 | }); 244 | } 245 | else 246 | { 247 | this.Invoke((MethodInvoker)delegate 248 | { 249 | btnStart.Text = "Start"; 250 | comboDisks.Enabled = true; 251 | chkRead.Enabled = true; 252 | chkReadRewriteVerify.Enabled = true; 253 | chkReadWriteVerifyRestore.Enabled = true; 254 | chkWriteVerify.Enabled = true; 255 | }); 256 | } 257 | }); 258 | thread.Start(); 259 | } 260 | } 261 | 262 | private void PerformTest(int physicalDiskIndex, TestName testName) 263 | { 264 | PhysicalDisk disk = new PhysicalDisk(physicalDiskIndex); 265 | m_startTime = DateTime.Now; 266 | ClearLog(); 267 | AddToLog("Hard Disk Validator {0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3)); 268 | AddToLog("Starting {0} Test", GetTestTitle(testName)); 269 | AddToLog("Disk: {0}, S/N: {1}", disk.Description, disk.SerialNumber); 270 | AddToLog("Disk size: {0} ({1:###,###,###,###,##0} sectors, {2} bytes per sector)", UIHelper.GetSizeString(disk.Size), disk.TotalSectors, disk.BytesPerSector); 271 | if (!(testName == TestName.Read || testName == TestName.Verify)) 272 | { 273 | bool success = disk.ExclusiveLock(); 274 | if (!success) 275 | { 276 | MessageBox.Show("Failed to lock the disk."); 277 | return; 278 | } 279 | 280 | if (Environment.OSVersion.Version.Major >= 6) 281 | { 282 | success = disk.SetOnlineStatus(false, false); 283 | if (!success) 284 | { 285 | disk.ReleaseLock(); 286 | MessageBox.Show("Failed to take the disk offline."); 287 | return; 288 | } 289 | } 290 | } 291 | 292 | m_diskTester = new DiskTester(testName, disk); 293 | m_diskTester.OnStatusUpdate += delegate(long currentPosition) 294 | { 295 | UpdateStatus(currentPosition); 296 | }; 297 | m_diskTester.OnLogUpdate += delegate(string format, object[] args) 298 | { 299 | AddToLog(format, args); 300 | }; 301 | // The last segment might be bigger than the others 302 | long uiBlockSize = disk.TotalSectors / (HorizontalBlocks * VerticalBlocks); 303 | m_blocks = new BlockStatus[HorizontalBlocks * VerticalBlocks]; 304 | this.Invoke((MethodInvoker)delegate 305 | { 306 | pictureBoxMap.Invalidate(); 307 | pictureBoxMap.Update(); 308 | }); 309 | for (int uiBlockIndex = 0; uiBlockIndex < m_blocks.Length; uiBlockIndex++) 310 | { 311 | long sectorIndex = uiBlockIndex * uiBlockSize; 312 | long sectorCount = uiBlockSize; 313 | if (uiBlockIndex == m_blocks.Length - 1) 314 | { 315 | sectorCount = disk.TotalSectors - ((m_blocks.Length - 1) * uiBlockSize); 316 | } 317 | BlockStatus blockStatus = m_diskTester.PerformTest(sectorIndex, sectorCount); 318 | m_blocks[uiBlockIndex] = blockStatus; 319 | this.Invoke((MethodInvoker)delegate 320 | { 321 | pictureBoxMap.Invalidate(); 322 | pictureBoxMap.Update(); 323 | }); 324 | if (m_diskTester.Abort) 325 | { 326 | break; 327 | } 328 | } 329 | 330 | if (testName != TestName.Read) 331 | { 332 | if (Environment.OSVersion.Version.Major >= 6) 333 | { 334 | try 335 | { 336 | disk.SetOnlineStatus(true, false); 337 | } 338 | catch 339 | { 340 | // This may happen if the disk disappeared from the system 341 | MessageBox.Show("Unable to set disk status back to online"); 342 | } 343 | } 344 | disk.ReleaseLock(); 345 | disk.UpdateProperties(); 346 | } 347 | 348 | if (m_diskTester.Abort) 349 | { 350 | AddToLog("Test Aborted"); 351 | } 352 | else 353 | { 354 | AddToLog("Test Completed"); 355 | } 356 | m_diskTester = null; 357 | } 358 | 359 | private void UpdateStatus(long currentPosition) 360 | { 361 | if (InvokeRequired) 362 | { 363 | this.Invoke((MethodInvoker)delegate 364 | { 365 | UpdateStatus(currentPosition); 366 | }); 367 | } 368 | else 369 | { 370 | int totalSeconds = (int)((TimeSpan)(DateTime.Now - m_startTime)).TotalSeconds; 371 | totalSeconds = Math.Max(totalSeconds, 1); 372 | long speed = currentPosition / totalSeconds; 373 | lblSpeed.Text = String.Format("Speed: {0}/s", UIHelper.GetSizeString(speed)); 374 | lblPosition.Text = String.Format("Position: {0:###,###,###,###,##0}", currentPosition); 375 | } 376 | } 377 | 378 | private void MainForm_KeyDown(object sender, KeyEventArgs e) 379 | { 380 | if (e.Control) 381 | { 382 | if (e.Shift) 383 | { 384 | chkWriteVerify.Text = GetTestTitle(TestName.Write); 385 | } 386 | else 387 | { 388 | chkWriteVerify.Text = GetTestTitle(TestName.Verify); 389 | } 390 | } 391 | } 392 | 393 | private void MainForm_KeyUp(object sender, KeyEventArgs e) 394 | { 395 | if (e.Control) 396 | { 397 | chkWriteVerify.Text = GetTestTitle(TestName.Verify); 398 | } 399 | else 400 | { 401 | chkWriteVerify.Text = GetTestTitle(TestName.WriteVerify); 402 | } 403 | } 404 | 405 | private void MainForm_Deactivate(object sender, EventArgs e) 406 | { 407 | chkWriteVerify.Text = GetTestTitle(TestName.WriteVerify); 408 | } 409 | 410 | private void ClearLog() 411 | { 412 | m_log = new List(); 413 | } 414 | 415 | private void AddToLog(string format, params object[] args) 416 | { 417 | string message = String.Format(format, args); 418 | AddToLog(message); 419 | } 420 | 421 | private void AddToLog(string message) 422 | { 423 | string messageFormatted = String.Format("{0}: {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), message); 424 | m_log.Add(messageFormatted); 425 | } 426 | 427 | private static string GetTestTitle(TestName testName) 428 | { 429 | if (testName == TestName.Read) 430 | { 431 | return "Read"; 432 | } 433 | else if (testName == TestName.ReadWipeDamagedRead) 434 | { 435 | return "Read + Wipe Damaged + Read"; 436 | } 437 | else if (testName == TestName.ReadWriteVerifyRestore) 438 | { 439 | return "Read + Write + Verify + Restore"; 440 | } 441 | else if (testName == TestName.WriteVerify) 442 | { 443 | return "Write + Verify"; 444 | } 445 | else if (testName == TestName.Write) 446 | { 447 | return "Write"; 448 | } 449 | else 450 | { 451 | return "Verify"; 452 | } 453 | } 454 | } 455 | } --------------------------------------------------------------------------------