1+ //
2+ // DiskTrim 1.0 by Antoni Sawicki and Tomasz Nowak
3+ // Requires Windows 2008 R2 / Windows 8.1 or above
4+ //
5+ // DiskTrim is a small command line application for Windows that allows
6+ // to send ATA TRIM / SCSI UNMAP command directly to an SSD using SCSI
7+ // pass through. It functionis to securely erase contents of an SSD and
8+ // test whether TRIM actually worked. If you just want to test if your
9+ // SSD supports TRIM under Windows without deleting it's contents, you
10+ // can create and mount a small VHDX file and run DiskTrim on the VHDX
11+ // instead of physical disk.
12+ //
13+ #include <windows.h>
14+ #include <stdio.h>
15+ #include <stdlib.h>
16+ #include <wchar.h>
17+ #include <stdarg.h>
18+
19+ typedef struct _CDB_UNMAP {
20+ UCHAR OperationCode ; // 0x42 - SCSIOP_UNMAP
21+ UCHAR Anchor : 1 ;
22+ UCHAR Reserved1 : 7 ;
23+ UCHAR Reserved2 [4 ];
24+ UCHAR GroupNumber : 5 ;
25+ UCHAR Reserved3 : 3 ;
26+ UCHAR AllocationLength [2 ];
27+ UCHAR Control ;
28+ } CDB_UNMAP , * PCDB_UNMAP ;
29+
30+
31+ #pragma pack(1)
32+
33+ typedef struct _UNMAP_BLOCK_DESCRIPTOR {
34+ ULONG64 StartingLba ;
35+ ULONG LbaCount ;
36+ UCHAR Reserved [4 ];
37+ } UNMAP_BLOCK_DESCRIPTOR , * PUNMAP_BLOCK_DESCRIPTOR ;
38+
39+ typedef struct _UNMAP_LIST_HEADER {
40+ USHORT DataLength ;
41+ USHORT BlockDescrDataLength ;
42+ UCHAR Reserved [4 ];
43+ UNMAP_BLOCK_DESCRIPTOR Descriptors [0 ];
44+ } UNMAP_LIST_HEADER , * PUNMAP_LIST_HEADER ;
45+
46+
47+ typedef struct _READ_CAPACITY {
48+ ULONG LBA ;
49+ ULONG BlockLength ;
50+ } READ_CAPACITY , * PREAD_CAPACITY ;
51+
52+ #pragma pack()
53+
54+
55+ typedef struct _SCSI_PASS_THROUGH {
56+ USHORT Length ;
57+ UCHAR ScsiStatus ;
58+ UCHAR PathId ;
59+ UCHAR TargetId ;
60+ UCHAR Lun ;
61+ UCHAR CdbLength ;
62+ UCHAR SenseInfoLength ;
63+ UCHAR DataIn ;
64+ ULONG DataTransferLength ;
65+ ULONG TimeOutValue ;
66+ ULONG_PTR DataBufferOffset ;
67+ ULONG SenseInfoOffset ;
68+ UCHAR Cdb [16 ];
69+ } SCSI_PASS_THROUGH , * PSCSI_PASS_THROUGH ;
70+
71+ #define REVERSE_BYTES_SHORT ( x ) ( ((x & 0xFF) << 8) | (x & 0xFF00) >> 8)
72+ #define REVERSE_BYTES_LONG ( x ) ( ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | (x & 0xFF000000) >> 24)
73+
74+ #define SRB_FLAGS_DATA_IN 0x00000040
75+ #define SRB_FLAGS_DATA_OUT 0x00000080
76+
77+ #define IOCTL_SCSI_BASE 0x00000004
78+ #define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
79+
80+ #define SENSE_INFO_LENGTH 128
81+
82+ #define TEST_PATTERN L"====[Test*Pattern]===="
83+
84+ #define WIDEN2 (x ) L ## x
85+ #define WIDEN (x ) WIDEN2(x)
86+ #define __WDATE__ WIDEN(__DATE__)
87+ #define __WTIME__ WIDEN(__TIME__)
88+
89+ #define USAGE L"Usage: %s [-y] <disk #>\n\nDisk# number can be obtained from:\n"\
90+ L"- diskmgmt.msc\n"\
91+ L"- diskpart (list disk)\n"\
92+ L"- get-disk\n"\
93+ L"- get-physicaldisk | ft deviceid,friendlyname\n\n"
94+
95+ void error (int exit , WCHAR * msg , ...) {
96+ va_list ap , valist ;
97+ WCHAR vaBuff [1024 ]= {'\0' };
98+ WCHAR errBuff [1024 ]= {'\0' };
99+ DWORD err ;
100+
101+ va_start (valist , msg );
102+ _vsnwprintf (vaBuff , sizeof (vaBuff ), msg , valist );
103+ va_end (valist );
104+
105+ wprintf (L"ERROR: %s\n" , vaBuff );
106+ err = GetLastError ();
107+ if (err ) {
108+ FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS , NULL , err , MAKELANGID (LANG_NEUTRAL , SUBLANG_DEFAULT ), errBuff , sizeof (errBuff ) , NULL );
109+ wprintf (L"[%08X] %s\n\n" , err , errBuff );
110+ }
111+ else {
112+ putchar (L'\n' );
113+ }
114+
115+ if (exit )
116+ ExitProcess (1 );
117+ }
118+
119+ int wmain (int argc , WCHAR * argv []) {
120+ HANDLE hDisk ;
121+ WCHAR DevName [64 ]= {'\0' };
122+ OVERLAPPED Ovr = {0 };
123+ WCHAR TestBuff [512 ]= {'\0' };
124+ WCHAR * DiskNo ;
125+ ULONG i ;
126+ DWORD y = 0 ;
127+ wint_t p ;
128+ PSCSI_PASS_THROUGH ScsiPass ;
129+ GET_LENGTH_INFORMATION DiskLengthInfo ;
130+ PVOID Buffer ;
131+ ULONG BufLen ;
132+ ULONG TransferSize ;
133+ PCDB_UNMAP pCDB ;
134+ PUNMAP_LIST_HEADER pUnmapHdr ;
135+ ULONG BytesRet ;
136+ PUCHAR pSenseCode ;
137+ PREAD_CAPACITY pReadCapacity ;
138+ ULONG DiskLbaCount , DiskBlockSize ;
139+
140+ wprintf (L"=[ DiskTrim v1.0 by Antoni Sawicki & Tomasz Nowak, %s %s ]=\n\n" , __WDATE__ , __WTIME__ );
141+
142+
143+ if (argc == 3 ) {
144+ if (wcscmp (argv [1 ], L"-y" )== 0 ) {
145+ DiskNo = argv [2 ];
146+ y = 1 ;
147+ }
148+ else {
149+ error (1 , USAGE , argv [0 ]);
150+ }
151+ }
152+ else if (argc == 2 ) {
153+ DiskNo = argv [1 ];
154+ }
155+ else {
156+ error (1 , USAGE , argv [0 ]);
157+ }
158+
159+ _snwprintf (DevName , sizeof (DevName ), L"\\\\.\\PhysicalDrive%s" , DiskNo );
160+
161+ if ((hDisk = CreateFileW (DevName , GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ | FILE_SHARE_WRITE , NULL , OPEN_EXISTING , FILE_FLAG_NO_BUFFERING , NULL )) == INVALID_HANDLE_VALUE )
162+ error (1 , L"Cannot open %s" , DevName );
163+
164+ if (!DeviceIoControl (hDisk , IOCTL_DISK_GET_LENGTH_INFO , NULL , 0 , & DiskLengthInfo , sizeof (GET_LENGTH_INFORMATION ), & BytesRet , NULL ))
165+ error (1 , L"Error on DeviceIoControl IOCTL_DISK_GET_LENGTH_INFO [%d] " , BytesRet );
166+
167+
168+ if (!y ) {
169+ wprintf (L"WARNING: Contents of your drive an all data will be permanently erased! \n" );
170+ wprintf (L"\nDo you want to erase disk %s, Size: %.1f GB, (y/N) ? " , DiskNo , (float )DiskLengthInfo .Length .QuadPart /1024.0 /1024.0 /1024.0 );
171+ p = getwchar ();
172+ if (p == L'y' )
173+ wprintf (L"All right...\n" );
174+ else
175+ error (1 , L"\rAborting...\n" );
176+ }
177+
178+ //
179+ // Query disk size
180+ //
181+ TransferSize = 36 ;
182+
183+ BufLen = sizeof (SCSI_PASS_THROUGH ) + SENSE_INFO_LENGTH + TransferSize ;
184+
185+ Buffer = malloc (BufLen );
186+ ZeroMemory (Buffer , BufLen );
187+
188+ (PVOID )ScsiPass = Buffer ;
189+
190+ ScsiPass -> Length = sizeof (SCSI_PASS_THROUGH );
191+ ScsiPass -> TargetId = 1 ;
192+ ScsiPass -> PathId = 0 ;
193+ ScsiPass -> Lun = 0 ;
194+ ScsiPass -> CdbLength = 12 ;
195+ ScsiPass -> SenseInfoLength = SENSE_INFO_LENGTH ;
196+ ScsiPass -> SenseInfoOffset = sizeof (SCSI_PASS_THROUGH );
197+ ScsiPass -> DataIn = SRB_FLAGS_DATA_IN ;
198+ ScsiPass -> TimeOutValue = 5000 ;
199+ ScsiPass -> DataTransferLength = TransferSize ;
200+ ScsiPass -> DataBufferOffset = ScsiPass -> SenseInfoOffset + ScsiPass -> SenseInfoLength ;
201+
202+ pSenseCode = (PUCHAR )Buffer + ScsiPass -> SenseInfoOffset ;
203+
204+ (PVOID )pReadCapacity = (PUCHAR )Buffer + ScsiPass -> DataBufferOffset ;
205+
206+ (PVOID )pCDB = ScsiPass -> Cdb ;
207+ pCDB -> OperationCode = 0x25 ;
208+ pCDB -> Anchor = 0 ;
209+ pCDB -> GroupNumber = 0 ;
210+ pCDB -> AllocationLength [0 ] = 0 ;
211+ pCDB -> AllocationLength [1 ] = 0 ;
212+
213+ if (!DeviceIoControl (hDisk , IOCTL_SCSI_PASS_THROUGH , Buffer , BufLen , Buffer , BufLen , & BytesRet , NULL ))
214+ error (1 , L"Error on DeviceIoControl IOCTL_SCSI_PASS_THROUGH" );
215+
216+ DiskLbaCount = REVERSE_BYTES_LONG (pReadCapacity -> LBA );
217+ DiskBlockSize = REVERSE_BYTES_LONG (pReadCapacity -> BlockLength );
218+
219+ /*wprintf(L"SCSI Status: %u\n", ScsiPass->ScsiStatus);
220+ wprintf(L"Sense Code: ");
221+ for (i = 0; i<32; i++)
222+ wprintf(L"%02X ", pSenseCode[i]);
223+ wprintf(L"\n");*/
224+ wprintf (L"%s LBA: %lu, Block: %lu, Size: %.1f GB\n" , DevName , DiskLbaCount , DiskBlockSize , (float )(((float )DiskLbaCount * (float )DiskBlockSize )/1024.0 /1024.0 /1024.0 ) );
225+
226+ free (Buffer );
227+
228+ //
229+ // Uninitialize disk so it doesn't have any partitions
230+ //
231+ if (!DeviceIoControl (hDisk , IOCTL_DISK_DELETE_DRIVE_LAYOUT , NULL , 0 , NULL , 0 , & BytesRet , NULL ))
232+ error (1 , L"Error on DeviceIoControl IOCTL_DISK_DELETE_DRIVE_LAYOUT [%d] " , BytesRet );
233+
234+
235+ //
236+ // Write test pattern
237+ //
238+ ZeroMemory (& Ovr , sizeof (Ovr ));
239+ Ovr .Offset = 0x00 ;
240+ Ovr .OffsetHigh = 0 ;
241+
242+ ZeroMemory (TestBuff , sizeof (TestBuff ));
243+ _snwprintf (TestBuff , sizeof (TestBuff ), TEST_PATTERN );
244+
245+ if (!WriteFile (hDisk , TestBuff , sizeof (TestBuff ), NULL , & Ovr ))
246+ error (1 , L"Error writing test pattern to disk" );
247+
248+ ZeroMemory (TestBuff , sizeof (TestBuff ));
249+
250+ if (!ReadFile ( hDisk , TestBuff , sizeof (TestBuff ), NULL , & Ovr ))
251+ error (1 , L"Error reading disk" );
252+
253+ wprintf (L"Buffer before TRIM: \"%s\"\n" , TestBuff );
254+
255+ if (wcscmp (TestBuff , TEST_PATTERN )!= 0 )
256+ error (1 , L"Unable to write test pattern to disk" );
257+
258+ //
259+ // UNMAP
260+ //
261+ TransferSize = sizeof (UNMAP_LIST_HEADER ) + sizeof (UNMAP_BLOCK_DESCRIPTOR );
262+
263+ BufLen = sizeof (SCSI_PASS_THROUGH ) + SENSE_INFO_LENGTH + TransferSize ;
264+
265+ Buffer = malloc ( BufLen );
266+ ZeroMemory ( Buffer , BufLen );
267+
268+ (PVOID )ScsiPass = Buffer ;
269+
270+ ScsiPass -> Length = sizeof (SCSI_PASS_THROUGH );
271+ ScsiPass -> TargetId = 1 ;
272+ ScsiPass -> PathId = 0 ;
273+ ScsiPass -> Lun = 0 ;
274+ ScsiPass -> CdbLength = 10 ;
275+ ScsiPass -> SenseInfoLength = SENSE_INFO_LENGTH ;
276+ ScsiPass -> SenseInfoOffset = sizeof (SCSI_PASS_THROUGH );
277+ ScsiPass -> DataIn = SRB_FLAGS_DATA_OUT ;
278+ ScsiPass -> TimeOutValue = 5000 ;
279+ ScsiPass -> DataTransferLength = TransferSize ;
280+ ScsiPass -> DataBufferOffset = sizeof (SCSI_PASS_THROUGH ) + ScsiPass -> SenseInfoLength ;
281+
282+ pSenseCode = (PUCHAR )Buffer + ScsiPass -> SenseInfoOffset ;
283+
284+ (PVOID )pCDB = ScsiPass -> Cdb ;
285+ pCDB -> OperationCode = 0x42 ;
286+ pCDB -> Anchor = 0 ;
287+ pCDB -> GroupNumber = 0 ;
288+ pCDB -> AllocationLength [0 ] = (UCHAR )(TransferSize >> 8 );
289+ pCDB -> AllocationLength [1 ] = (UCHAR )TransferSize ;
290+
291+ (PVOID )pUnmapHdr = (PUCHAR )ScsiPass + ScsiPass -> DataBufferOffset ;
292+
293+ pUnmapHdr -> DataLength = REVERSE_BYTES_SHORT (TransferSize - 2 );
294+ pUnmapHdr -> BlockDescrDataLength = REVERSE_BYTES_SHORT (TransferSize - sizeof (UNMAP_LIST_HEADER ));
295+
296+ pUnmapHdr -> Descriptors [0 ].StartingLba = 0 ;
297+ pUnmapHdr -> Descriptors [0 ].LbaCount = REVERSE_BYTES_LONG (DiskLbaCount + 1 );
298+
299+ if (!DeviceIoControl ( hDisk , IOCTL_SCSI_PASS_THROUGH , Buffer , BufLen , Buffer , BufLen , & BytesRet , NULL ))
300+ error (1 , L"Error performing DeviceIoControl IOCTL_SCSI_PASS_THROUGH" );
301+
302+ /*wprintf(L"SCSI Status: %u\n", ScsiPass->ScsiStatus );
303+ wprintf(L"Sense Code: ");
304+ for( i=0; i<32; i++ )
305+ wprintf(L"%02X ", pSenseCode[i] );
306+ wprintf(L"\n");*/
307+
308+ ZeroMemory (TestBuff , sizeof (TestBuff ));
309+
310+ if (!ReadFile (hDisk , TestBuff , sizeof (TestBuff ), NULL , & Ovr ))
311+ error (1 , L"Error reading disk" );
312+
313+ wprintf (L"Buffer after TRIM : \"%s\" [if empty, TRIM worked]\n" , TestBuff );
314+
315+ if (wcscmp (TestBuff , TEST_PATTERN )== 0 )
316+ wprintf (L"ERROR: TRIM didn't seem to work\n" );
317+ else if (wcslen (TestBuff )== 0 )
318+ wprintf (L"Looks like TRIM worked!\n" );
319+
320+ return 0 ;
321+ }
0 commit comments