Search code examples
delphihardwaredisk

How to get physical disk number by drive letter using Delphi


Sorry, I know NOTHING about this subject, so asking for your help. I found this code below on Google, to get physical disk number by drive letter, and despite it works, takes about 4 or 5 seconds to get the result. I'd like to know if there is a faster way and how to do it? Thanks!

function GetPhysicalDiskNumber(Drive: Char): Byte;

  function GetLD(Drive: Char): Cardinal;
  var
    Buffer : String;
  begin
    Buffer := Format('\\.\%s:',[Drive]);
    Result := CreateFile(PChar(Buffer),GENERIC_READ Or GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
    If Result = INVALID_HANDLE_VALUE Then
      begin
      Result := CreateFile(PChar(Buffer),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
    end;
  end;

type
  PDiskInfo = ^TDiskInfo;
  TDiskInfo = record
    BootStatus,
    StartHead    : Byte;
    StartSecClu  : Array[0..1]  Of Byte;
    ParitionType,
    LastHead     : Byte;
    LastSecClu   : Array[0..1]  Of Byte;
    ABSSector,
    TTLSector    : Integer;
    Reserved     : Array[0..47] Of Byte;
    Signature    : Array[0..1]  Of Byte;
  end;
  TDiskExtent = record
    DiskNumber: Cardinal;
    StartingOffset: Int64;
    ExtentLength: Int64;
  end;
  DISK_EXTENT = TDiskExtent;
  PDiskExtent = ^TDiskExtent;
  TVolumeDiskExtents = record
    NumberOfDiskExtents: Cardinal;
    Extents: array[0..0] of TDiskExtent;
  end;
  VOLUME_DISK_EXTENTS = TVolumeDiskExtents;
  PVolumeDiskExtents = ^TVolumeDiskExtents;

const
  FILE_DEVICE_DISK                     = $00000007;
  METHOD_BUFFERED                      = 0;
  FILE_ANY_ACCESS                      = 0;
  IOCTL_DISK_BASE                      = FILE_DEVICE_DISK;
  IOCTL_VOLUME_BASE                    = DWORD('V');
  IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = ((IOCTL_VOLUME_BASE shl 16) or (FILE_ANY_ACCESS shl 14) or (0 shl 2) or METHOD_BUFFERED);

var
  LD : DWORD;
  DiskExtents : PVolumeDiskExtents;
  DiskExtent : TDiskExtent;
  BytesReturned : Cardinal;
begin
  Result := 0;
  LD := GetLD(Drive);
  If LD = INVALID_HANDLE_VALUE Then Exit;
  Try
    DiskExtents := AllocMem(Max_Path);
    DeviceIOControl(LD,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,nil,0,DiskExtents,Max_Path,BytesReturned,nil);
    If DiskExtents^.NumberOfDiskExtents > 0 Then
      begin
      DiskExtent := DiskExtents^.Extents[0];
      Result := DiskExtent.DiskNumber;
    end;
  Finally
    CloseHandle(LD);
  end;
end;

Solution

  • Documentation for CreateFile states:

    • When opening a volume or floppy disk, the dwShareMode parameter must have the FILE_SHARE_WRITE flag.

    The code you're using is missing the flag. The code also has a peculiar characteristic in that it does not notify failure. When CreateFile fails, your GetPhysicalDiskNumber returns '0', suggesting the result is the first disk.

    This is what I think is happening: you're testing on a volume that the system cannot lock against write access and times out while trying to do so (hence the delay). But your function still returns '0', so you think it is working.

    In any case, you need the flag. I would, additionally, raise an exception when CreateFile fails, so that you'd have a clue what is happening.

      function GetLD(Drive: Char): Cardinal;
      var
        Buffer : String;
      begin
        Buffer := Format('\\.\%s:',[Drive]);
        Result := CreateFile(PChar(Buffer), GENERIC_READ,
            FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
        Win32Check(Result <> INVALID_HANDLE_VALUE);
      end;
    

    You may choose to have a silent fail though. In that case you can initially set Result of GetPhysicalDiskNumber to '-1' for instance, and pass on raising an exception for CreateFile and DeviceIoControl.

    The code also fails to release the memory it allocates, that's a leak:

      ...
      try
        DiskExtents := AllocMem(Max_Path);
        try
          Win32Check(DeviceIOControl(LD, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, nil, 0,
              DiskExtents, Max_Path, BytesReturned, nil));
          if DiskExtents^.NumberOfDiskExtents > 0 then
          begin
            DiskExtent := DiskExtents^.Extents[0];
            Result := DiskExtent.DiskNumber;
          end;
        finally
          FreeMem(DiskExtents);
        end;
      finally
        CloseHandle(LD);
      ...