SVERROR GetVolumeSize()

in host/common/win32/portablehelpersmajor.cpp [2663:2822]


SVERROR GetVolumeSize(ACE_HANDLE volumeHandle,
    unsigned long long* volumeSize)
{

    SVERROR sve = SVS_OK;


#define MAXIMUM_SECTORS_ON_VOLUME (1024i64 * 1024i64 * 1024i64 * 1024i64)
#define DISK_SECTOR_SIZE (512)

    PVOLUME_DISK_EXTENTS pDiskExtents; // dynamically sized allocation based on number of extents
    GET_LENGTH_INFORMATION  lengthInfo;
    DWORD bytesReturned;
    DWORD returnBufferSize;
    BOOLEAN fSuccess;
    LONGLONG currentGap;
    LONGLONG currentOffset;
    LARGE_INTEGER largeInt;
    PUCHAR sectorBuffer;

    // try a simple IOCTl to get size, should work on WXP and later
    fSuccess = DeviceIoControl(volumeHandle,
        IOCTL_DISK_GET_LENGTH_INFO,
        NULL,
        0,
        &lengthInfo,
        sizeof(lengthInfo),
        &bytesReturned,
        NULL);
#if (PRINT_SIZES)
    printf("Volume size via IOCTL_DISK_GET_LENGTH_INFO is hex:%I64X or decimal:%I64i\n", lengthInfo.Length.QuadPart, lengthInfo.Length.QuadPart);
#endif 

#if (TEST_GET_EXTENTS || TEST_BINARY_SEARCH || TEST_COMPLETE_BINARY_SEARCH)
    fSuccess = FALSE;
#endif
    if (fSuccess) {
        *volumeSize = lengthInfo.Length.QuadPart;
        return SVS_OK;
    }

    // next attempt, see if the volume is only 1 extent, if yes use it
    returnBufferSize = sizeof(VOLUME_DISK_EXTENTS);
    pDiskExtents = (PVOLUME_DISK_EXTENTS) new UCHAR[returnBufferSize];
    fSuccess = DeviceIoControl(volumeHandle,
        IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
        NULL,
        0,
        pDiskExtents,
        returnBufferSize,
        &bytesReturned,
        NULL);
#if (PRINT_SIZES)
    printf("Volume size via IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS is hex:%I64X or decimal:%I64i\n", pDiskExtents->Extents[0].ExtentLength.QuadPart, pDiskExtents->Extents[0].ExtentLength.QuadPart);
#endif
#if (TEST_BINARY_SEARCH || TEST_COMPLETE_BINARY_SEARCH)
    fSuccess = FALSE;
#endif
    if (fSuccess) {
        // must only have 1 extent as the buffer only has space for one
        assert(pDiskExtents->NumberOfDiskExtents == 1);
        *volumeSize = pDiskExtents->Extents[0].ExtentLength.QuadPart;
        delete pDiskExtents;
        return SVS_OK;
    }

    // use harder ways of finding the size
    sve = GetLastError();
#if (TEST_BINARY_SEARCH)
    sve = ERROR_MORE_DATA;
#endif
    if (ERROR_MORE_DATA != sve.error.hr) {
        // some error other than buffer too small happened
        delete pDiskExtents;
        return sve;
    }

    // has multiple extents
    DWORD nde;
    INM_SAFE_ARITHMETIC(nde = InmSafeInt<DWORD>::Type(pDiskExtents->NumberOfDiskExtents) - 1, INMAGE_EX(pDiskExtents->NumberOfDiskExtents))
        INM_SAFE_ARITHMETIC(returnBufferSize = sizeof(VOLUME_DISK_EXTENTS) + (nde * InmSafeInt<size_t>::Type(sizeof(DISK_EXTENT))), INMAGE_EX(sizeof(VOLUME_DISK_EXTENTS))(nde)(sizeof(DISK_EXTENT)))
        delete pDiskExtents;
    pDiskExtents = (PVOLUME_DISK_EXTENTS) new UCHAR[returnBufferSize];
    fSuccess = DeviceIoControl(volumeHandle,
        IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
        NULL, 0,
        pDiskExtents, returnBufferSize,
        &bytesReturned,
        NULL);
#if (TEST_COMPLETE_BINARY_SEARCH)
    fSuccess = FALSE;
#endif
    if (!fSuccess) {
        currentOffset = MAXIMUM_SECTORS_ON_VOLUME; // if IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS failed, do a binary search with a large limit
    }
    else {
        // must be multiple extents, so will have to binary search last valid sector

        currentOffset = 0;
        for (unsigned int i = 0; i < pDiskExtents->NumberOfDiskExtents; i++) {
            currentOffset += pDiskExtents->Extents[i].ExtentLength.QuadPart;
        }

        // scale things now so we don't need to divide on every read
        currentOffset += DISK_SECTOR_SIZE; // these two are needed to make the search algorithm work
        currentOffset /= DISK_SECTOR_SIZE;
    }

    delete pDiskExtents;

    // the search gap MUST be a power of two, so find an appropriate value
    currentGap = 1i64;
    while ((currentGap * 2i64) < currentOffset) {
        currentGap *= 2i64;
    }

    // we are all ready to binary search for the last valid sector
    sectorBuffer = (PUCHAR)VirtualAlloc(NULL, DISK_SECTOR_SIZE, MEM_COMMIT, PAGE_READWRITE);
    do {
        largeInt.QuadPart = currentOffset * DISK_SECTOR_SIZE;
        SetFilePointerEx(volumeHandle,
            largeInt,
            NULL,
            FILE_BEGIN);

        fSuccess = ReadFile(volumeHandle,
            sectorBuffer,
            DISK_SECTOR_SIZE,
            &bytesReturned,
            NULL);

        if (fSuccess && (bytesReturned == DISK_SECTOR_SIZE)) {
            currentOffset += currentGap;
            if (currentGap == 0) {
                *volumeSize = (currentOffset * DISK_SECTOR_SIZE) + DISK_SECTOR_SIZE;
                sve = SVS_OK;
                break;
            }
        }
        else {
            currentOffset -= currentGap;
            if (currentGap == 0) {
                *volumeSize = currentOffset * DISK_SECTOR_SIZE;
                sve = SVS_OK;
                break;
            }
        }
        currentGap /= 2;

    } while (TRUE);

    VirtualFree(sectorBuffer, DISK_SECTOR_SIZE, MEM_DECOMMIT);

#if (PRINT_SIZES)
    printf("Volume size via binary search is hex:%I64X or decimal:%I64i\n", *volumeSize, *volumeSize);
#endif


    return sve;
}