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 requiresGENERIC_READ
access, unlike all other queries, andGENERIC_READ
requires administrator access.)IOCTL_STORAGE_READ_CAPACITY
-- Not available on XP, also shares the drawbacks ofIOCTL_DISK_GET_LENGTH_INFO
andIOCTL_DISK_GET_DRIVE_GEOMETRY_EX
FSCTL_GET_VOLUME_BITMAP
+GetFreeDiskSpace
for cluster size -- RequiresGENERIC_READ
(admin access) and gives the size of the data area of the filesystem, not the entire volume.IOCTL_DISK_GET_PARTITION_INFO
-- RequiresGENERIC_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.
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:
- a)
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);
}
- b)
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);
}
- c)
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);
}
- d)
open \Device\Harddisk<I>\Partition0
or \Device\Harddisk<I>\Dr0
with FILE_READ_ACCESS
and use IOCTL_DISK_GET_LENGTH_INFO
- 2)
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);
}
- 3)
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
来源:https://stackoverflow.com/questions/19825910/get-size-of-volume-on-windows