How do I query "Size on disk" file information?
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.
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 GetVolumeInformation 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 GetCompressedFileSize 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 FindFirstFile function rounded up to the nearest cluster.
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 GetVolumeInformation
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.