Skip to content

Commit 55a8693

Browse files
committed
Initial commit
0 parents  commit 55a8693

2 files changed

Lines changed: 331 additions & 0 deletions

File tree

README

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DiskTrim 1.0 by Antoni Sawicki and Tomasz Nowak
2+
Requires Windows 2008 R2 / Windows 8.1 or above
3+
4+
DiskTrim is a small command line application for Windows that allows
5+
to send ATA TRIM / SCSI UNMAP command directly to an SSD using SCSI
6+
pass through. It functionis to securely erase contents of an SSD and
7+
test whether TRIM actually worked. If you just want to test if your
8+
SSD supports TRIM under Windows without deleting it's contents, you
9+
can create and mount a small VHDX file and run DiskTrim on the VHDX
10+
instead of physical disk.

disktrim.c

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
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

Comments
 (0)