Get size of volume on Windows

社会主义新天地 提交于 2019-12-04 12:51:26

问题


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.


回答1:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!