Converting Virtual Disks to ZVOLs

Converting virtual disks to zvols

Having recently worked with the latest Windows Eval environment for some testing I downloaded WinDev2309Eval.VMWare.zip from Microsoft's Get a Windows 11 development environment page and went with the VMWare option knowing it would be a quick n' easy vmdk conversion.

Determining the virtual disk's "true" size.

With qcow2 files you can simply run file my.qcow2 to see the true size of the virtual disk, regardless of the actual consumed storage space as a file:

 $ file my.qcow2 
my.qcow2: QEMU QCOW Image (v3), 21474836480 bytes (v3), 21474836480 bytes

file isn't as familiar with vmdk files so to get the true size of the virtual disk we can instead call qemu-img info to help determine what size the zvol should be:

$ qemu-img info WinDev2309Eval-disk1.vmdk
image: WinDev2309Eval-disk1.vmdk
file format: vmdk
virtual size: 125 GiB (134217728000 bytes)
disk size: 23 GiB
cluster_size: 65536
Format specific information:
    cid: 2132776597
    parent cid: 4294967295
    create type: streamOptimized
    extents:
        [0]:
            compressed: true
            virtual size: 134217728000
            filename: WinDev2309Eval-disk1.vmdk
            cluster size: 65536
            format:
Child node '/file':
    filename: WinDev2309Eval-disk1.vmdk
    protocol type: file
    file length: 23 GiB (24689722880 bytes)
    disk size: 23 GiB

In this output from qemu-img it confirms the file only consumes consuming 23GiB of storage space - but the virtual disk's true size is 125GiB and the rest of the space hasn't been consumed. Given the various use-cases these eval environments may face it makes sense for Microsoft to create this with an additional 100GB space in the guest's disk partitioning for it to support taking in a lot more data than just this (mostly) base OS.

Making a ZVol

Knowing the VMDK's virtual disk has a total size of 125 GiB (134217728000 bytes) we can create a zvol for it to land in:

sudo zfs create zpool/WinDev2309Eval-disk1 -V134217728000B -s -o encryption=on -o compression=lz4

The above command created a zvol aptly given the same basename as the virtual disk with a perfect volume size in bytes to store the VMDK's contents.

The -s flag created the volume sparse without reserving the space. This makes it possible to create virtual making it possible to create zvol of any size without immediately locking off that storage space. I often find this useful for mapping large virtual drives to take block level images of real disks with 1:1 block allocation as the original disk - knowing they have appropriate free space to a avoid actually filling up my host. image a disk knowing ahead that they won't actually consume much space maeven if the host doesn't have the storage space for it.

I created this zvol in a dataset which was already encrypted so I've specified -o encryption=on to inherit the parent's encryption settings and decryption passphrase. I also specified -o compression=lz4 for example's sake despite that already being the default setting for my zpool.

Converting the guest image to a zvol

We can use the raw conversion option to convert the image to a format suitable for zvols:

sudo chown $USER /dev/zvol/zpool/WinDev2309Eval-disk1 ; qemu-img convert -f vmdk -O raw WinDev2309Eval-disk1.vmdk /dev/zvol/zpool/WinDev2309Eval-disk1.

Depending on the IO capabilities of your host's involved source/dest disks and arrays this can take either a few minutes as it did for my laptop's NVMe or over an hour for slower disk/array configurations. - especially if the source/dest is the same zpool, there's quite a bit of overhead to be considered.

Because there's only about 25GB of consumed space in this image - the remaining 100GB stream of zeros (\x00's) after the VMDK's main used storage space should compress and write out near-instantly.

Making the virtual disk boot

It's trivial to set up a new VM on Linux with virt-manager, virt-install, virsh commands or even calling qemu-system-x86_64 directly (Maybe with some of the wrapper scripts out there) but given this is a pre-packaged Windows install which wants to immediately boot into an Out of the Box user-account setup experience - it's going to have drivers preloaded in for VMWare environments but not for the drivers QEMU can provide.

As such, creating a generic Windows VM template and specifying the disk will likely fail to boot with either 0x00000001 or 0xC0000001 as it fails to comprehend what happened to the environment it was expecting.

We can open the XML WinDev2309Eval.ovf to get an idea of what hardware configuration this VM was made for.

Reading the omf XML

Reading this file reveals the guest expects some SCSI and IDE controllers. IDE controllers are commonly used for CDROM drives - especially in VMWare. The SCSI controller is more commonly seen for OS disks and at the top we can see the disk defined as vmdisk1, seen again further down defined as an with "AddressOnParent" set to 0 which corresponds to the first-defined SCSI controller. Further down the guest also has firmware set to efi and secureboot enabled. This should be enough information to get started on booting this thing.

Packing up and doing something else.

2023-11-03 11:45:05.016947397 +1100

After a few hours of fiddling. No amount of virtual-hardware tweaking was able to get this Win11 Eval VM past 0xC00000001 and 0x00000098. Getting the Win10 Eval vmdk to boot in QEMU is something I'll have to come back to, but this process works fine for converting VM qcow2's to a zvol block device.