Как использовать IOCTL_SCSI_MINIPORT через DeviceIoControl из C # .net?

2

Моя задача - реализовать надежное решение для получения серийного номера жесткого диска. К сожалению, метод WMI не является надежным. Поэтому я ищу другое решение.

Я нашел эту небольшую часть программного обеспечения, которая делает именно то, что я хочу реализовать в С#.net. К счастью, исходный код также доступен.

В принципе, я хотел бы реализовать функцию ReadIdeDriveAsScsiDriveInNT из diskid32 в С#.

Как я общаюсь с устройством:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(
    SafeFileHandle device, 
    int inputOutputControlCode, 
    [In] ref sbyte[] inputBuffer, 
    int inputBufferSize,
    [In] [Out] ref sbyte[] outputBuffer, 
    int outputBufferSize, 
    ref uint bytesCount, 
    int overlapped);

public static string GetSerialNumberUsingMiniportDriver(int deviceNumber)
{
    using (var device = OpenScsi(2))
    {
        var bytesReturned = default(uint);
        var sio = new ScsiRequestBlockInputOutputControl();
        var sop = new SendCommandOutParameters();
        var sip = new SendCommandInParameters();
        var buffer = new byte[Marshal.SizeOf(sio) + Marshal.SizeOf(sop) + IdentifyBufferSize];

        sio.HeaderLength = Marshal.SizeOf(sio);
        sio.Timeout = 10000;
        sio.Length = Marshal.SizeOf(sop) + IdentifyBufferSize;
        sio.ControlCode = InputOutputControlSCSIMiniportIdentify;
        sio.Signature = Encoding.ASCII.GetBytes("SCSIDISK".ToCharArray());

        var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sio));
        Marshal.StructureToPtr(sio, ptr, true);
        Marshal.Copy(ptr, buffer, 0, Marshal.SizeOf(sio));

        sip.DriveRegister.CommandRegister = IDEATAIdentify;
        sip.DriveNumber = 0;

        ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sip));
        Marshal.StructureToPtr(sip, ptr, true);
        Marshal.Copy(ptr, buffer, Marshal.SizeOf(sio), Marshal.SizeOf(sip));

        var signedBuffer = new sbyte[buffer.Length];
        Buffer.BlockCopy(buffer, 0, signedBuffer, 0, buffer.Length);

        if (
            !DeviceIoControl(
                device, 
                InputOutputControlSCSIMiniport,
                ref signedBuffer, 
                Marshal.SizeOf(sio) + Marshal.SizeOf(sip) - 1,
                ref signedBuffer, 
                Marshal.SizeOf(sio) + Marshal.SizeOf(sop) + IdentifyBufferSize, 
                ref bytesReturned, 
                0))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        var result = new StringBuilder();

        result.Append(buffer);

        return result.ToString();
    }
}

Как я создаю дескриптор:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(
    string fileName, 
    int desiredAccess, 
    FileShare shareMode, 
    IntPtr securityAttributes, 
    FileMode creationDisposition, 
    FileAttributes flagsAndAttributes, 
    IntPtr templateFile);

private static SafeFileHandle OpenScsi(int scsiNumber)
{
    var device = CreateFile(string.Format(@"\\.\Scsi{0}:", scsiNumber), 0, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
    if (device.IsInvalid)
    {
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    return device;
}

Подписанный signedBuffer содержит точно такие же байты, что и буфер в примере diskid32! Diskid32 возвращает для этого дескриптора \\.\Scsi2: и результат DriveNumber = 0 a, поэтому я использую те же параметры.

Есть разница, когда я создаю дескриптор. Я также пробовал, что делать в diskid32. Без успеха.

Я всегда получаю DeviceIoControl Win32Exception когда я вызываю DeviceIoControl в С#, в котором говорится, что Access denied. У кого-нибудь есть идея?

Теги:
pinvoke
deviceiocontrol

1 ответ

3
Лучший ответ

Основные проблемы заключались в том, что я получил доступ к устройству с неправильным разрешением, а одна из структур имела неправильный макет.

Сначала я загрузил WinDDK, чтобы узнать, как выглядят структуры. Я перевел использованные структуры на C#.

Вот пример из WinDDK\7600.16385.1\inc\api\ntddscsi.h:

/// <summary>
/// The SRB_IO_CONTROL.
/// Define header for I/O control SRB.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct SRB_IO_CONTROL
{
    /// <summary>
    /// The HeaderLength.
    /// </summary>
    public uint HeaderLength;

    /// <summary>
    /// The Signature.
    /// </summary>
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] Signature;

    /// <summary>
    /// The Timeout.
    /// </summary>
    public uint Timeout;

    /// <summary>
    /// The ControlCode.
    /// </summary>
    public uint ControlCode;

    /// <summary>
    /// The ReturnCode.
    /// </summary>
    public uint ReturnCode;

    /// <summary>
    /// The Length.
    /// </summary>
    public uint Length;
}

Я изменил значения разрешений, к которым я обращаюсь к устройству, до 0x80000000 | 0x40000000 0x80000000 | 0x40000000. Это мой метод для открытия дескриптора устройства:

/// <summary>
/// The open scsi.
/// </summary>
/// <param name="scsiNumber">
/// The scsi number.
/// </param>
/// <returns>
/// The <see cref="SafeFileHandle"/>.
/// </returns>
/// <exception cref="Win32Exception">
/// Will be thrown, when the safe file handle is not valid.
/// </exception>
private static SafeFileHandle OpenScsi(int scsiNumber)
{
    var device = FileManagement.CreateFile(
        string.Format(@"\\.\Scsi{0}:", scsiNumber), 
        WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, 
        FileShare.ReadWrite, 
        IntPtr.Zero, 
        FileMode.Open, 
        0, 
        IntPtr.Zero);
    if (device.IsInvalid)
    {
        throw new NativeException(string.Format(@"Error during the creation of a safe file handle for \\.\Scsi{0}:", scsiNumber));
    }

    return device;
}

И, наконец, как мой код похож на сбор серийного номера устройства:

/// <summary>
/// The get serial number using miniport driver.
/// </summary>
/// <param name="busNumber">
/// The bus number.
/// </param>
/// <param name="deviceNumber">
/// The device number.
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
/// <exception cref="NativeException">
/// Throws an excpetion, if the device io control couldn't execute successfully!
/// </exception>
internal static string GetSerialNumberUsingMiniportDriver(int busNumber, byte deviceNumber = 0)
{
    using (var device = OpenScsi(busNumber))
    {
        var bytesReturned = default(uint);
        var sic = new SCSI.SRB_IO_CONTROL();
        var sop = new Disk.SENDCMDOUTPARAMS();
        var sip = new Disk.SENDCMDINPARAMS();
        var id = new ATA.IDENTIFY_DEVICE_DATA();
        var buffer = new byte[Marshal.SizeOf(sic) + Marshal.SizeOf(sop) + Marshal.SizeOf(id)];

        sic.HeaderLength = (uint)Marshal.SizeOf(sic);
        sic.Timeout = 10000;
        sic.Length = (uint)(Marshal.SizeOf(sop) + Marshal.SizeOf(id));
        sic.ControlCode = SCSI.IOCTL_SCSI_MINIPORT_IDENTIFY;

        // disk access signature
        sic.Signature = Encoding.ASCII.GetBytes("SCSIDISK".ToCharArray());

        var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sic));
        Marshal.StructureToPtr(sic, ptr, true);
        Marshal.Copy(ptr, buffer, 0, Marshal.SizeOf(sic));

        sip.irDriveRegs.bCommandReg = (byte)ATA.CTRL_CMDS.ATA_IDENTIFY;
        sip.bDriveNumber = deviceNumber;

        ptr = Marshal.AllocHGlobal(Marshal.SizeOf(sip));
        Marshal.StructureToPtr(sip, ptr, true);
        Marshal.Copy(ptr, buffer, Marshal.SizeOf(sic), Marshal.SizeOf(sip));

        if (
            !FileManagement.DeviceIoControl(
                device, 
                SCSI.IOCTL_SCSI_MINIPORT, 
                buffer, 
                (uint)(Marshal.SizeOf(sic) + Marshal.SizeOf(sip) - 1), 
                buffer, 
                (uint)(Marshal.SizeOf(sic) + Marshal.SizeOf(sop) + Marshal.SizeOf(id)), 
                ref bytesReturned, 
                IntPtr.Zero))
        {
            throw new NativeException("P/invoke error on SCSI MINIPORT IDENTIFY");
        }

        var resultPtr = Marshal.AllocHGlobal(Marshal.SizeOf(id));
        Marshal.Copy(buffer, Marshal.SizeOf(sic) + Marshal.SizeOf(sop), resultPtr, Marshal.SizeOf(id));

        id = (ATA.IDENTIFY_DEVICE_DATA)Marshal.PtrToStructure(resultPtr, typeof(ATA.IDENTIFY_DEVICE_DATA));

        var model = Encoding.ASCII.GetString(id.ModelNumber).Replace('\0', ' ').Trim();
        if (!string.IsNullOrEmpty(model))
        {
            model = model.Swap();

            Logging.Add(
                Message.Type.INFO, 
                string.Format("Found model definition (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}: {2}", busNumber, deviceNumber, model));
        }

        var serial = Encoding.ASCII.GetString(id.SerialNumber).Replace('\0', ' ').Trim();
        if (!string.IsNullOrEmpty(serial))
        {
            serial = serial.Swap();

            Logging.Add(
                Message.Type.INFO, 
                string.Format("Found serial number (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}: {2}", busNumber, deviceNumber, serial));
        }
        else
        {
            Logging.Add(
                Message.Type.INFO, 
                string.Format("Couldn't find serial number (via IOCTL_SCSI_MINIPORT) for storage device #{0}/{1}", busNumber, deviceNumber));
        }

        return serial.Trim();
    }
}

Если вы хотите использовать код, вам необходимо сделать следующее:

  • Реализовать структуры (вы найдете их в WinDDK): SENDCMDOUTPARAMS, SENDCMDINPARAMS, IDENTIFY_DEVICE_DATA и SRB_IO_CONTROL

  • Определите значения const: IOCTL_SCSI_MINIPORT, IOCTL_SCSI_MINIPORT_IDENTIFY и ATA_IDENTIFY

  • Я использовал расширение string для замены символов, которые используются.

Мой обмен строк:

/// <summary>
/// The swap.
/// </summary>
/// <param name="input">
/// The input.
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
/// <exception cref="ArgumentException">
/// Can't swap input, which is null, empty or a white space!
/// </exception>
public static string Swap(this string input)
{
    if (string.IsNullOrEmpty(input) || string.IsNullOrWhiteSpace(input))
    {
        throw new ArgumentException("Can't swap input, which is null, empty or a white space!");
    }

    var characters = input.ToCharArray();
    var returnValue = new StringBuilder();

    for (var i = 0; i < characters.Length; i++)
    {
        if (i % 2 != 0)
        {
            continue;
        }

        if ((i + 1) < characters.Length)
        {
            returnValue.Append(characters[i + 1]);
        }

        returnValue.Append(characters[i]);
    }

    return returnValue.ToString();
}
  • 1
    Вы также можете найти мою библиотеку полезной. scsi.codeplex.com
  • 0
    Отлично выглядит @Mehrdad. Я посмотрю. Спасибо! :-)
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню