Reputation: 979
I use Lazarus 1.2.2 and Freepascal 2.6.4.
I have a program called QuickHash that hashes files and, when I run it on Linux, it can be used to hash physcial disks too (/dev/sdXX). However, I'd like to add the ability to the Windows version.
I gather that to access physcial devices, like disks, one has to use CreateFile. Specifically, CreateFileW.
So, the user clicks a button which scans the computer for disks and lists them in a List Box. The one the user double clicks is then parsed (ListBox.GetSelectedText) for the string '\.\PhyscialDiskX' and that is assigned to a string variable,
strDiskID := getDiskID(Listbox.GetSelectedText);
That works fine.
I then try to create a handle to that disk :
hSelectedDisk := Windows.CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
Based on this article, specifcally "You must use both the CreateFile() FILE_SHARE_READ and FILE_SHARE_WRITE flags to gain access to the drive" I have also tried the other two combinations below :
hSelectedDisk := CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ AND FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
hSelectedDisk := CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
All three successfully assign a handle. But, the top syntax and the bottom syntax eventually generate an error (explained below). The middle option immediately returns the default initialisation hash for a zero byte file, i.e. DA39... for SHA1.
My problem is I am unable to pass that handle (which is an integer) to the SHA1File and MD5FILE functions of the Freepascal md5 and SHA1 units. They expect a filename, which has to be a string.
So, if I pass it the strDiskID ('\.\PhyscialDiskX') (which defeats the objects of assigning a handle at all) I do get disk activity and the program appears to be working.
strDiskHashValue := SHA1Print(SHA1File(strDiskID));
ShowMessage(strDiskHashValue);
CloseHandle(hSelectedDisk);
But even when run on really small disks like a 500Mb USB drive, it takes many minutes and eventually returns "Run Error 1117" which according to this means
"ERROR_IO_DEVICE
1117 (0x45D)
The request could not be performed because of an I/O device error."
However, I have tried it on several working disks and the error continues.
So, my question, ultimately, is how on earth do I pass that successfully assigned THandle to the hashing functions? (UI have also asked the question at the Lazarus forums but sometimes I get answers here from members who don't see the threads there)
Upvotes: 2
Views: 478
Reputation: 613562
You aren't going to be able to pass volume handles to functions that aren't expecting volume handles. These are very special handles with quite stringent requirements on their use. Not the least of which is that you must read blocks that are sector aligned, and whose sizes are multiples of the sector size.
So the solution is for you to take charge of reading the data. Read it into a buffer, and then pass that buffer to the hashing library. This means that you'll need a hashing library that can be called repeatedly to process new data. All comprehensive hashing libraries will offer such functionality.
The share mode flags are combined with bitwise or:
FILE_SHARE_READ or FILE_SHARE_WRITE
I would create the handle like this:
hSelectedDisk := CreateFileW(PWideChar(strDiskID), FILE_READ_DATA,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
First of all I would concentrate on reading the content of the volume. Once you can do that, hashing will be routine.
It seems from the comments that you are having some trouble writing the hashing code. First of all you need to allocate a buffer that is a multiple of the sector size:
var
Buffer: Pointer;
....
GetMem(Buffer, BufferSize);
Use IOCTL_DISK_GET_DRIVE_GEOMETRY
to find out the sector size. And take note of this text from the documentation:
To read or write to the last few sectors of the volume, you must call DeviceIoControl and specify FSCTL_ALLOW_EXTENDED_DASD_IO. This signals the file system driver not to perform any I/O boundary checks on partition read or write calls. Instead, boundary checks are performed by the device driver.
Now that you have a buffer, you can read and hash the content.
var
ctx: TSHA1Context;
Digest: TSHA1Digest;
BytesRead: DWORD;
....
SHA1Init(ctx);
repeat
if not ReadFile(hSelectedDisk, Buffer^, BufferSize, BytesRead, nil) then
// handle error, raise exception
SHA1Update(ctx, Buffer^, BytesRead);
until BytesRead < BufferSize;
SHA1Final(ctx, Digest);
I've not attempted to compile or test this code. It's not meant to be complete or comprehensive. It is just intended to show you how to tackle the problem.
Upvotes: 4