Search code examples
c++cwinapidriverkernel32

How to call DeviceIoControl to retrieve the amount of memory it needs?


I'm trying to call DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS) API, as shown here, but I need it to first "tell me" how much memory it needs (unlike the code I linked to.)

So I call it as such:

//First determine how much data do we need?
BYTE dummyBuff[1];
DWORD bytesReturned = 0;
if(!::DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, 
    dummyBuff, sizeof(dummyBuff), &bytesReturned, NULL))
{
    //Check last error
    int nError = ::GetLastError();
    if(nOSError == ERROR_INSUFFICIENT_BUFFER ||
        nOSError == ERROR_MORE_DATA)
    {
        //Alloc memory from 'bytesReturned' ...
    }
}

but it always returns error code 87, or ERROR_INVALID_PARAMETER and my bytesReturned is always 0.

So what am I doing wrong?


Solution

  • The instructions for getting all disk volume extents are documented under the VOLUME_DISK_EXTENTS structure:

    When the number of extents returned is greater than one (1), the error code ERROR_MORE_DATA is returned. You should call DeviceIoControl again, allocating enough buffer space based on the value of NumberOfDiskExtents after the first DeviceIoControl call.

    The behavior, if you pass an output buffer, that is smaller than sizeof(VOLUME_DISK_EXTENTS) is also documented at IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS control code:

    If the output buffer is less than sizeof(VOLUME_DISK_EXTENTS), the call fails, GetLastError returns ERROR_INSUFFICIENT_BUFFER, and lpBytesReturned is 0 (zero).

    While this explains the returned value in lpBytesReturned, it doesn't explain the error code 87 (ERROR_INVALID_PARAMETER)1).

    The following code will return the disk extents for all volumes:

    VOLUME_DISK_EXTENTS vde = { 0 };
    DWORD bytesReturned = 0;
    if ( !::DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, 
                             (void*)&vde, sizeof(vde), &bytesReturned, NULL ) )
    {
        // Check last error
        int nError = ::GetLastError();
        if ( nError != ERROR_MORE_DATA )
        {
            // Unexpected error -> error out
            throw std::runtime_error( "DeviceIoControl() failed." );
        }
    
        size_t size = offsetof( VOLUME_DISK_EXTENTS, Extents[vde.NumberOfDiskExtents] );
        std::vector<BYTE> buffer( size );
        if ( !::DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, 
                                 (void*)buffer.data(), size, &bytesReturned, NULL ) )
        {
            // Unexpected error -> error out
            throw std::runtime_error( "DeviceIoControl() failed." );
        }
        // At this point we have a fully populated VOLUME_DISK_EXTENTS structure
        const VOLUME_DISK_EXTENTS& result =
            *reinterpret_cast<const VOLUME_DISK_EXTENTS*>( buffer.data() );
    }
    else
    {
        // Call succeeded; vde is populated with single disk extent.
    }
    


    Additional references:


    1) At a guess I would assume, that BYTE[1] begins at a memory address, that is not sufficiently aligned for the alignment requirements of VOLUME_DISK_EXTENTS.