How can I request information about the "Disk Size" file?

I want to reproduce the behavior displayed in the Windows Explorer dialog β†’ Properties β†’ Shared page for any given file. In particular, I want to reproduce the exact value of the "Size on disk" field.

+6
source share
5 answers

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 required information is returned in the AllocationSize element of each of them, but the second is intended for directory descriptors to list the files instead of the directory itself (note: not recursive, just the top level). To make this easier, FILE_STANDARD_INFO has a Boolean Directory value, so call it first if you are unsure. According to the documentation for FILE_ID_BOTH_DIR_INFO ,

AllocationSize Contains a value that indicates 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 information on Size on Disk .

I did not find the Delphi translation of the FILE_ID_BOTH_DIR_INFO structure. The difficulty seems to lie in the final member, WCHAR FileName[1] , which is described as:

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

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

+2
source

Raymond Chen 's Confidential Windows version of the article describes how this value is calculated. The most appropriate paragraph says:

The measurement size on the disk is more complex. If the disk supports compression (as indicated 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 disk size for the file is the value reported by the GetCompressedFileSize function. This reports the compressed file size (if it is compressed) or the file size minus the parts that were released and logically treated as zero (if sparse). If the file is neither compressed nor sparse, then the size on the disk is the file size reported by the FindFirstFile function, rounded to the nearest cluster.

+3
source

Since GetCompressedFileSize will return the actual size for normal / compressed / spare files of any type of volume, you can rely on this function to return File Size on Disk (Windows Explorer displays this value as a cluster volume factor) and get File Size using GetFileSize .

From the MSDN GetCompressedFileSize 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 the sparse file, the resulting value is the actual file size, the same as the return value by calling GetFileSize.

Thus, 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 GetVolumeInformation to support compression / sparseness, and then GetFileAttributes to check for FILE_ATTRIBUTE_COMPRESSED or FILE_ATTRIBUTE_SPARSE_FILE , BUT, since GetCompressedFileSize does this inside us (by calling NtQueryInformationFile ), I don’t see the point in these.

+3
source

You can use the GetFileInformationByHandleEx function to get the FILE_COMPRESSION_INFO structure, the CompressedFileSize field is the value you need (the same as GetCompressedFileSize returns).

+2
source

Posting a routine on 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; 
0
source

Source: https://habr.com/ru/post/911840/


All Articles