OnTheFly
OnTheFly

Reputation: 2101

How do I query "Size on disk" file information?

I want to reproduce behaviour exhibited in the Windows Explorer -> Properties dialog -> General property page for any given file. Specifically I want to reproduce the exact value of the "Size on disk" field.

Upvotes: 8

Views: 4094

Answers (5)

Ken White
Ken White

Reputation: 125669

As others have said, you need to use GetFileInformationByHandleEx, but it looks like you need to use FILE_STANDARD_INFO or FILE_ID_BOTH_DIR_INFO. The information you want is returned in the AllocationSize member of each, but the second is for directory handles, to list files within instead of the directory itself (note: not recursive, just top-level). To make it easier, FILE_STANDARD_INFO has a Directory boolean, so call it first if you're not sure. According to the documentation for FILE_ID_BOTH_DIR_INFO,

AllocationSize Contains the value that specifies how much space is allocated for the file, in bytes. This value is usually a multiple of the sector or cluster size of the underlying physical device.

This seems to give you the Size on Disk information.

I haven't found a Delphi translation of the FILE_ID_BOTH_DIR_INFO structure. The difficulty would seem to be the final member, WCHAR FileName[1], which is described as:

FileName[1]
Contains the first character of the file name string. This is followed in memory by the remainder of the string.

I'm not sure how this would be handled in Delphi.

Upvotes: 3

kobik
kobik

Reputation: 21242

Since GetCompressedFileSize will return the actual size for both normal/compressed/spares files of any volume type, you can rely on this function to return the File Size on Disk (Windows Explorer is displaying this value as factor of volume Cluster Size), and get the File Size by using GetFileSize function.

From MSDN docs about GetCompressedFileSize:

If the file is not located on a volume that supports compression or sparse files, or if the file is not compressed or a sparse file, the value obtained is the actual file size, the same as the value returned by a call to GetFileSize.

So the logic is described by the following code (tested on Windows XP with FAT32/FAT/CDfs files):

procedure FileSizeEx(const FileName: string; out Size, SizeOnDisk: UINT);
var
  Drive: string;
  FileHandle: THandle;
  SectorsPerCluster,
  BytesPerSector,
  Dummy: DWORD;
  ClusterSize: DWORD;
  SizeHigh, SizeLow: DWORD;
begin
  Assert(FileExists(FileName));
  Drive := IncludeTrailingPathDelimiter(ExtractFileDrive(FileName));
  if not GetDiskFreeSpace(PChar(Drive), SectorsPerCluster, BytesPerSector, Dummy, Dummy) then
    RaiseLastOSError;

  ClusterSize := SectorsPerCluster * BytesPerSector;

  FileHandle := CreateFile(PChar(FileName), 0, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
    nil, OPEN_EXISTING, 0, 0);
  if (FileHandle = INVALID_HANDLE_VALUE) then
    RaiseLastOSError;
  try
    SizeLow := Windows.GetFileSize(FileHandle, @SizeHigh);
    if (GetLastError <> NO_ERROR) and (SizeLow = INVALID_FILE_SIZE) then
      RaiseLastOSError;
    Size := UINT(SizeHigh shl 32 or SizeLow);
  finally
    if (FileHandle <> INVALID_HANDLE_VALUE) then
      CloseHandle(FileHandle);
  end;

  SizeLow := GetCompressedFileSize(PChar(FileName), @SizeHigh);
  if (GetLastError <> NO_ERROR) and (SizeLow = INVALID_FILE_SIZE) then
    RaiseLastOSError;

  SizeOnDisk := UINT(SizeHigh shl 32 or SizeLow);
  if (SizeOnDisk mod ClusterSize) > 0 then
    SizeOnDisk := SizeOnDisk + ClusterSize - (SizeOnDisk mod ClusterSize);
end;

We could check the Get­Volume­Information for compression/sparse support, and then GetFileAttributes to test for FILE_ATTRIBUTE_COMPRESSED or FILE_ATTRIBUTE_SPARSE_FILE, BUT since the GetCompressedFileSize does that internally for us (by Calling NtQueryInformationFile), I see no point in these tests.

Upvotes: 3

LU RD
LU RD

Reputation: 34899

Posting a routine according to David's extract from Raymond's article. Feel free to improve it !

uses
  System.SysUtils, Windows;

function GetClusterSize(Drive: String): integer;
var
  SectorsPerCluster, BytesPerSector, dummy: Cardinal;
begin
  SectorsPerCluster := 0;
  BytesPerSector := 0;
  GetDiskFreeSpace(PChar(Drive), SectorsPerCluster, BytesPerSector, dummy, dummy);

  Result := SectorsPerCluster * BytesPerSector;
end;

function FindSizeOnDisk(Drive: String; AFilename: string): Int64;
var
  VolumeSerialNumber: DWORD;
  MaximumComponentLength: DWORD;
  FileSystemFlags: DWORD;
  HighSize: DWORD;
  FRec: TSearchRec;
  AClusterSize: integer;
  AFileSize, n: Int64;
begin
  Result := 0;
  Drive := IncludeTrailingPathDelimiter(ExtractFileDrive(Drive));
  GetVolumeInformation( PChar(Drive), nil, 0, @VolumeSerialNumber,
    MaximumComponentLength, FileSystemFlags, nil, 0);
  if ((FileSystemFlags AND FILE_FILE_COMPRESSION) <> 0) AND
    ((FileSystemFlags AND (FILE_VOLUME_IS_COMPRESSED OR
    FILE_SUPPORTS_SPARSE_FILES)) <> 0) then
  begin // Compressed or Sparse disk
    Result := GetCompressedFileSize(PChar(AFilename), @HighSize);
    // Not sure if this is correct on a sparse disk ??
  end
  else
  begin
    if (System.SysUtils.FindFirst(AFilename, faAnyFile, FRec) = 0) then
    begin
      AFileSize := FRec.Size;
      AClusterSize := GetClusterSize(Drive);
      n := AFileSize mod AClusterSize;
      if n > 0 then // Round up to nearest cluster size
        Result := AFileSize + (AClusterSize - n)
      else
        Result := AFileSize;
      System.SysUtils.FindClose(FRec);
    end;
  end;
end;

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 612934

Raymond Chen's article on Windows Confidential describes how that value is calculated. The most pertinent paragraph states:

The Size on disk measurement is more complicated. If the drive supports compression (as reported by the FILE_FILE_COMPRESSION flag returned by the Get­Volume­Information function) and the file is compressed or sparse (FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_SPARSE_FILE), then the Size on disk for a file is the value reported by the Get­Compressed­File­Size function. This reports the compressed size of the file (if compressed) or the size of the file minus the parts that were de-committed and logically treated as zero (if sparse). If the file is neither compressed nor sparse, then the Size on disk is the file size reported by the Find­First­File function rounded up to the nearest cluster.

Upvotes: 3

Adriano Repetti
Adriano Repetti

Reputation: 67080

You may use the GetFileInformationByHandleEx function to obtain FILE_COMPRESSION_INFO structure, its CompressedFileSize field is the value you need (same as returned by GetCompressedFileSize).

Upvotes: 2

Related Questions