INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!
  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Jobs

Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

(OP)
I'm using the Windows API I/O Control to identify specifically which sectors on any disk are occupied by pieces of any particular given file. I have found what I need to use, and am partially done implementing it, but the rest of what I need help with is a bit out of the scope to as a question on StackOverflow.com. Therefore, can someone help me make sense of what I'm doing?

The end goal is to provide a visual display of how a single file is broken up into different sectors of a disk. The visual part is still to come, and I don't want any help with that part. I need to get this data first, before I even think about how to display it. Just for reference, here's a link to my related question on StackOverflow.com: http://stackoverflow.com/questions/12763551/how-sh...

It has come down to using the "DeviceIoControl()" function with the "FSCTL_GET_RETRIEVAL_POINTERS" control code. Using "CreateFile()", I create a handle to the appropriate file. Then, I use that handle in "DeviceIoControl()" to fetch a structure "RETRIEVAL_POINTERS_BUFFER". This structure is supposed to contain the data I need in the record array of "Extents".

What I'm not understanding is why I'm not getting any data back. The extents array is coming back empty. Not only that, but I need to understand once I do have this data, how am I supposed to use it to identify what I'm trying to do? That is, how to use this data to identify the exact sectors being occupied by pieces of any given file.

In the end, I would like to have a list of clusters (array of a record) where each of these listed items represents a piece of that file, and info about the clusters where it's found on the disk. I'm really not educated in the NTFS file system or anything along those lines, all I'm trying to do is give the user a visual demonstration of when and how a file is fragmented on a disk.

Here's what I've put together so far:

CODE

unit JD.DiskPosition;

interface

uses
  Windows, Classes, SysUtils;

type
  PStartingVcnInputBuffer = ^TStartingVcnInputBuffer;
  TStartingVcnInputBuffer = record
    StartingVcn: Int64;
  end;

  PRetrievalPointersBuffer = ^TRetrievalPointersBuffer;
  TRetrievalPointersBuffer = record
    ExtentCount: DWORD;
    StartingVcn: Int64;
    Extents: array [0..0] of record
      NextVcn: Int64;
      Lcn: Int64;
    end;
  end;

  TLogEvent = procedure(Sender: TObject; const Msg: String) of object;

  TDiskControl = class(TObject)
  private
    FOnLog: TLogEvent;
    procedure DoLog(const Msg: String);
  public
    constructor Create;
    destructor Destroy; override;
    procedure DoTest(const Filename: String);
    property OnLog: TLogEvent read FOnLog write FOnLog;
  end;

implementation

{ TDiskControl }

constructor TDiskControl.Create;
begin
  inherited Create;
end;

destructor TDiskControl.Destroy;
begin
  inherited;
end;

procedure TDiskControl.DoLog(const Msg: String);
begin
  if assigned(FOnLog) then
    FOnLog(Self, Msg);
end;

procedure TDiskControl.DoTest(const Filename: String);
var
  hDevice: THandle;
  Input: PStartingVcnInputBuffer;
  Output: PRetrievalPointersBuffer;
  Return: DWORD;
  X: Integer;
begin
  DoLog('CreateFile...');
  hDevice:= CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ,
    nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if hDevice > 0 then begin
    DoLog('CreateFile Success');
    try
      DoLog('DeviceIoControl...');
      if DeviceIoControl(hDevice, FSCTL_GET_RETRIEVAL_POINTERS, Input,
        SizeOf(Input), Output, SizeOf(Output), Return, nil) then
      begin
        DoLog('DeviceIoControl Success');
      end else begin
        DoLog('DeviceIoControl Failure');
      end;
      DoLog('> '+IntToStr(Output.StartingVcn));
      for X := 0 to Length(Output.Extents) - 1 do
        DoLog('> '+IntToStr(X)+': '+IntToStr(Output.Extents[X].Lcn));
    finally
      CloseHandle(hDevice);
    end;
  end else begin
    DoLog('CreateFile Failure');
  end;
end;

end. 

And in my main form, I just have one "TMemo" control called "Log" and an "OnCreate" event handler on the form...

CODE

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  JD.DiskPosition;

type
  TForm1 = class(TForm)
    Log: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    FDisk: TDiskControl;
    procedure GotLog(Sender: TObject; const Msg: String);
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Log.Align:= alClient;
  Show;
  BringToFront;
  Application.ProcessMessages;
  FDisk:= TDiskControl.Create;
  try
    FDisk.OnLog:= GotLog;
    FDisk.DoTest('C:\SomeFile.abc');
  finally
    FDisk.Free;
  end;
end;

procedure TForm1.GotLog(Sender: TObject; const Msg: String);
begin
  Log.Lines.Append(Msg);
end;

end. 

Every time I run this, the function "DeviceIoControl()" returns false, and I get a random number in the returned record structure. Can anyone see what's going wrong here? And help me understand what too look for in this data?

JD Solutions

RE: Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

(OP)
I'm having tons of typos today, "has" not "as"...

JD Solutions

RE: Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

Quote:


What I'm not understanding is why I'm not getting any data back. The extents array is coming back empty. Not only that, but I need to understand once I do have this data, how am I supposed to use it to identify what I'm trying to do? That is, how to use this data to identify the exact sectors being occupied by pieces of any given file.

Decided to work up this one, but to start, your DeviceIOControl call is using pointer values that are unallocated and you aren't indicating proper sizes of the data that it should be pointing to. That's your DeviceIOControl problems in calling it.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.

RE: Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

Here. Pretty rough code, but hopefully it helps you along on this part of what you want to do.

See http://www.wd-3.com/archive/luserland.htm for more information and http://technet.microsoft.com/en-us/sysinternals/bb... for a program to verify (which basically does what you want to do already).

CODE

{$R-}
unit DiskPos;
// sample code to answer this question as prepared by Glenn9999 on tek-tips.com using Delphi 3.
interface

uses
  Windows, Classes, SysUtils;

const
  FILE_DEVICE_FILE_SYSTEM: DWord = $00000009;
  METHOD_NEITHER: DWord = 3;
  FILE_ANY_ACCESS: DWord = 0;

type
  Int64 = TLargeInteger;
  // WINIOCTL.H
  PRetrievalPointersBuffer = ^TRetrievalPointersBuffer;
  TRetrievalPointersBuffer = record
    ExtentCount: DWORD;
// evidently it aligns on 8 byte boundaries?  Should check the real header file to be sure.
    filler: DWord;         
    StartingVcn: Int64;
    Extents: array [0..0] of record
      NextVcn: Int64;
      Lcn: Int64;
    end;
  end;

  PStartingVcnInputBuffer = ^TStartingVcnInputBuffer;
  TStartingVcnInputBuffer = record
    StartingVcn: Int64;
  end;

  TLogEvent = procedure(Sender: TObject; const Msg: String) of object;

  TDiskControl = class(TObject)
  private
    FOnLog: TLogEvent;
    procedure DoLog(const Msg: String);
  public
    constructor Create;
    destructor Destroy; override;
    procedure DoTest(const Filename: String);
    property OnLog: TLogEvent read FOnLog write FOnLog;
  end;

var
  FSCTL_GET_RETRIEVAL_POINTERS: DWord;

implementation

{ TDiskControl }

function CTL_CODE(devicetype,func,method,access:DWord):DWord;
// CTL_CODE function.  Converts values where this is used in the headers.
begin
   Result:=(devicetype SHL 16) or (access SHL 14) or (func SHL 2) or method;
end;

function CompatTest(FilePath: String): Boolean;
// returns whether this is compatible.  FAT32 doesn't play well with this stuff.
var
  ffpath: string;
  dummy: DWord;
  dummyp: PDWord;
  FileSystemFlags: DWord;
  FileSystemName: array[1..5] of char;
begin
  ffpath := ExtractFileDrive(ExpandFileName(FilePath)) + '\';
  dummyp := nil;
  GetVolumeInformation(PChar(ffpath), nil, dummy, dummyp, dummy, FileSystemFlags,
      PChar(@FileSystemName[1]), SizeOf(FileSystemName));
  Result := String(FileSystemName) = 'NTFS'#0; // is it an NTFS drive?
end;

constructor TDiskControl.Create;
begin
  inherited Create;
  // can't use functions in constants in Delphi.
  FSCTL_GET_RETRIEVAL_POINTERS :=
    CTL_CODE(FILE_DEVICE_FILE_SYSTEM,  28, METHOD_NEITHER,  FILE_ANY_ACCESS);
end;

destructor TDiskControl.Destroy;
begin
  inherited;
end;

procedure TDiskControl.DoLog(const Msg: String);
begin
  if assigned(FOnLog) then
    FOnLog(Self, Msg);
  writeln(msg); // my own debugging.
end;

procedure TDiskControl.DoTest(const Filename: String);

var
  hDevice: THandle;
  InputBuf: TStartingVcnInputBuffer;
  OutputBuf: PRetrievalPointersBuffer;
  OutputBufSize: Longint;
  BytesReturned: DWORD;
  X, total: Longint;

  errcode: Longint;
begin
  if not CompatTest(FileName) then
    begin
      Dolog('File System Not Supported');
      exit;    // only works for NTFS
    end;
  DoLog('CreateFile...');

  hDevice:= CreateFile(PChar(Filename), GENERIC_READ or GENERIC_WRITE,
    FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, 0);
  if hDevice <> INVALID_HANDLE_VALUE then
    begin
      DoLog('CreateFile Success');
// whatever we want as long as it's SizeOf(TRetrievalPointersBuffer) or greater
//      OutputBufSize := 8192;
      OutputBufSize := SizeOf(OutputBuf^);

      GetMem(OutputBuf, OutputBufSize);
      try
        DoLog('DeviceIoControl...');
        InputBuf.StartingVcn.QuadPart := 0;
        total := 0;
        repeat
          DeviceIoControl(hDevice, FSCTL_GET_RETRIEVAL_POINTERS, @InputBuf,
               Sizeof(inputbuf), OutputBuf, OutputBufSize, BytesReturned, nil);
          errcode := GetLastError;
          if errcode = ERROR_HANDLE_EOF then
            break
          else
          if errcode in [ERROR_MORE_DATA, NO_ERROR] then
            begin
              DoLog('> Number of Extents: ' + IntToStr(OutputBuf^.ExtentCount));
              // delphi 3 comp being used, FloatToStrF required for that.
              DoLog('> Starting VCN: ' + FloatToStrF(OutputBuf^.StartingVcn.QuadPart, ffGeneral, 30, 30));
              // common C trick, get rid of range checking and can process like this against the above.
              // use for loop in C, but Delphi complains.
              x := 0;
              while x < OutputBuf^.ExtentCount do
                begin
                  DoLog(IntToStr(x) + '> Logical Offset: ' + FloatToStrF(OutputBuf^.Extents[X].Lcn.QuadPart, ffGeneral, 30, 30) );
                  inc(total);
                  inc(x);
                end;
              InputBuf.StartingVCN.QuadPart := OutputBuf^.Extents[X-1].NextVCN.QuadPart;
            end;
         until errcode <> ERROR_MORE_DATA;
         DoLog('Total LCNs are: ' + IntToStr(total));  // put out total number of LCNs to check against.
      finally
        CloseHandle(hDevice);
        FreeMem(OutputBuf);
      end;
    end
  else
    begin
      DoLog('CreateFile Failure');
      writeln('Error: ', SysErrorMessage(GetLastError));
    end;
end;

end. 

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.

RE: Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

From the code above:

Quote:


// evidently it aligns on 8 byte boundaries? Should check the real header file to be sure.

I just checked, there's nothing specific in the header file, but it's not a packed record (to use Delphi parlance), so the StartingVCN TLargeInteger goes on an 8 byte boundary in memory. Hence the need for the extra space (newer delphis might account for this, I don't know :S ).

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.

RE: Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

(OP)
I'm getting "I/O Error 105" after the "CompatTest()" has passed...

JD Solutions

RE: Windows API I/O Control - Retrieving list of disk sectors occupied by a given file

(OP)
Never mind, I figured it out. I had to disable your "WriteLn" debugging. That was all. Thanks, results look very promising now!

JD Solutions

Red Flag This Post

Please let us know here why this post is inappropriate. Reasons such as off-topic, duplicates, flames, illegal, vulgar, or students posting their homework.

Red Flag Submitted

Thank you for helping keep Tek-Tips Forums free from inappropriate posts.
The Tek-Tips staff will check this out and take appropriate action.

Reply To This Thread

Posting in the Tek-Tips forums is a member-only feature.

Click Here to join Tek-Tips and talk with other members!

Resources

Close Box

Join Tek-Tips® Today!

Join your peers on the Internet's largest technical computer professional community.
It's easy to join and it's free.

Here's Why Members Love Tek-Tips Forums:

Register now while it's still free!

Already a member? Close this window and log in.

Join Us             Close