在Windows上使用CreateFileW在物理磁盘上运行Freepascal散列函数

时间:2014-06-11 19:56:53

标签: winapi freepascal

我使用Lazarus 1.2.2和Freepascal 2.6.4。

我有一个名为QuickHash的程序可以散列文件,当我在Linux上运行它时,它也可以用来散列物理磁盘(/ dev / sdXX)。但是,我想添加Windows版本的功能。

我认为要访问物理设备,如磁盘,必须使用CreateFile。具体来说,CreateFileW。

因此,用户单击一个按钮,该按钮扫描计算机中的磁盘并将其列在列表框中。然后解析用户双击的那个(ListBox.GetSelectedText)为字符串' \。\ PhyscialDiskX'并将其分配给字符串变量

  strDiskID := getDiskID(Listbox.GetSelectedText);

工作正常。

然后我尝试创建该磁盘的句柄:

    hSelectedDisk := Windows.CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING,  FILE_FLAG_RANDOM_ACCESS, 0);

基于this article,具体为"您必须同时使用CreateFile()FILE_SHARE_READ和FILE_SHARE_WRITE标志才能访问驱动器"我还尝试了以下其他两种组合:

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);

所有三个成功分配了句柄。但是,顶部语法和底部语法最终会产生错误(如下所述)。中间选项立即返回零字节文件的默认初始化哈希值,即SHA1的DA39 ...。

我的问题是我无法将该句柄(这是一个整数)传递给Freepascal md5和SHA1单元的SHA1File和MD5FILE函数。他们期望一个文件名,必须是一个字符串。

所以,如果我将strDiskID(' \。\ PhyscialDiskX')传递给它(它完全取消了分配句柄的对象),我确实得到了磁盘活动,程序似乎正在运行。

strDiskHashValue := SHA1Print(SHA1File(strDiskID));
ShowMessage(strDiskHashValue);
CloseHandle(hSelectedDisk);

但即使在像500Mb USB驱动器这样的小型磁盘上运行,也需要很长时间才能返回"运行错误1117"根据{{​​3}}表示

"ERROR_IO_DEVICE
1117 (0x45D)
The request could not be performed because of an I/O device error."

但是,我已经在几个工作磁盘上尝试过,错误仍在继续。

所以,我的问题,最终,我是如何将成功分配的THandle传递给散列函数的呢? (UI也在this提出了问题,但有时我会从那些看不到帖子的成员那里得到答案。

1 个答案:

答案 0 :(得分:4)

您无法将卷句柄传递给不期望卷句柄的函数。这些都是非常特殊的手柄,对使用要求非常严格。其中最重要的是您必须读取扇区对齐的块,其大小是扇区大小的倍数。

因此解决方案是让您负责读取数据。将其读入缓冲区,然后将该缓冲区传递给散列库。这意味着您将需要一个可以重复调用以处理新数据的散列库。所有全面的散列库都将提供此类功能。

共享模式标志与按位或:

组合
FILE_SHARE_READ or FILE_SHARE_WRITE

我会像这样创建句柄:

hSelectedDisk := CreateFileW(PWideChar(strDiskID), FILE_READ_DATA,
  FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);

首先,我将专注于阅读卷的内容。一旦你能做到这一点,哈希将是常规的。


从评论中可以看出,您在编写散列代码时遇到了一些麻烦。首先,您需要分配一个扇区大小倍数的缓冲区:

var
  Buffer: Pointer;
....
GetMem(Buffer, BufferSize);

使用IOCTL_DISK_GET_DRIVE_GEOMETRY查找扇区大小。并记下文档中的这篇文章:

  

要读取或写入卷的最后几个扇区,必须调用DeviceIoControl并指定FSCTL_ALLOW_EXTENDED_DASD_IO。这表示文件系统驱动程序不对分区读取或写入调用执行任何I / O边界检查。相反,边界检查由设备驱动程序执行。

现在你有了一个缓冲区,你可以阅读并散列内容。

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);

我没有尝试编译或测试此代码。它并不意味着完整或全面。它只是为了向您展示如何解决这个问题。