[python-win32] DeviceIOControl using IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS fails with 'Incorrect function.'

Eryk Sun eryksun at gmail.com
Tue Feb 9 20:05:15 EST 2021


On 2/9/21, Doug Campbell <wdouglascampbell at hotmail.com> wrote:
>
> win32file.DeviceIoControl(hDevice,
> winioctlcon.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
> None, extents, None)
> pywintypes.error: (1, 'DeviceIoControl', 'Incorrect function.')
>
> I have tried with all three of the disks on my system:  \\.\PhysicalDrive0,
> \\.\PhysicalDrive1, and \\.\PhyscialDrive2 but the results are always the
> same.

IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS requests the disk extents of a
volume device. Disk devices do not implement this request. A volume
device such as "\\.\C:" can span multiple extents across one or more
physical disks such as "\\.\PhysicalDrive0" and "\\.\PhysicalDrive1".

If the request fails with winerror.ERROR_MORE_DATA, you need to be
prepared to resize the structure according to NumberOfDiskExtents.
ctypes doesn't make this all that easy or convenient at a high level.
What I do is to make the array field private and implement a property
that takes the dynamic length into account. For example:

    ANYSIZE_ARRAY = 1

    class VOLUME_DISK_EXTENTS(ctypes.Structure):
        _fields_ = (('NumberOfDiskExtents', ctypes.c_ulong),
                    ('_Extents', DISK_EXTENT * ANYSIZE_ARRAY))

        @property
        def Extents(self):
            offset = type(self)._Extents.offset
            array_t = DISK_EXTENT * self.NumberOfDiskExtents
            return array_t.from_buffer(self, offset)

        def resize(self):
            if self.NumberOfDiskExtents < 1:
                self.NumberOfDiskExtents = 1
            offset = type(self)._Extents.offset
            array_size = ctypes.sizeof(DISK_EXTENT) * self.NumberOfDiskExtents
            ctypes.resize(self, offset + array_size)

If you passed `vde = VOLUME_DISK_EXTENTS()`, and the request fails
with winerror.ERROR_MORE_DATA, the call should have set
NumberOfDiskExtents to the required length. Call vde.resize(), and try
again. It's a bit clumsy, but it works.

> hDevice = win32file.CreateFile(
>             disk, win32con.GENERIC_READ,
>             win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
>             None, win32con.OPEN_EXISTING, win32con.FILE_ATTRIBUTE_NORMAL,
>             None)

IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS is defined to require
FILE_ANY_ACCESS. This means the desired access and access sharing can
both be 0, and should be since there's no reason to request access
that you don't need that could cause the call to fail with a
permission error (e.g. access denied, or a sharing violation if read
access isn't shared). For example:

    volume = r'\\.\C:'
    hDevice = win32file.CreateFile(volume, 0, 0, None,
        win32file.OPEN_EXISTING, 0, None)

---

Volume Device Names

The base name of a volume device is typically something like
"\Device\HarddiskVolume<N>" or "\Device\CdRom<N>", but these are
automatic names that can vary in the device number N each time a
volume comes online. The mountpoint manager usually will also assign
persistent names to a volume device, which are stored in the registry
if necessary (HKLM\System\MountedDevices) and are set globally in the
"\GLOBAL??" device-alias directory when the volume comes online.
Usually it assigns a GUID name of the form
"Volume{12345678-0000-0000-0000-123456789ABC}". A GUID name is used
when mounting a volume on an existing directory (e.g. mounting a
volume as "C:\Mount\BackupDrive"). It usually also assigns a DOS
drive-letter name such as "C:", which provides the classic DOS volume
mountpoint, such as "C:\".

The "\GLOBAL??" object directory is rarely accessed directly, even in
native NT API programs that have full access to the NT object
namespace. Instead, native NT programs use "\??", a virtual directory
that allows accessing global device symlinks in addition to, and
shadowed by, device symlinks in the caller's logon session device
directory. The SYSTEM logon uses "\GLOBAL??" for its logon session
devices, so it always creates and accesses global device aliases.

In the Windows API, the NT API "\??\" prefix maps to "\\.\" or "\\?\"
UNC-style paths. The former is more common if just the device is
accessed, and the latter is more common for a filesystem path. Thus to
access the "C:" volume device directly, programs typically use
"\\.\C:". Note that just "C:" by itself is a DOS drive-relative path
that expands to the working directory in the mounted filesystem, which
defaults to the root directory. So passing "C:" as the path to open is
actually a reference to a filesystem directory, not to the volume
device, which obviously won't work.


More information about the python-win32 mailing list