Получить размер тома в Windows

Я пишу библиотеку для извлечения информации о физических дисках, разделах и томах в системе 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 (возможно, с использованием супердискеты)

Как ни странно, число кластеров из FSCTL_GET_VOLUME_BITMAP и свойство WMI CIM_LogicalDisk.Size совпадают, и оба на 4096 байт меньше, чем значение из IOCTL_DISK_GET_LENGTH_INFO.

Каков правильный способ получить объемную емкость? Поскольку все остальные запросы работают без доступа администратора, я и для этого ищу решение с минимальными привилегиями.


person Ben Voigt    schedule 07.11.2013    source источник
comment
На первый взгляд это кажется невозможным без доступа администратора.   -  person Jonathan Potter    schedule 07.11.2013
comment
Пробовали ли вы использовать WMI, в частности Win32_LogicalDisk   -  person ESG    schedule 07.11.2013
comment
@JonathanPotter: я бы вполне согласился с ОС, которая требует доступа администратора для получения этой информации на фиксированных дисках и интерактивного пользователя для съемных носителей. Но Windows, очевидно, приняла другое дизайнерское решение, поскольку IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS прекрасно работает для обычного пользователя. Так что, похоже, должен быть способ.   -  person Ben Voigt    schedule 07.11.2013
comment
@TheVedge: я обычно держусь подальше от WMI, так как доступ с C ++ - настоящий медведь из-за небрежного набора текста. Есть ли пример скрипта PowerShell для быстрого тестирования?   -  person Ben Voigt    schedule 07.11.2013
comment
Неважно, get-WmiObject win32_logicaldisk кажется правильной командой. Теперь, как я узнаю, с квотой эти числа или без?   -  person Ben Voigt    schedule 07.11.2013
comment
@TheVedge: WMI также пропускает несколько томов, найденных FindFirstVolume ... загрузочные тома, у которых нет буквы диска, пропускаются.   -  person Ben Voigt    schedule 07.11.2013
comment
@BenVoigt Правильно, он показывает только смонтированные разделы. Win32_DiskPartition показывает мне мой системный раздел, но показывает только мой физический жесткий диск (без съемных дисков)   -  person ESG    schedule 07.11.2013
comment
С каких пор GENERIC_READ требуется доступ администратора? Я понимаю, что это нужно для GENERIC_WRITE, но GENERIC_READ??   -  person Remy Lebeau    schedule 07.11.2013
comment
@Remy: Возможность чтения любого кластера на диске/разделе/томе, безусловно, представляет угрозу для конфиденциальности, не так ли?   -  person Ben Voigt    schedule 07.11.2013
comment
@TheVedge: Похоже, Win32_Volume / CIM_StorageVolume совпадают со списком томов, который я получил через Win32 API. И емкость у них доступная без повышения прав (хотя она как ни странно на 4096 байт меньше, чем значение из IOCTL_DISK_GET_LENGTH_INFO)   -  person Ben Voigt    schedule 07.11.2013
comment
Вы пробовали CreateFile(\\\\.\\...   -  person i486    schedule 19.11.2014
comment
@ i486: Это был бы первый шаг в использовании любого из IOCTL.   -  person Ben Voigt    schedule 19.11.2014
comment
Оффтопик (отметьте для удаления, если хотите): Поздравляю с попаданием в 200к!   -  person IInspectable    schedule 09.08.2016
comment
@IInspectable: спасибо   -  person Ben Voigt    schedule 09.08.2016


Ответы (1)


Что именно вы хотите получить?

  • 1) Емкость физического диска

    OR

  • 2) емкость раздела на диске

    OR

  • 3) емкость файловой системы на разделе

Для физического диска есть PDO, для него disk.sys создает и прикрепляет FDO (\Device\Harddisk<I>\DR0 - имя или \Device\Harddisk<I>\Partition0 - символическая ссылка, где I номер диска в 0,1,2..)

для каждого раздела на физическом диске disk.sys создает PDO (\Device\Harddisk<I>\Partition<J> - (J в {1,2,3..}) - символическая ссылка на некоторые \Device\HarddiskVolume<X> )

1) есть несколько способов получить емкость Физического диска:

  • a)

откройте любое из \Device\Harddisk<I>\Partition<J> устройств (J в {0,1,..} - таким образом, FDO диска или PDO любого раздела) с помощью (FILE_READ_ACCESS | FILE_WRITE_ACCESS) и отправьте IOCTL_SCSI_PASS_THROUGH_DIRECT с SCSIOP_READ_CAPACITY и/или SCSIOP_READ_CAPACITY16 - и мы получили SCSIOP_READ_CAPACITY или SCSIOP_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);
}

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)

откройте любое из \Device\Harddisk<I>\Partition<J> устройств с помощью FILE_READ_ACCESS и отправьте IOCTL_STORAGE_READ_CAPACITY — должен быть тот же результат, что и a) — этот дескриптор запроса ClassReadDriveCapacity в classpnp.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>\Dr0 с помощью FILE_READ_ACCESS и используйте IOCTL_DISK_GET_LENGTH_INFO

  • 2)

чтобы получить емкость раздела на диске - откройте \Device\Harddisk<I>\Partition<J> (где J в {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, поэтому в качестве входного параметра вы можете использовать только каталоги

person RbMm    schedule 09.08.2016
comment
Том — это блочное устройство, в котором существует файловая система. В случае простого тома это то же самое, что и раздел. В случае зеркального, чередующегося или составного тома RAID задействовано несколько разделов. Я ценю ваши усилия, но вы, видимо, не внимательно прочитали вопрос, потому что (кроме IOCTL_SCSI_PASS_THROUGH_DIRECT, который не нужен из-за IOCTL_DISK_GET_DRIVE_GEOMETRY_EX) я попробовал все это и объяснил, что не так. Например, предложенный вами метод чтения размера файловой системы не считывает, а вместо этого считывает пользовательскую квоту. - person Ben Voigt; 09.08.2016
comment
@BenVoigt вы хотите получить объем тома из представления диска (необработанный размер байтов) или из представления файловой системы (байты в кластерах)? - вторая обычно меньше - person RbMm; 09.08.2016
comment
Мне нужен размер тома, а не размер файловой системы. (Например, если вы расширяете составной том, чтобы фактически использовать это дополнительное пространство, файловая система NTFS также должна быть изменена.) Инструменты управления томами Windows, такие как оснастка MMC «Управление дисками», как правило, выдают команды тома и файловой системы вместе, но например Linux видно, что это фактически отдельные операции и файловая система может быть значительно меньше, чем том (блочное устройство). - person Ben Voigt; 09.08.2016
comment
Кроме того, том может содержаться в (группе) файлов VHD, а не в разделе, но я просто ищу решение для физического случая, а не для виртуального. - person Ben Voigt; 09.08.2016
comment
@BenVoigt - в этом случае используйте IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS - и это любой доступ - person RbMm; 09.08.2016
comment
Как упоминалось в моем вопросе, проблема заключается в том, что он сообщает внешний диаметр тома - сколько места он занимает в каждом из разделов, которые он пересекает. Мне нужен внутренний диаметр - размер комбинированного блочного устройства, которое является объемом. Для простого тома они одинаковы, для томов RAID они сильно отличаются. И я не хочу возиться с попытками определить тип RAID и обновить свои вычисления, когда будут изобретены разные методы RAID, я хочу, чтобы ОС сообщала мне, сколько места в этом томе. Сумма, которую я мог бы использовать, если бы сделал внутри свою собственную файловую систему. - person Ben Voigt; 09.08.2016
comment
@BenVoigt, но IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS дает вам несколько DISK_EXTENT. в случае просто Volume - будет только 1 экстент. и тот же результат, что и IOCTL_DISK_GET_LENGTH_INFO для раздела тома. не ? я не могу проверить это на сложных томах RAID - person RbMm; 09.08.2016