Windows 上物理驱动器上的 SCSI 读取 (10)

Posted

技术标签:

【中文标题】Windows 上物理驱动器上的 SCSI 读取 (10)【英文标题】:SCSI Read(10) on a Physical Drive on Windows 【发布时间】:2017-01-04 03:58:56 【问题描述】:

我尝试向 Windows 7 机器上的物理驱动器发出 SCSI Read(10) 命令。下面是我正在使用的代码 sn-p。它失败,错误代码为 87。

void scsi_read() 


 const UCHAR cdb[10] =  0x28, 0, 0, 0, 0, 0, 0, 0, 512, 0 ;
 UCHAR buf[512];
 BYTE senseBuf[196];
 const int SENSE_LENGTH = 196;
 LPCSTR fname = "\\\\.\\E:";
 HANDLE fh; 
 DWORD ioctl_bytes;
 DWORD err = 0;

 SCSI_PASS_THROUGH s = 0;
 memcpy(s.Cdb, cdb, sizeof(cdb));
 s.CdbLength = 10;
 s.DataIn = SCSI_IOCTL_DATA_IN;
 s.TimeOutValue = 30;
 s.Length = sizeof(SCSI_PASS_THROUGH);  
 s.ScsiStatus         = 0x00;
 s.SenseInfoOffset =  senseBuf;
 s.SenseInfoLength  = SENSE_LENGTH;
 s.DataBufferOffset = buf;
 s.DataTransferLength = 512;

 fh = CreateFile("\\\\.\\E:", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,  OPEN_EXISTING, 0, NULL);
 if(fh == INVALID_HANDLE_VALUE) 
    printf("Could not open %s file, error %d\n", fname, GetLastError());
    return (FALSE);
 

 int ret = DeviceIoControl(fh,IOCTL_SCSI_PASS_THROUGH, &s,sizeof(s), //scsiPassThrough.sizeof,
                                &s,
                                sizeof(s),
                                &ioctl_bytes,
                                NULL);

 printf("ret %d",(int)ret);                                    
 if (ret==1) 
    printf("OK");
 
 else 

    err = GetLastError();
    printf("Last error code %u\n", err);
    printf("Return size %d\n", ioctl_bytes);
    printf("Sense data\n");
    int i=0;
    for (i = 0; i < 20; i++) 
        printf("\t%x", senseBuf[i]);
    
    printf("\n");
 
 CloseHandle(fh);

错误:输出中打印了十六进制转储

【问题讨论】:

【参考方案1】:

您收到错误代码 87 - ERROR_INVALID_PARAMETER,因为代码完全错误。

例如:

const UCHAR cdb[10] =  0x28, 0, 0, 0, 0, 0, 0, 0, 512, 0 ;

但是512&gt; 255 (MAXUCHAR) 你这里没有编译器警告吗?

warning C4305: 'initializing': truncation from 'int' to 'const UCHAR'

看看这条线!

s.DataBufferOffset = buf;

来自SCSI_PASS_THROUGH 结构:

DataBufferOffset

包含从这个结构的开头到数据的偏移量 缓冲。偏移量必须遵守数据对齐要求 设备。

所以 offset 到缓冲区,而不是 指针 到缓冲区

为了使用这个正确的代码,你的代码需要是这样的:

struct MY_DATA : SCSI_PASS_THROUGH 

    UCHAR buf[512];
 s;

s.DataBufferOffset = FIELD_OFFSET(MY_DATA, buf);

但最好将SCSI_PASS_THROUGH_DIRECTIOCTL_SCSI_PASS_THROUGH_DIRECT 一起使用

您在运行时需要时硬编码扇区大小 (512)。以及如何初始化CDB ?!?完全不清楚你尝试做什么。

工作代码示例(抱歉,使用 c++ 而不是 c

#define _NTSCSI_USER_MODE_
#include <scsi.h>
#include <ntddscsi.h>

BOOL scsi_read(HANDLE fh, PVOID buf, DWORD cb, ULONGLONG LogicalBlock, ULONG TransferBlocks)

    SCSI_PASS_THROUGH_DIRECT s =  
        sizeof(SCSI_PASS_THROUGH_DIRECT), 0, 0, 0, 0, 0, 0, SCSI_IOCTL_DATA_IN, cb, 30, buf
    ;

    union 
        PUCHAR Cdb;
        CDB::_CDB10* Cdb10;
        CDB::_CDB16* Cdb16;
    ;

    Cdb = s.Cdb;

    if (MAXULONG < LogicalBlock || MAXUSHORT < TransferBlocks)
    
        s.CdbLength = sizeof(CDB::_CDB16);
        Cdb16->OperationCode = SCSIOP_READ16;
        *(ULONGLONG*)Cdb16->LogicalBlock = _byteswap_uint64(LogicalBlock);
        *(ULONG*)Cdb16->TransferLength = _byteswap_ulong(TransferBlocks);
    
    else
    
        s.CdbLength = sizeof(CDB::_CDB10);
        Cdb10->OperationCode = SCSIOP_READ;
        *(ULONG*)&Cdb10->LogicalBlockByte0 = _byteswap_ulong((ULONG)LogicalBlock);
        *(USHORT*)&Cdb10->TransferBlocksMsb = _byteswap_ushort((USHORT)TransferBlocks);
    

    DWORD ioctl_bytes;

    return DeviceIoControl(fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &s, sizeof(s), &s, sizeof(s), &ioctl_bytes, NULL);


BOOL test_scsi_read(PCWSTR fname) 

    BOOL fOk = FALSE;

    HANDLE fh = CreateFileW(fname, GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

    if (fh != INVALID_HANDLE_VALUE) 
    
        DWORD ioctl_bytes;

        DISK_GEOMETRY_EX dg;

        if (DeviceIoControl(fh, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, &dg, sizeof(dg), &ioctl_bytes, 0))
        
            // 16 sectors for example
            ULONG cb = 16 * dg.Geometry.BytesPerSector;

            if (PVOID buf = new CHAR[cb])
            
                // read first 16 sectors
                fOk = scsi_read(fh, buf, cb, 0, 16);

                if (ULONGLONG LogicalBlock = dg.DiskSize.QuadPart / dg.Geometry.BytesPerSector)
                
                    // read last sector
                    fOk = scsi_read(fh, buf, dg.Geometry.BytesPerSector, LogicalBlock - 1, 1);
                

                delete buf;
            
        

        CloseHandle(fh);
    

    return fOk;

    test_scsi_read(L"\\\\?\\e:");

【讨论】:

以上是关于Windows 上物理驱动器上的 SCSI 读取 (10)的主要内容,如果未能解决你的问题,请参考以下文章

确定文件在哪个物理硬盘上?

从物理硬盘读取数据

从物理硬盘读取数据

新建磁盘管理

读取网络驱动器上的文件

如何将特定命令发送到 Linux 中的 scsi 设备?