10

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • On the face of it seems to be impossible without admin access. – Jonathan Potter Nov 07 '13 at 01:03
  • Have you tried using WMI, specifically [Win32_LogicalDisk](http://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx) – ESG Nov 07 '13 at 01:13
  • @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. – Ben Voigt Nov 07 '13 at 01:19
  • @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? – Ben Voigt Nov 07 '13 at 01:21
  • Nevermind, `get-WmiObject win32_logicaldisk` seems to be the right command. Now, how do I know whether those numbers are with or without quota? – Ben Voigt Nov 07 '13 at 01:23
  • @TheVedge: WMI also misses several volumes found by `FindFirstVolume`... the boot volumes which have no drive letter are skipped. – Ben Voigt Nov 07 '13 at 01:30
  • @BenVoigt Right, it only shows mounted partitions. Win32_DiskPartition shows me my system partition, but only shows my physical hard drive (no removable drives) – ESG Nov 07 '13 at 01:44
  • Since when does `GENERIC_READ` require admin access? I can understand it being needed for `GENERIC_WRITE`, but `GENERIC_READ`?? – Remy Lebeau Nov 07 '13 at 03:01
  • @Remy: Being able to read any cluster in the drive/partition/volume would certainly be a privacy risk, don't you think? – Ben Voigt Nov 07 '13 at 03:02
  • @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`) – Ben Voigt Nov 07 '13 at 03:04
  • Have you tried CreateFile("\\\\.\\... – i486 Nov 19 '14 at 10:16
  • @i486: That would be the first step in using any of the IOCTLs. – Ben Voigt Nov 19 '14 at 14:56
  • 1
    Offtopic (flag for deletion, if you wish): Congrats on hitting the 200k! – IInspectable Aug 09 '16 at 06:57
  • @IInspectable: Thanks – Ben Voigt Aug 09 '16 at 12:54
  • @Braiam: I'm confident that those tags are appropriate for questions about reading properties (in particular the size) of disk partitions and RAID volumes. – Ben Voigt Mar 16 '22 at 22:28
  • 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. – Braiam Mar 17 '22 at 01:36
  • @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" https://docs.microsoft.com/en-us/windows/win32/vds/volume-object – Ben Voigt Mar 17 '22 at 15:24
  • 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. – Braiam Mar 17 '22 at 18:17
  • @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. – Ben Voigt Mar 17 '22 at 18:45
  • 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. – Braiam Mar 17 '22 at 18:48
  • @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. – Ben Voigt Mar 17 '22 at 18:51

1 Answers1

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

Alex P.
  • 3,697
  • 9
  • 45
  • 110
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • 1
    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. – Ben Voigt Aug 09 '16 at 16:48
  • @BenVoigt you want got volume capacity from disk view (raw bytes size) or from file system view (bytes in clusters)? - second is usual smaller – RbMm Aug 09 '16 at 17:00
  • 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). – Ben Voigt Aug 09 '16 at 17:06
  • 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. – Ben Voigt Aug 09 '16 at 17:08
  • @BenVoigt - in this case use IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS - and this any access – RbMm Aug 09 '16 at 17:23
  • 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. – Ben Voigt Aug 09 '16 at 17:28
  • @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 volums – RbMm Aug 09 '16 at 17:34