如何以编程方式扩展卷

How to extend a volume programmatically

我的需求是通过程序扩展硬盘容量。当我在 DeviceIO 中使用 IOCTL_DISK_GROW_PARTITION 扩展它时,磁盘管理显示新修改的大小,而 This PC (My Computer) 中的驱动器大小保持不变。

   BOOL DeviceIoControl(
      (HANDLE) hDevice,            // handle to device
      IOCTL_DISK_GROW_PARTITION,   // dwIoControlCode
      (LPVOID) lpInBuffer,         // input buffer
      (DWORD) nInBufferSize,       // size of the input buffer
      NULL,                        // lpOutBuffer
      0,                           // nOutBufferSize 
      (LPDWORD) lpBytesReturned,   // number of bytes returned
      (LPOVERLAPPED) lpOverlapped  // OVERLAPPED structure
    );

通过一些分析我发现在使用这个API时修改了磁盘的MBR但是没有改变驱动器的簇位图。我想知道使用此 DeviceIO 扩展卷或其他一些 API 来执行相同过程的正确方法。

需要了解磁盘驱动程序和文件系统之间的区别,磁盘驱动程序维护有关磁盘布局和分区的信息(它的大小、从磁盘开始的偏移量、样式(gpt 或 mbr))和安装此分区的文件系统。

IOCTL_DISK_GROW_PARTITION - this ioctl is handled by disk driver and extend partition, but this can not have effect for file system, which not handle this ioctl and have no knowledge at all that partition was extended. so you need additional ioctl use FSCTL_EXTEND_VOLUME - 此 ioctl 已发送并处理到文件系统。

所以如果我们必须执行后续步骤

  1. 发送 IOCTL_DISK_GROW_PARTITION DISK_GROW_PARTITION 作为输入缓冲区
  2. 发送IOCTL_DISK_UPDATE_DRIVE_SIZE with DISK_GEOMETRY 作为输出缓冲区
  3. 发送 IOCTL_DISK_GET_PARTITION_INFO_EX PARTITION_INFORMATION_EX 作为获取实际大小的输出 现在分区。
  4. 计算卷的新大小,以扇区为单位

    LONGLONG SectorsPerPartition = PartitionEntry->PartitionLength.QuadPart / dg.BytesPerSector;

    (dg 我们在第 2 步得到 PartitionEntry 在第 3 步)

  5. 终于用上了FSCTL_EXTEND_VOLUME

完整代码可以像下一个

int __cdecl SortPartitions(PPARTITION_INFORMATION_EX PartitionEntry1, PPARTITION_INFORMATION_EX PartitionEntry2)
{
    if (!PartitionEntry1->PartitionNumber) return PartitionEntry2->PartitionNumber ? -1 : 0;
    if (!PartitionEntry2->PartitionNumber) return +1;
    if (PartitionEntry1->StartingOffset.QuadPart < PartitionEntry2->StartingOffset.QuadPart) return -1;
    if (PartitionEntry1->StartingOffset.QuadPart > PartitionEntry2->StartingOffset.QuadPart) return +1;
    return 0;
}

DWORD ExtendTest(HANDLE hDisk)
{
    STORAGE_DEVICE_NUMBER sdn;

    ULONG dwBytesRet;

    if (!DeviceIoControl(hDisk, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesRet, NULL))
    {
        return GetLastError();
    }

    if (sdn.DeviceType != FILE_DEVICE_DISK || sdn.PartitionNumber != 0)
    {
        return ERROR_GEN_FAILURE;
    }

    GET_LENGTH_INFORMATION gli;

    if (!DeviceIoControl(hDisk, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), &dwBytesRet, NULL))
    {
        return GetLastError();
    }

    DbgPrint("Disk Length %I64x (%I64u)\n", gli.Length.QuadPart, gli.Length.QuadPart);

    PVOID stack = alloca(guz);

    union {
        PVOID buf;
        PDRIVE_LAYOUT_INFORMATION_EX pdli;
    };

    ULONG cb = 0, rcb, PartitionCount = 4;

    for (;;)
    {
        if (cb < (rcb = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[PartitionCount])))
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        if (DeviceIoControl(hDisk, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0, buf, cb, &dwBytesRet, NULL))
        {
            if (PartitionCount = pdli->PartitionCount)
            {
                PPARTITION_INFORMATION_EX PartitionEntry = pdli->PartitionEntry;

                qsort(PartitionEntry, PartitionCount, sizeof(PARTITION_INFORMATION_EX), 
                    (int (__cdecl *)(const void *, const void *))SortPartitions );

                do 
                {
                    if (!PartitionEntry->PartitionNumber)
                    {
                        continue;
                    }

                    LARGE_INTEGER EndOffset; 
                    LARGE_INTEGER MaximumOffset = PartitionCount != 1 ? (PartitionEntry + 1)->StartingOffset : gli.Length;

                    EndOffset.QuadPart = PartitionEntry->StartingOffset.QuadPart + PartitionEntry->PartitionLength.QuadPart;

                    if (EndOffset.QuadPart > MaximumOffset.QuadPart)
                    {
                        //??
                        __debugbreak();
                    }
                    else if (EndOffset.QuadPart < MaximumOffset.QuadPart)
                    {
                        DISK_GROW_PARTITION dgp;
                        dgp.PartitionNumber = PartitionEntry->PartitionNumber;
                        dgp.BytesToGrow.QuadPart = MaximumOffset.QuadPart - EndOffset.QuadPart;

                        WCHAR sz[128];

                        swprintf(sz, L"\\?\GLOBALROOT\Device\Harddisk%d\Partition%u", sdn.DeviceNumber, dgp.PartitionNumber);

                        HANDLE hPartition = CreateFile(sz, FILE_READ_ACCESS|FILE_WRITE_ACCESS, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

                        if (hPartition != INVALID_HANDLE_VALUE)
                        {  
                            // +++ begin extend
                            BOOL fOk = FALSE;

                            DISK_GEOMETRY dg;
                            if (DeviceIoControl(hPartition, IOCTL_DISK_GROW_PARTITION, &dgp, sizeof(dgp), 0, 0, &dwBytesRet, 0) &&
                                DeviceIoControl(hPartition, IOCTL_DISK_UPDATE_DRIVE_SIZE, 0, 0, &dg, sizeof(dg), &dwBytesRet, 0) &&
                                DeviceIoControl(hPartition, IOCTL_DISK_GET_PARTITION_INFO_EX, 0, 0, PartitionEntry, sizeof(*PartitionEntry), &dwBytesRet, 0)
                                )
                            {
                                LONGLONG SectorsPerPartition = PartitionEntry->PartitionLength.QuadPart / dg.BytesPerSector;

                                fOk = DeviceIoControl(hPartition, FSCTL_EXTEND_VOLUME, &SectorsPerPartition, 
                                    sizeof(SectorsPerPartition), 0, 0, &dwBytesRet, 0);

                            }

                            if (!fOk)
                            {
                                GetLastError();
                            }

                            //--- end extend
                            CloseHandle(hPartition);
                        }
                    }
                    // else EndOffset.QuadPart == MaximumOffset.QuadPart - partition can not be extended

                } while (PartitionEntry++, --PartitionCount);
            }

            return NOERROR;
        }

        switch (ULONG err = GetLastError())
        {
        case ERROR_MORE_DATA:
            PartitionCount = pdli->PartitionCount;
            continue;
        case ERROR_BAD_LENGTH:
        case ERROR_INSUFFICIENT_BUFFER:
            PartitionCount <<= 1;
            continue;
        default:
            return err;
        }
    }

}
DWORD ExtendTest()
{
    HANDLE hDisk = CreateFileW(L"\\?\PhysicalDrive0", FILE_GENERIC_READ|FILE_GENERIC_WRITE, 
        FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

    if (hDisk != INVALID_HANDLE_VALUE)
    {
        DWORD err = ExtendTest(hDisk);
        CloseHandle(hDisk);

        return err;
    }

    return GetLastError();
}