winapi - 在 Windows 上获取卷的大小

标签 winapi disk-partitioning raid

我正在编写一个库来提取有关 Windows 系统(XP 或更高版本)上的物理磁盘、分区和卷的信息。

我正在尝试获取卷的容量。以下是我所知道的方法以及每种方法失败的原因:

  • GetDiskFreeSpaceEx -- 受用户配额影响。
  • IOCTL_DISK_GET_DRIVE_GEOMETRY_EX -- 获取整个物理磁盘的大小,即使使用卷句柄调用也是如此。
  • IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS -- 不考虑 RAID 开销。
  • IOCTL_DISK_GET_LENGTH_INFO -- 访问被拒绝而失败。 (实际上,它需要 GENERIC_READ 访问权限,与所有其他查询不同,GENERIC_READ 需要管理员访问权限。)
  • IOCTL_STORAGE_READ_CAPACITY -- 在 XP 上不可用,也有 IOCTL_DISK_GET_LENGTH_INFO 的缺点和 IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
  • FSCTL_GET_VOLUME_BITMAP + GetFreeDiskSpace集群大小 -- 需要 GENERIC_READ (管理员访问)并给出文件系统数据区域的大小,而不是整个卷。
  • IOCTL_DISK_GET_PARTITION_INFO -- 需要 GENERIC_READ (管理员访问)并且在 USB 连接的磁盘上也失败了(可能使用 super 软盘分区)

  • 奇怪的是,来自 FSCTL_GET_VOLUME_BITMAP 的簇数和 WMI 的 CIM_LogicalDisk.Size属性一致,并且两者都比 IOCTL_DISK_GET_LENGTH_INFO 中的值小 4096 字节.

    获得卷容量的正确方法是什么?由于所有其他查询都无需管理员访问权限即可工作,因此我也在为此寻找最低权限的解决方案。

    最佳答案

    你到底想得到什么?

  • 1) 物理磁盘容量

  • 2) 磁盘分区的容量

  • 3) 分区上文件系统的容量

  • 物理磁盘有 PDO,因为它 disk.sys 创建并附加 FDO(\Device\Harddisk<I>\DR0 - 名称或 \Device\Harddisk<I>\Partition0 - 符号链接(symbolic link),其中我的磁盘编号在 0,1,2..)

    为物理磁盘上的每个分区 disk.sys 创建 PDO(\Device\Harddisk<I>\Partition<J> -({1,2,3..} 中的 J)-指向某些 \Device\HarddiskVolume<X> 的符号链接(symbolic link))

    1)有几种方法可以获得物理磁盘容量:
  • 一)

  • 打开任何\Device\Harddisk<I>\Partition<J>设备({0,1,..} 中的 J - 所以磁盘 FDO 或任何分区 PDO)
    (FILE_READ_ACCESS | FILE_WRITE_ACCESS)并发送IOCTL_SCSI_PASS_THROUGH_DIRECTSCSIOP_READ_CAPACITY和/或 SCSIOP_READ_CAPACITY16 - 我们得到了SCSIOP_READ_CAPACITYSCSIOP_READ_CAPACITY16结构。
    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);
    }
    

    或者
        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)

  • 打开任何\Device\Harddisk<I>\Partition<J>带有 FILE_READ_ACCESS 的设备并发送IOCTL_STORAGE_READ_CAPACITY - 必须与 a) 结果相同 - 此请求句柄 ClassReadDriveCapacityclasspnp.sys内部发送 SCSI 请求 (SCSIOP_READ_CAPACITY) 到磁盘 PDO。这种方式不适用于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)

  • 打开任何\Device\Harddisk<I>\Partition<J>具有任何访问权限并发送IOCTL_DISK_GET_DRIVE_GEOMETRY_EX并使用 DISK_GEOMETRY_EX.DiskSize .这是认为最好的方法。不需要任何权利和在 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)

  • 打开\Device\Harddisk<I>\Partition0\Device\Harddisk<I>\Dr0FILE_READ_ACCESS并使用 IOCTL_DISK_GET_LENGTH_INFO
  • 2)

  • 获取磁盘上分区的容量 - 打开 \Device\Harddisk<I>\Partition<J> (其中 J in {1,2..} )或者如果 X 字母分配给分区 - \GLOBAL??\X:并使用 IOCTL_DISK_GET_LENGTH_INFO .再次需要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)

  • 要获取分区上文件系统的容量 - 打开任何文件(例如 \GLOBAL??\X:\)并使用 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);
    }
    

    或使用 GetDiskFreeSpaceEx - 在内部它也调用 NtQueryVolumeInformationFile( FileFsSizeInformation)但使用标志 FILE_DIRECTORY_FILE ,因此作为输入参数,您只能使用目录

    关于winapi - 在 Windows 上获取卷的大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19825910/

    相关文章:

    linux - 如何找出目录或文件所在的挂载/分区? (Linux 服务器)

    windows - 如何从 Windows 应用程序监视 Intel Controller 上 RAID 阵列的状态?

    apache-kafka - 在大量读取期间 kafka 磁盘 hault 写入并导致 kafka 生产者中的 "queue full"错误

    c++ - 如何在 C++ 中使用 HRESULT 条件检查

    c++ - 使用 C++ 连接到 PC 的设备检测

    c - 在 Win32 中使用 C 语言中的扩展 ASCII 归类表

    linux - 如何使用终端而不是应用程序在VM中扩展磁盘?

    windows - Diskpart脚本删除所有分区

    hadoop - hadoop中使用哪种JBOD?和COD与hadoop?

    c++ - FindFirstFileW 通配符匹配