Get size of volume on Windows
Asked Answered
U

1

11

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:

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.

Usage answered 7/11, 2013 at 0:49 Comment(21)
On the face of it seems to be impossible without admin access.Lakeesha
Have you tried using WMI, specifically Win32_LogicalDiskRomola
@JonathanPotter: I'd be perfectly accepting an OS that required admin access to get that information on fixed disks, and interactive user for removable media. But Windows evidently made a different design decision, since IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS works perfectly fine for a normal user. So it seems like there's meant to be a way.Usage
@TheVedge: I generally stay far far away from WMI, since it's a real bear to access from C++ due to loose typing. Any example of a PowerShell script to test that quickly?Usage
Nevermind, get-WmiObject win32_logicaldisk seems to be the right command. Now, how do I know whether those numbers are with or without quota?Usage
@TheVedge: WMI also misses several volumes found by FindFirstVolume... the boot volumes which have no drive letter are skipped.Usage
@BenVoigt Right, it only shows mounted partitions. Win32_DiskPartition shows me my system partition, but only shows my physical hard drive (no removable drives)Romola
Since when does GENERIC_READ require admin access? I can understand it being needed for GENERIC_WRITE, but GENERIC_READ??Fitful
@Remy: Being able to read any cluster in the drive/partition/volume would certainly be a privacy risk, don't you think?Usage
@TheVedge: It looks like Win32_Volume / CIM_StorageVolume match up with the volume list I got through the Win32 API. And they have capacity available without elevation (although it is strangely 4096 bytes smaller than the value from IOCTL_DISK_GET_LENGTH_INFO)Usage
Have you tried CreateFile("\\\\.\\...Furnishing
@i486: That would be the first step in using any of the IOCTLs.Usage
Offtopic (flag for deletion, if you wish): Congrats on hitting the 200k!Laborer
@IInspectable: ThanksUsage
@Braiam: I'm confident that those tags are appropriate for questions about reading properties (in particular the size) of disk partitions and RAID volumes.Usage
RAID is not particularly useful here, as it's not a question about raid's, but about the windows storage/volume api. Same with disk partitioning. You are not asking about them, you are asking about how Windows visualize any kind of storage. You are not even defining programmatically raids arrays or partitioning disk. Read the tag description for both.Iata
@Braiam: The disk-partitioning tag descriptions does indicate that not only defining, but also querying attributes is covered. The raid tag doesn't give any usage guidance at all. Based on your belief that Windows storage functions are mutually exclusive with RAID, I find myself wondering if you are unaware of the supported volume types: "five volume types: simple, spanned, striped, mirrored, and striped with parity. Simple, spanned, and striped volumes are non-fault tolerant; mirrored and parity volumes are fault tolerant" docs.microsoft.com/en-us/windows/win32/vds/volume-objectUsage
No, I don't believe that storage functions are mutually exclusive with raid, I believe that the tag is saying that this is a raid question when it's not. It's like you tag with windows, just because you are arguably using Windows.Iata
@Braiam: But I'm querying attributes of volumes which may be fault-tolerant volumes by sending IOCTLs to the volume objects, and some of the known methods fail (giving an undesired result, not by erroring) specifically on fault-tolerant volumes. RAID is quite relevant to that.Usage
Exactly, your question would be a raid question if you knew already the system was a RAID. And even then, you actually don't care about that, but only the "capacity of a volume". In other words, you only want to be able to get the information that windows shows users as a volume, and found several ways to not obtain that.Iata
@Braiam: No, not the information that Windows shows to users. The size usable by a custom filesystem. It's true that "get capacity of a volume" wouldn't be a RAID question if the RAID-ness of the volume was transparent... but it's not. RAID-ness is abstracted away from filesystem operations but not from volume attribute queries.Usage
Q
4

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

Quiff answered 9/8, 2016 at 16:23 Comment(7)
A volume is the block device which the filesystem exists within. In the case of a simple volume, it's the same as a partition. In the case of a RAID mirror, stripe, or span volume, multiple partitions are involved. I do appreciate your effort, but you apparently didn't read the question thoroughly, because (except for IOCTL_SCSI_PASS_THROUGH_DIRECT which is not needed because of IOCTL_DISK_GET_DRIVE_GEOMETRY_EX) I tried all of these and explained what's wrong. For example, your suggested method for reading filesystem size does not, it reads the user quota instead.Usage
@BenVoigt you want got volume capacity from disk view (raw bytes size) or from file system view (bytes in clusters)? - second is usual smallerQuiff
I want the size of the volume, not the size of the filesystem. (For example, if you extend a spanned volume, in order to actually use that extra space the NTFS filesystem has to be resized too) Windows volume management tools such as Disk Management MMC snap-in tend to issue volume and filesystem commands together, but on e.g. Linux one can see that these are actually separate operations and the filesystem can be significantly smaller than the volume (block device).Usage
Also, it's possible for a volume to be contained in (a group of) VHD files instead of in a partition -- but I'm just looking for a solution to the physical case not the virtual one.Usage
@BenVoigt - in this case use IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS - and this any accessQuiff
As mentioned in my question, the problem with that is that it tells the "outer diameter" of the volume -- how much space it consumes in each of the partitions it crosses. I want the "inner diameter" -- the size of the combined block device which is the volume. For a simple volume they're the same, for RAID volumes they're very different. And I don't want to mess around with trying to detect RAID type and update my calculation when different RAID methods are invented, I want the OS to tell me how much space is in that volume. The amount I could use if I made my own filesystem inside.Usage
@BenVoigt but IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS give you several DISK_EXTENT. in case simply Volume - will be only 1 extent. and same result as IOCTL_DISK_GET_LENGTH_INFO on volume partition. are not ? i cannot check this on complex RAID volumsQuiff

© 2022 - 2024 — McMap. All rights reserved.