1. Overview

QCOW2, which stands for QEMU Copy-On-Write version 2, is a popular file format used for virtual disk images in QEMU. In this tutorial, we’ll look at different methods of mounting QCOW2 image files in a Linux environment, using the official QCOW2 image of Debian 12, saved locally as example.qcow2, as a reference.

Before mounting a QCOW2 image, it’s recommended to disable os-prober, as it may add unwanted and incorrect menu entries to GRUB. These entries will be unusable because they will refer to our QCOW2 virtual disks, which GRUB doesn’t support.

In the following steps, we’ll see the necessary Debian packages that we can install with apt. Other distributions have their own equivalent packages.

2. Network Block Device (NBD)

Network Block Device (NBD) is a protocol that provides a versatile way to access block devices over a network. Essentially, it treats remote block devices as if they’re locally attached to our system so we can manage them with the same tools and protocols we use for local storage.

It’s one of the most appropriate solutions for mounting QCOW2 images in Linux because it efficiently handles the advanced features of QCOW2 at the kernel level, ensuring that the underlying complexities are transparent to the user.

2.1. Connect the QCOW2 Image to a NBD Device

First, let’s load the NBD kernel module with modprobe. In most standard Linux distributions, this command should work out of the box:

$ sudo modprobe nbd max_part=8

The Linux kernel needs the max_part option to create device files for the partitions on the NBD device. We can adjust the choice of 8 based on the maximum number of partitions we expect to have on the QCOW2 image.

Next, let’s connect the QCOW2 image to the NBD device with qemu-nbd, which is part of the qemu-utils package:

$ sudo qemu-nbd --connect=/dev/nbd0 ~/example.qcow2

If we are using the graphical environment of a recent Linux distribution, we may find the QCOW2 disk already added to the local file manager with read/write permissions without any further action. It can be as simple as adding a USB stick. For example, here is the result in Linux Mint 21:

Out of curiosity, let’s check where the local file manager mounted the QCOW2 disk:

$ mount
[...]
/dev/nbd0p1 on /media/francesco/8f994d1d-8ae8-4dff-aaa5-9aef4574329b type ext4 (rw,[...])

However, the file manager of our distribution may not have this feature.

2.2. Mount an NBD Partition

We may still need or want to manually mount an NBD partition. In this case, let’s first check the available partitions with fdisk to find the one we want to mount:

$ sudo fdisk -l /dev/nbd0
Disk /dev/nbd0: 2 GiB, 2147483648 bytes, 4194304 sectors
[...]
Device        Start     End Sectors  Size Type
/dev/nbd0p1  262144 4192255 3930112  1,9G Linux filesystem
/dev/nbd0p14   2048    8191    6144    3M BIOS boot
/dev/nbd0p15   8192  262143  253952  124M EFI System
[...]

We are interested in /dev/nbd0p1. Let’s mount it with read/write permissions to a local directory:

$ sudo mkdir /mnt/qcow2
$ sudo mount /dev/nbd0p1 /mnt/qcow2

If we want read-only access to prevent any changes to the QCOW2 image, we can use the -o ro option:

$ sudo mount -o ro /dev/nbd0p1 /mnt/qcow2

Let’s remember to unmount the virtual hard disk after using it:

$ sudo umount /mnt/qcow2

As a final note, when the system is rebooted, the NBD kernel module is typically unloaded, and any established NBD connections are disconnected.

2.3. Automatically Mount QCOW2 Image on Boot

To make our NBD setup persistent across reboots, we can use a systemd service to load the kernel module and mount the QCOW2 image.

Let’s create the file /etc/systemd/system/mount-qcow2.service with the following content, making sure we replace /home/user/example.qcow2 with the actual path to our QCOW2 file and /mnt/qcow2 with the mount point we want to use:

[Unit]
Description=Mount QCOW2 Image using NBD
After=network-online.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'modprobe nbd max_part=8 && qemu-nbd --connect=/dev/nbd0 /home/user/example.qcow2 && sleep 5 && mount /dev/nbd0p1 /mnt/qcow2'
RemainAfterExit=yes
ExecStop=/bin/sh -c 'umount /mnt/qcow2 && qemu-nbd --disconnect /dev/nbd0'
TimeoutSec=0
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Compared to the commands we saw earlier, the only addition in this service is sleep 5. This introduces a delay between the qemu-nbd command and the mount command because the /dev/nbd0 device may take a moment to be ready after it is connected to qemu-nbd.

After creating this service file, let’s enable it:

$ sudo systemctl enable mount-qcow2.service
Created symlink /etc/systemd/system/multi-user.target.wants/mount-qcow2.service → /etc/systemd/system/mount-qcow2.service.

At the next reboot, the service automatically loads the NBD module and mounts the QCOW2 image. If there are any problems, let’s take a look at the status of the service:

$ systemctl status mount-qcow2.service
● mount-qcow2.service - Mount QCOW2 Image using NBD
[...]
     Active: active (exited) since Tue 2023-11-07 20:48:48 +07; 1min 8s ago
[...]

If that is not enough, we can also check all its logs:

$ sudo journalctl -u mount-qcow2.service

The combination of systemctl status and journalctl commands is usually sufficient to diagnose most service-related problems in systemd.

3. Alternatives to NBD

Despite its benefits, there are scenarios where NBD is not an option. Let’s explore some alternatives.

3.1. Loop Device

A loop device in Linux is a pseudo-device that allows a file to be treated as a block device. This means we can take an image file containing a file system and make the system treat it like a hard drive, CD-ROM, USB stick, or other physical storage device.

Mounting a QCOW2 image directly as a loop device isn’t possible because the QCOW2 format includes features like snapshots, compression, copy-on-write, dynamic size, and encryption that the loop device driver doesn’t understand. However, qemu-img, which is part of the qemu-utils package, can convert a QCOW2 image to an uncompressed and fixed-size raw disk image format, which we can then mount as a loop device:

$ qemu-img convert -O raw ~/example.qcow2 ~/example.raw
$ file example.*
example.qcow2: QEMU QCOW2 Image (v3), 2147483648 bytes
example.raw:   DOS/MBR boot sector, extended partition table (last)
$ ls -l example.*
-rw-rw-r-- 1 francesco francesco  390266880 Nov  8 19:48 example.qcow2
-rw-r--r-- 1 francesco francesco 2147483648 Nov  8 23:25 example.raw

losetup associates loop devices with regular files or block devices. The -f flag tells losetup to use the first unused loop device, and –show shows the name of the associated loop device:

$ sudo losetup -f --show ~/example.raw
/dev/loop28

This way, the loop device /dev/loop28 maps the file ~/example.raw. At this point, we can see its partitions:

$ sudo fdisk -l /dev/loop28
Disk /dev/loop28: 2 GiB, 2147483648 bytes, 4194304 sectors
[...]
Device          Start     End Sectors  Size Type
/dev/loop28p1  262144 4192255 3930112  1,9G Linux filesystem
/dev/loop28p14   2048    8191    6144    3M BIOS boot
/dev/loop28p15   8192  262143  253952  124M EFI System

Sometimes, the kernel doesn’t automatically know the partition table of the loop device, even if fdisk shows it. That’s why it’s better to force the kernel to read it using partprobe before proceeding:

$ sudo partprobe /dev/loop28

Now we’re ready to mount the partition we’re interested in:

$ sudo mount /dev/loop28p1 /mnt/qcow2
$ sudo du -sh /mnt/qcow2
988M    /mnt/qcow2

The most obvious drawback to this approach is the amount of space it takes. The file system contained in the QCOW2 image is 988MB, the QCOW2 file is only 372MB due to compression, but the raw image we use here takes up 2GB.

3.2. guestfish

guestfish is a shell and command line tool for safely and conveniently editing virtual machine disk images. It’s provided by the guestfish packageIts shell has a rich set of commands to access and modify the contents of disk images used by virtual machines, such as those in QCOW2 format.

First, let’s start guestfish in read/write mode without a disk image:

$ sudo guestfish --rw
Welcome to guestfish, the guest filesystem shell for
editing virtual machine filesystems and disk images.
[...]
><fs>

Next, inside the guestfish shell, let’s attach the disk image, replacing our sample full path with the actual one:

><fs> add /home/user/example.qcow2

Now, let’s execute the run command to detect the partitions and file systems:

><fs> run
 100% ⟦▒▒▒▒▒▒▒▒▒⟧ 00:00

Let’s list the file systems guestfish has just detected:

><fs> list-filesystems
/dev/sda1: ext4
/dev/sda14: unknown
/dev/sda15: vfat

We’re ready to mount the desired partition and list the contents of the root directory:

><fs> mount /dev/sda1 /
><fs> ls /
bin
boot
dev
[...]
usr
var

Let’s look at the contents of a test file that we previously created with NBD:

><fs> cat /tmp/testfile.txt 
Test4

After finishing the operations, let’s remember to terminate guestfish:

><fs> quit

For simple tasks, guestfish can be more complex than a standard mount operation. Another drawback is that it has its own set of commands and syntax, which may require additional learning for new users.

3.3. guestmount

guestmount is a libguestfs tool distributed in a separate package. It allows us to mount virtual machine disk images, including QCOW2:

$ sudo guestmount --add ~/example.qcow2 --mount /dev/sda1 /mnt/qcow2

In this example:

  • –add option specifies the path to the disk image
  • –mount option specifies the partition to mount, in this case, /dev/sda1
  • /mnt/qcow2 is the directory where the file system will be mounted

Let’s do a test:

$ sudo ls /mnt/qcow2 
bin   dev  home  lib32    libx32        media  opt     root  sbin  sys  usr
boot  etc  lib     lib64    lost+found  mnt    proc  run   srv   tmp  var
$ sudo cat /mnt/qcow2/tmp/testfile.txt
Test4

guestmount has its place and may be the preferred option when the goal is to quickly access the file system inside a disk image. However, when comparing guestmount to NBD, the better efficiency of NBD is certainly a factor, but there are other considerations as well:

  • NBD can offer better support for concurrent access
  • NBD has more mature support for caching and cache coherency
  • In the event of a system crash or failure, recovery from an NBD-mounted image can be easier

In addition, NBD exposes the image as a block device, which means it’s possible to perform block-level operations that might not be possible with guestmount. This includes things like using dd to clone sections of the disk, resizing partitions with tools like gparted or even running fsck on the file system.

4. Conclusion

In this article, we’ve discussed how to mount QCOW2 images under Linux, with different methods appropriate for different use cases:

  • NBD is the most robust and efficient solution
  • guestmount is the simplest solution
  • guestfish is the only way to modify a virtual disk without mounting it in the host system
  • A loop device is a well-supported solution but requires conversion from QCOW2 to raw

Whether we’re working in a development environment, performing forensic analysis, or managing virtual machine storage, mounting QCOW2 images with confidence is a valuable skill.