SuicideClutchX2
SuicideClutchX2

Reputation: 87

Delphi - Using DeviceIoControl passing IOCTL_DISK_GET_LENGTH_INFO to get flash media physical size (Not Partition)

Alright this is the result of a couple of other questions. It appears I was doing something wrong with the suggestions and at this point have come up with an error when using the suggested API to get the media size. Those new to my problem I am working at the physical disk level, not within the confines of a partition or file system.

What I am doing

I am trying to get the size of a flash card from the first block to the last, boot record partition space and all. While I don't need it to dump the information from a card, I do want dynamic writing abilities. I would like to be able to say, put a marker so far from the end of a card, despite what size it might be. It was suggested that I pass IOCTL_DISK_GET_LENGTH_INFO to DeviceIoControl and at first I had no results but now I am finally getting an error from windows 50.

Source to the project

Here is the pastebin code for the main unit (Delphi 2009) - http://clutchx2.pastebin.com/iMnq8kSx

Here is the application source and executable with a form built to output the status of whats going on - http://www.mediafire.com/?js8e6ci8zrjq0de

Its probably easier to use the download, unless your just looking for problems within the code. I will also paste the code here.

unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TfrmMain = class(TForm)
    edtDrive: TEdit;
    lblDrive: TLabel;
    btnMethod1: TButton;
    btnMethod2: TButton;
    lblSpace: TLabel;
    edtSpace: TEdit;
    lblFail: TLabel;
    edtFail: TEdit;
    lblError: TLabel;
    edtError: TEdit;
    procedure btnMethod1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  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;

var
  frmMain: TfrmMain;

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

implementation

{$R *.dfm}

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;

function GetPD(Drive: Byte): Cardinal;
var
  Buffer : String;
begin
  If Drive = 0 Then
    begin
    Result := INVALID_HANDLE_VALUE;
    Exit;
  end;
  Buffer := Format('\\.\PHYSICALDRIVE%d',[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;

function GetPhysicalDiskNumber(Drive: Char): Byte;
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;

procedure TfrmMain.btnMethod1Click(Sender: TObject);
var
  PD : DWORD;
  CardSize: Int64;
  BytesReturned: DWORD;
  CallSuccess: Boolean;
begin
  PD := GetPD(GetPhysicalDiskNumber(edtDrive.Text[1]));
  If PD = INVALID_HANDLE_VALUE Then
    Begin
      ShowMessage('Invalid Physical Disk Handle');
      Exit;
    End;
  CallSuccess := DeviceIoControl(PD, IOCTL_DISK_GET_LENGTH_INFO, nil, 0, @CardSize, SizeOf(CardSize), BytesReturned, nil);
  if not CallSuccess then
    begin
      edtError.Text := IntToStr(GetLastError());
      edtFail.Text := 'True';
    end
    else edtFail.Text := 'False';
  CloseHandle(PD);
end;

end.

I placed a second method button on the form so I can write a different set of code into the app if I feel like it. Only minimal error handling and safeguards are there is nothing that wasn't necessary for debugging this via source.

Media Type & Interface

I tried this on a Sony Memory Stick using a PSP as the reader because I cant find the adapter for using a duo in my machine. The target is an MS and half of my users use a PSP for a reader half dont. However this should work fine on SD cards and that is a secondary target for my work as well. I tried this on a usb memory card reader and several SD cards.

The Problem

Now that I have fixed my attempt I get an error returned. 50 ERROR_NOT_SUPPORTED The request is not supported.

A similar application

I have found an application that uses this API as well as alot of related functions for what I am trying todo. I am getting ready to look into it the application is called DriveImage and its source is here - http://sourceforge.net/projects/diskimage/

The only thing I have really noticed from that application is there use of TFileStream and using that to get a handle on the physical disk.

Upvotes: 3

Views: 7183

Answers (2)

kludg
kludg

Reputation: 27493

The question is already answered. As an alternative you can use IOCTL_DISK_GET_DRIVE_GEOMETRY to obtain disk size. Here a working code that I used in my project:

const
  IOCTL_DISK_GET_DRIVE_GEOMETRY  = $00070000;

type
{$MINENUMSIZE 4}
  TMediaType = (
    Unknown,                // Format is unknown
    F5_1Pt2_512,            // 5.25", 1.2MB,  512 bytes/sector
    F3_1Pt44_512,           // 3.5",  1.44MB, 512 bytes/sector
    F3_2Pt88_512,           // 3.5",  2.88MB, 512 bytes/sector
    F3_20Pt8_512,           // 3.5",  20.8MB, 512 bytes/sector
    F3_720_512,             // 3.5",  720KB,  512 bytes/sector
    F5_360_512,             // 5.25", 360KB,  512 bytes/sector
    F5_320_512,             // 5.25", 320KB,  512 bytes/sector
    F5_320_1024,            // 5.25", 320KB,  1024 bytes/sector
    F5_180_512,             // 5.25", 180KB,  512 bytes/sector
    F5_160_512,             // 5.25", 160KB,  512 bytes/sector
    RemovableMedia,         // Removable media other than floppy
    FixedMedia,             // Fixed hard disk media
    F3_120M_512             // 3.5", 120M Floppy
  );
{$MINENUMSIZE 1}
  PDiskGeometry = ^TDiskGeometry;
  TDiskGeometry = packed record
    Cylinders: int64;
    MediaType: TMediaType;
    TracksPerCylinder: DWORD;
    SectorsPerTrack: DWORD;
    BytesPerSector: DWORD;
  end;

var
  H: THandle;
  BytesReturned: DWORD;
  DG: TDiskGeometry;
  DSize: int64;

begin
  H:= CreateFile(PChar('\\.\G:'), GENERIC_READ,
    FILE_SHARE_WRITE or FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if Handle = INVALID_HANDLE_VALUE then
    raise Exception.Create('OOps!');
  if not DeviceIOControl(H, IOCTL_DISK_GET_DRIVE_GEOMETRY, nil, 0,
    @DG, SizeOf(TDiskGeometry), BytesReturned, nil) then
      raise Exception.Create('OOps #2!');
  DSize:= DG.Cylinders * DG.TracksPerCylinder;
  DSize:= DSize * (DG.SectorsPerTrack * DG.BytesPerSector);
  ShowMessage(IntToStr(DSize));
end;

Upvotes: 3

ThievingSix
ThievingSix

Reputation: 1064

I see what might be happening here.

Try this:

For every "FILE_SHARE_READ", change it to "FILE_SHARE_WRITE Or FILE_SHARE_READ"

From msdn:

"If you want to open a volume using \.\X:, you must use FILE_SHARE_WRITE | FILE_SHARE_READ, not just FILE_SHARE_WRITE. If you omit FILE_SHARE_READ, you'll get ERROR_NOT_SUPPORTED on most volumes"

http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx

EDIT:

Your going to hate this, but the real reason it's failing is because your define for IOCTL_DISK_GET_LENGTH_INFO is wrong. Replace it with this:

((IOCTL_DISK_BASE shl 16) or (FILE_READ_ACCESS shl 14) or ($0017 shl 2) or METHOD_BUFFERED);

Which turns out to be 0x7405C not 0x80070017

Upvotes: 5

Related Questions