none
DeviceIoControl with IOCTL_SCSI_PASS_THROUGH_DIRECT for PhysicalDrive devices sometimes fails with I/O errors and eventually deadlocks. RRS feed

  • Question

  • You cannot vote on your own post

    I am using DeviceIoControl with the IOCTL_SCSI_PASS_THROUGH_DIRECT control code in order to send inquiries to SCSI devices (physical drives only) to retrieve specific SCSI pages.

    First, the physical drives are discovered by using QueryDosDeviceA and looking for PhysicalDrive entries.
    For each device, I then send a standard inquiry request to determine the vendor and find out whether or not I need to query any more SCSI pages.
    Next, if necessary, I send 2-5 more inquiries (depending on the device identification).

    Throughout this process, occasionally I/O failures will occur: 0x0000045D.  Not always, but sometimes when this error occurs, the next inquiry to the device will cause a deadlock and DeviceIoControl will never return.

    To make matters 10 times worse:
    1. Once this occurs, the problem is SYSTEM WIDE.  Any process that attempts to send a SCSI inquiry to the device in question will deadlock.
    2. Any process deadlocked in this manner cannot be killed.  Not with the Task Manager or with taskkill /F on an elevated command line (thought he latter will claim it was successful, it is not).
    3. The only way to fix this problem is to reboot the server.

    The physical drive that these I/O errors (and eventual deadlocks) occur on is not always the same.

    I am able to very easily reproduce this behavior by brute forcing reads over and over of all (17) devices (with around 100ms of sleep in between each set of reads).  It's guaranteed to occur.  Sometimes within seconds, other times within several minutes (10 minutes is the longest I've ever seen it last).

    Below is an example of the code I am using. 

    Before anyone asks:
    1. I have tried with no sharing enabled (that one lasted the longest at about 10 minutes).
    2. The alignment mask is 0 for all devices, so for the code example below that is not an issue.

    Does anyone have any ideas?  I'm basically dead in the water here.  I don't think I'm doing anything wrong with the below code, but clearly something is not right, and this particular part of the API is not the easiest to find information about.

    #pragma pack(1)
    #define SCSI_RESPONSE_LENGTH (255)
    struct SCSI_RESPONSE
    {
        unsigned char ucaResponse[SCSI_RESPONSE_LENGTH]; 
    };
    #define SCSI_SENSE_BUFFER_LENGTH (32)
    struct SCSI_SENSE_BUFFER
    {
        unsigned char ucaSense[SCSI_SENSE_BUFFER_LENGTH];
    };
    #pragma pack()
    #define SCSI_CDB_OPCODE_IDX                     (0)
    #define SCSI_CDB_INQUIRY_FLAGS_IDX              (1)
    #define SCSI_CDB_INQUIRY_PAGE_IDX               (2)
    #define SCSI_CDB_INQUIRY_SUB_PAGE_IDX           (3) // Response size MSB for some operations.
    #define SCSI_CDB_INQUIRY_RESPONSE_SIZE_LSB_IDX  (4)
    #define SCSI_CDB_INQUIRY_TERMINATOR_IDX         (5)
    #define SCSI_OPCODE_INQUIRY (0x12)
    #define SCSI_INQUIRY_CMD_LENGTH (6)
    #define SCSI_VPD_FLAG (0x01)
    #define SCSI_CDB_TERMINATOR (0x00)
    struct SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER 
    {
        SCSI_PASS_THROUGH_DIRECT    sptd;     // Passthrough data (contains CDB).
        unsigned long               ulFiller; // Realign buffer to DWORD boundary.
        SCSI_SENSE_BUFFER           sb;       // Buffer for returned sense data.
    };
    
    bool DoSCSIPageInquiry(const char *pszDevicePath, unsigned char ucPage, unsigned char ucSubPage, bool bVPD, SCSI_RESPONSE &response)
    {
        HANDLE hDevice = ::CreateFileA(pszDevicePath,
                               GENERIC_READ | GENERIC_WRITE,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL,
                               OPEN_EXISTING,
                               0,
                               NULL);
        if(INVALID_HANDLE_VALUE==hDevice)
            return false;
        SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER pt = {0}; 
        pt.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
        pt.sptd.PathId = 0;
        pt.sptd.TargetId = 1;
        pt.sptd.Lun = 0;
        pt.sptd.CdbLength = SCSI_INQUIRY_CMD_LENGTH;
        pt.sptd.DataIn = SCSI_IOCTL_DATA_IN;
        pt.sptd.SenseInfoLength = SCSI_SENSE_BUFFER_LENGTH;
        pt.sptd.DataTransferLength = sizeof(response);
        pt.sptd.TimeOutValue = 1; 
        pt.sptd.DataBuffer = &response;
        pt.sptd.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, sb);
        pt.sptd.Cdb[SCSI_CDB_OPCODE_IDX]                    = SCSI_OPCODE_INQUIRY;
    
        pt.sptd.Cdb[SCSI_CDB_INQUIRY_FLAGS_IDX]             = (ucPage!=0x00 || bVPD) ? SCSI_VPD_FLAG : 0x00;
    
        pt.sptd.Cdb[SCSI_CDB_INQUIRY_PAGE_IDX]              = ucPage;
        pt.sptd.Cdb[SCSI_CDB_INQUIRY_SUB_PAGE_IDX]          = ucSubPage;
        pt.sptd.Cdb[SCSI_CDB_INQUIRY_RESPONSE_SIZE_LSB_IDX] = SCSI_RESPONSE_LENGTH;
        pt.sptd.Cdb[SCSI_CDB_INQUIRY_TERMINATOR_IDX]        = SCSI_CDB_TERMINATOR;
    
        DWORD dwError = ERROR_SUCCESS;
        DWORD dwOutSize = 0;
        if(!::DeviceIoControl(hDevice,
            IOCTL_SCSI_PASS_THROUGH_DIRECT,
            &pt, 
            sizeof(pt),
            &pt,
            sizeof(pt),
            &dwOutSize,
            NULL))
        {
            dwError = ::GetLastError();
        }
        CloseHandle(hDevice);
        return dwError==ERROR_SUCCESS && pt.sptd.SenseInfoLength==0;
    }
    Wednesday, August 19, 2015 7:28 PM