├── 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 | 
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 | 
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 | }
--------------------------------------------------------------------------------