I'm writing a library to extract information about physical disks, partitions, and volumes on a Windows system (XP or later).
I'm trying to get the capacity of a volume. Here are the approaches I know about and the reason each fails:
GetDiskFreeSpaceEx
-- Affected by user quota.IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
-- Gets size of entire physical disk, even when invoked using a volume handle.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
-- Doesn't account for RAID overhead.IOCTL_DISK_GET_LENGTH_INFO
-- Fails with access denied. (Actually, it requires GENERIC_READ
access, unlike all other queries, and GENERIC_READ
requires administrator access.)IOCTL_STORAGE_READ_CAPACITY
-- Not available on XP, also shares the drawbacks of IOCTL_DISK_GET_LENGTH_INFO
and IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
FSCTL_GET_VOLUME_BITMAP
+ GetFreeDiskSpace
for cluster size -- Requires GENERIC_READ
(admin access) and gives the size of the data area of the filesystem, not the entire volume.IOCTL_DISK_GET_PARTITION_INFO
-- Requires GENERIC_READ
(admin access) and also failed on a USB-attached disk (possibly using superfloppy partitioning)Oddly, the number of clusters from FSCTL_GET_VOLUME_BITMAP
and WMI's CIM_LogicalDisk.Size
property agree, and both are 4096 bytes smaller than the value from IOCTL_DISK_GET_LENGTH_INFO
.
What is the correct way to get volume capacity? Since all the other queries work without administrator access, I'm looking for a least-privilege solution for this too.
To check the total disk space left on your Windows 10 device, select File Explorer from the taskbar, and then select This PC on the left. The available space on your drive will appear under Devices and drives.
Go to File Explorer, and go to "This PC". Look for your default hard drive, which by default is (C:), and from there you can check your total space, and how much you have used.
From the Windows desktop, double-click the My Computer icon. In My Computer, highlight and right-click the drive whose capacity you'd like to determine. In the menu that appears, select Properties. The Properties window displays the used space, free space, and the total capacity of the hard drive or other drives.
Open the System Information file. In the System Information window, click the + symbol next to Components. Click the + next to Storage and click Drives. In the right-side of the window, you see information about the hard drive including its capacity and serial number.
What exactly do you want to get?
1) Physical Disk capacity
OR
2) capacity of the Partition on the Disk
OR
3) capacity of the File System on the Partition
There is PDO for Physical Disk, for it disk.sys creates and attaches FDO (\Device\Harddisk<I>\DR0
- name or \Device\Harddisk<I>\Partition0
- symbolick link, where I disk number in 0,1,2..)
for every Partition on Physical Disk disk.sys creates PDO (\Device\Harddisk<I>\Partition<J>
- (J in {1,2,3..}) - symlink to some \Device\HarddiskVolume<X>
)
1) there are several ways to get Physical Disk capacity:
open any of \Device\Harddisk<I>\Partition<J>
devices (J in {0,1,..} - so disk FDO or any partition PDO)
with (FILE_READ_ACCESS | FILE_WRITE_ACCESS)
and send IOCTL_SCSI_PASS_THROUGH_DIRECT with SCSIOP_READ_CAPACITY
and/or SCSIOP_READ_CAPACITY16
- and we got SCSIOP_READ_CAPACITY
or SCSIOP_READ_CAPACITY16
struct.
READ_CAPACITY_DATA_EX rcd;
SCSI_PASS_THROUGH_DIRECT sptd = {
sizeof(sptd), 0, 0, 0, 0, CDB12GENERIC_LENGTH, 0, SCSI_IOCTL_DATA_IN,
sizeof(rcd), 1, &rcd, 0, {SCSIOP_READ_CAPACITY16}
};
if (0 <= NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptd, sizeof(sptd), &sptd, sizeof(sptd)))
{
DbgPrint("---- SCSIOP_READ_CAPACITY16 ----\n");
rcd.BytesPerBlock = _byteswap_ulong(rcd.BytesPerBlock);
rcd.LogicalBlockAddress.QuadPart = _byteswap_uint64(rcd.LogicalBlockAddress.QuadPart) + 1;
DbgPrint("%I64x %x\n", rcd.LogicalBlockAddress, rcd.BytesPerBlock);
rcd.LogicalBlockAddress.QuadPart *= rcd.BytesPerBlock;
DbgPrint("%I64x %I64u\n", rcd.LogicalBlockAddress.QuadPart, rcd.LogicalBlockAddress.QuadPart);
}
or
READ_CAPACITY_DATA rcd;
SCSI_PASS_THROUGH_DIRECT sptd = {
sizeof(sptd), 0, 0, 0, 0, CDB10GENERIC_LENGTH, 0, SCSI_IOCTL_DATA_IN,
sizeof(rcd), 1, &rcd, 0, {SCSIOP_READ_CAPACITY}
};
if (0 <= NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptd, sizeof(sptd), &sptd, sizeof(sptd)))
{
DbgPrint("---- SCSIOP_READ_CAPACITY ----\n");
rcd.BytesPerBlock = _byteswap_ulong(rcd.BytesPerBlock);
rcd.LogicalBlockAddress = _byteswap_ulong(rcd.LogicalBlockAddress) + 1;
DbgPrint("%x %x\n", rcd.LogicalBlockAddress, rcd.BytesPerBlock);
ULARGE_INTEGER u = {rcd.LogicalBlockAddress};
u.QuadPart *= rcd.BytesPerBlock;
DbgPrint("%I64x %I64u\n", u.QuadPart, u.QuadPart);
}
open any of \Device\Harddisk<I>\Partition<J>
devices with FILE_READ_ACCESS
and send IOCTL_STORAGE_READ_CAPACITY - must be the same result as a) - this request handle ClassReadDriveCapacity
in classpnp.sys wich internal send SCSI request (SCSIOP_READ_CAPACITY
) to disk PDO. this way not worked on XP.
STORAGE_READ_CAPACITY sc;
if (0 <= NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_STORAGE_READ_CAPACITY, 0, 0, &sc, sizeof(sc)))
{
DbgPrint("---- IOCTL_STORAGE_READ_CAPACITY ----\n");
DbgPrint("%I64x %I64x %x \n", sc.DiskLength.QuadPart, sc.NumberOfBlocks.QuadPart, sc.BlockLength);
sc.NumberOfBlocks.QuadPart *= sc.BlockLength;
DbgPrint("%I64x %I64u\n", sc.NumberOfBlocks.QuadPart, sc.NumberOfBlocks.QuadPart);
}
open any of \Device\Harddisk<I>\Partition<J>
with any access and send IOCTL_DISK_GET_DRIVE_GEOMETRY_EX and use DISK_GEOMETRY_EX.DiskSize
. this think the best way. not need any rights and work on XP
DISK_GEOMETRY_EX GeometryEx;
if (0 <= NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, 0, 0, &GeometryEx, sizeof(GeometryEx)))
{
DbgPrint("---- IOCTL_DISK_GET_DRIVE_GEOMETRY ----\n");
ULONG BytesPerCylinder = GeometryEx.Geometry.TracksPerCylinder * GeometryEx.Geometry.SectorsPerTrack * GeometryEx.Geometry.BytesPerSector;
DbgPrint("%I64x == %I64x\n", GeometryEx.Geometry.Cylinders.QuadPart, GeometryEx.DiskSize.QuadPart / BytesPerCylinder);
DbgPrint("%I64x <= %I64x\n", GeometryEx.Geometry.Cylinders.QuadPart * BytesPerCylinder, GeometryEx.DiskSize.QuadPart);
}
open \Device\Harddisk<I>\Partition0
or \Device\Harddisk<I>\Dr0
with FILE_READ_ACCESS
and use IOCTL_DISK_GET_LENGTH_INFO
to get capacity of the Partition on the Disk - open \Device\Harddisk<I>\Partition<J>
(where J in {1,2..} ) or if X letter assigned to partition - \GLOBAL??\X:
and use IOCTL_DISK_GET_LENGTH_INFO. again need FILE_READ_ACCESS
GET_LENGTH_INFORMATION gli;
if (0 <= NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_DISK_GET_LENGTH_INFO, 0, 0, &gli, sizeof(gli)))
{
DbgPrint("---- IOCTL_DISK_GET_LENGTH_INFO ----\n");
DbgPrint("%I64x %I64u\n", gli.Length.QuadPart, gli.Length.QuadPart);
}
to get capacity of the File System on the Partition - open any file (\GLOBAL??\X:\
for example) and use NtQueryVolumeInformationFile(FileFsSizeInformation)
FILE_FS_SIZE_INFORMATION fsi;
if (0 <= NtOpenFile(&hFile, SYNCHRONIZE, &oa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_FREE_SPACE_QUERY|FILE_SYNCHRONOUS_IO_NONALERT))
{
if (0 <= NtQueryVolumeInformationFile(hFile, &iosb, &fsi, sizeof(fsi), FileFsSizeInformation))
{
DbgPrint("%I64x %x %x\n", fsi.TotalAllocationUnits.QuadPart, fsi.SectorsPerAllocationUnit, fsi.BytesPerSector);
fsi.TotalAllocationUnits.QuadPart *= fsi.SectorsPerAllocationUnit * fsi.BytesPerSector;
DbgPrint("%I64x %I64u\n", fsi.TotalAllocationUnits.QuadPart, fsi.TotalAllocationUnits.QuadPart);
}
NtClose(hFile);
}
or use GetDiskFreeSpaceEx - internally it also calls NtQueryVolumeInformationFile( FileFsSizeInformation)
but uses flag FILE_DIRECTORY_FILE
, so as input parameter you can use only directories
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With