qubes.storage – Qubes data storage

Qubes provide extensible API for domains data storage. Each domain have multiple storage volumes, for different purposes. Each volume is provided by some storage pool. Qubes support different storage pool drivers, and it’s possible to register additional 3rd-party drivers.

Domain’s storage volumes:

  • root - this is where operating system is installed. The volume is available read-write to all domain classes. It could be made read-only for AppVM and DispVM to implement an untrusted storage domain in the future, but doing so will cause such VMs to set up a device-mapper based copy-on-write layer that redirects writes to the volatile volume. Whose storage driver may already do CoW, leading to an inefficient CoW-on-CoW setup. For this reason, root is currently read-write in all cases.
  • private - this is where domain’s data live. The volume is available read-write to all domain classes (including DispVM, but data written there is discarded on domain shutdown).
  • volatile - this is used for any data that do not to persist. This include swap, copy-on-write layer for a future read-only root volume etc.
  • kernel - domain boot files - operating system kernel, initial ramdisk, kernel modules etc. This volume is provided read-only and should be provided by a storage pool respecting qubes.vm.qubesvm.QubesVM.kernel property.

Storage pool concept

Storage pool is responsible for managing its volumes. Qubes have defined storage pool driver API, allowing to put domains storage in various places. By default three drivers are provided: qubes.storage.file.FilePool (named file), qubes.storage.reflink.ReflinkPool (named file-reflink), and qubes.storage.lvm.ThinPool (named lvm_thin). But the API allow to implement variety of other drivers (like additionally encrypted storage, external disk, drivers using special features of some filesystems, etc).

Most of storage API focus on storage volumes. Each volume have at least those properties:

  • rw - should the volume be available read-only or read-write to the domain
  • snap_on_start - should the domain start with its own state of the volume, or rather a snapshot of its template volume (pointed by a source property). This can be set to True only if a domain do have template property (AppVM and DispVM). If the domain’s template is running already, the snapshot should be made out of the template’s before its startup.
  • save_on_stop - should the volume state be saved or discarded on domain stop. In either case, while the domain is running, volume’s current state should not be committed immediately. This is to allow creating snapshots of the volume’s state from before domain start (see snap_on_start).
  • revisions_to_keep - number of volume revisions to keep. If greater than zero, at each domain stop (and if save_on_stop is True) new revision is saved and old ones exceeding revisions_to_keep limit are removed. This defaults to revisions_to_keep.
  • source - source volume for snap_on_start volumes
  • vid - pool specific volume identifier, must be unique inside given pool
  • pool - storage pool object owning this volume
  • name - name of the volume inside owning domain (like root, or private)
  • size - size of the volume, in bytes
  • ephemeral - whether volume is automatically encrypted with an ephemeral key. This can be set only on volumes that have both snap_on_start and save_on_stop set to False - namely, volatile volume. This property for DispVM’s volatile volume is inherited from the template (but not for other types of VMs). For volatile volumes, this property defaults to ephemeral_volatile.

Storage pool driver may define additional properties.

Storage pool driver API

Storage pool driver need to implement two classes:

Pool class should be registered with qubes.storage entry_point, under the name of storage pool driver. Volume class instances should be returned by qubes.storage.Pool.init_volume() method of pool class instance.

Methods required to be implemented by the pool class:
  • init_volume() - return instance of appropriate volume class; this method should not alter any persistent disk state, it is used to instantiate both existing volumes and create new ones
  • setup() - setup new storage pool
  • destroy() - destroy storage pool
Methods and properties required to be implemented by the volume class:
  • create() - create volume on disk
  • remove() - remove volume from disk
  • start() - prepare the volume for domain start; this include making a snapshot if snap_on_start is True
  • stop() - cleanup after domain shutdown; this include committing changes to the volume if save_on_stop is True
  • export() - return a path to be read to extract volume data; for complex formats, this can be a pipe (connected to some data-extracting process)
  • export_end() - cleanup after exporting the data; this function is called when the path returned by export() is not used anymore. This method optional - some storage drivers may not implement it if not needed.
  • import_data() - return a path the data should be written to, to import volume data; for complex formats, this can be pipe (connected to some data-importing process)
  • import_data_end() - finish data import operation (cleanup temporary files etc); this methods is called always after import_data() regardless if operation was successful or not
  • import_volume() - import data from another volume
  • resize() - resize volume
  • revert() - revert volume state to a given revision
  • revisions - collection of volume revisions (to use with qubes.storage.Volume.revert())
  • is_dirty() - is volume properly committed after domain shutdown? Applies only to volumes with save_on_stop set to True
  • is_outdated() - have the source volume started since domain startup? applies only to volumes with snap_on_start set to True
  • config - volume configuration, this should be enough to later reinstantiate the same volume object
  • block_device() - return qubes.storage.BlockDevice instance required to configure volume in libvirt

Some storage pool drivers can provide limited functionality only - for example support only volatile volumes (those with snap_on_start is False, save_on_stop is False, and rw is True). In that case, it should raise NotImplementedError in qubes.storage.Pool.init_volume() when trying to instantiate unsupported volume.

Note that pool driver should be prepared to recover from power loss before stopping a domain - so, if volume have save_on_stop is True, and qubes.storage.Volume.stop() wasn’t called, next start() should pick up previous (not committed) state.

See specific methods documentation for details.

Module contents

Qubes storage system

exception qubes.storage.StoragePoolException[source]

Bases: qubes.exc.QubesException

A general storage exception

class qubes.storage.BlockDevice(path, name, script=None, rw=True, domain=None, devtype='disk')[source]

Bases: object

Represents a storage block device.

class qubes.storage.DirectoryThinPool[source]

Bases: object

The thin pool containing the device of given filesystem

classmethod thin_pool(dir_path)[source]

Thin tuple (volume group, pool name) containing given filesystem

class qubes.storage.Pool(*, name, revisions_to_keep=1, ephemeral_volatile=False)[source]

Bases: object

A Pool is used to manage different kind of volumes (File based/LVM/Btrfs/…).

3rd Parties providing own storage implementations will need to extend this class.


Called when removing the pool. Use this for implementation specific clean up.

This can be implemented as a coroutine.


Return a volume with vid from this pool

Raises:KeyError – if no volume is found

Check if this pool is physically included in another one

This works on best-effort basis, because one pool driver may not know all the other drivers.

Parameters:app – Qubes() object to lookup other pools in

:returns pool or None

init_volume(vm, volume_config)[source]

Initialize a qubes.storage.Volume from volume_config.


Return a list of volumes managed by this pool


Called when adding a pool to the system. Use this for implementation specific set up.

This can be implemented as a coroutine.


Returns the pool config to be written to qubes.xml


Storage pool size in bytes, or None if unknown


Space used in the pool in bytes, or None if unknown


Detailed information about pool usage as a dictionary Contains data_usage for usage in bytes and data_size for pool size; other implementations may add more implementation-specific detail


Return a collection of volumes managed by this pool

class qubes.storage.Storage(vm)[source]

Bases: object

Class for handling VM virtual disks.

This is base class for all other implementations, mostly with Xen on Linux in mind.

attach(volume, rw=False)[source]

Attach a volume to the domain


Return all qubes.storage.BlockDevice for current domain for serialization in the libvirt XML template as <disk>.


Clone volumes from the specified vm

clone_volume(src_vm, name)[source]

Clone single volume from the specified vm

  • src_vm (QubesVM) – source VM
  • name (str) – name of volume to clone (‘root’, ‘private’ etc)

:return cloned volume object


Creates volumes on disk


Detach a volume from domain


Helper function to export volume

export_end(volume, export_path)[source]

Cleanup after exporting data from the volume

  • volume – volume that was exported
  • export_path – path returned by the export() call

Returns summed up disk utilization for all domain volumes

import_data(volume, size)[source]

Helper function to import volume data.

Size:new size in bytes, or None if using old size
import_data_end(volume, success)[source]

Helper function to finish/cleanup data import

init_volume(name, volume_config)[source]

Initialize Volume instance attached to this domain


Remove all the volumes.

Errors on removal are catched and logged.

resize(volume, size)[source]

Resizes volume a read-writable volume


Execute the start method on each volume


Execute the stop method on each volume


Find an unused device name


Verify that the storage is sane.

On success, returns normally. On failure, raises exception.

drive = None

Additional drive (currently used only by HVM)


Directory where kernel resides.

If self.vm.kernel is None, the this points inside self.vm.dir_path


Returns a list of outdated volumes


Used device names

vm = None

Domain for which we manage storage

class qubes.storage.VmCreationManager(vm)[source]

Bases: object

A ContextManager which cleans up if volume creation fails.

class qubes.storage.Volume(name, pool, vid, revisions_to_keep=0, rw=False, save_on_stop=False, size=0, snap_on_start=False, source=None, ephemeral=None, **kwargs)[source]

Bases: object

Encapsulates all data about a volume for serialization to qubes.xml and libvirt config.

Keep in mind! volatile = not snap_on_start and not save_on_stop snapshot = snap_on_start and not save_on_stop origin = not snap_on_start and save_on_stop origin_snapshot = snap_on_start and save_on_stop


Return BlockDevice for serialization in the libvirt XML template as <disk>.


Create the given volume on disk.

This method is called only once in the volume lifetime. Before calling this method, no data on disk should be touched (in context of this volume).

This can be implemented as a coroutine.

encrypted_volume_path(qube_name, device_name)[source]

Find the name of the encrypted volatile volume


Returns a path to read the volume data from.

Reading from this path when domain owning this volume is running (i.e. when is_dirty() is True) should return the data from before domain startup.

Reading from the path returned by this method should return the volume data. If extracting volume data require something more than just reading from file (for example connecting to some other domain, or decompressing the data), the returned path may be a pipe.

This can be implemented as a coroutine.


Cleanup after exporting data.

This method is called after exporting the volume data (using export()), when the path is not needed anymore.

This can be implemented as a coroutine.

Parameters:path – path to cleanup, returned by export()

Returns a path to overwrite volume data.

This method is called after volume was already create()-ed.

Writing to this path should overwrite volume data. If importing volume data require something more than just writing to a file ( for example connecting to some other domain, or converting data on the fly), the returned path may be a pipe.

This can be implemented as a coroutine.

Parameters:size (int) – size of new data in bytes

End the data import operation. This may be used by pool implementation to commit changes, cleanup temporary files etc.

This method is called regardless the operation was successful or not.

This can be implemented as a coroutine.

Parameters:success – True if data import was successful, otherwise False

Imports data from a different volume (possibly in a different pool.

The volume needs to be create()d first.

This can be implemented as a coroutine.


Return True if volume was not properly shutdown and committed.

This include the situation when domain owning the volume is still running.


Returns True if this snapshot of a source volume (for `snap_on_start`=True) is outdated.

static locked(method)[source]

Decorator running given Volume’s coroutine under a lock.

make_encrypted_device(device, qube_name)[source]

Takes BlockDevice and returns its encrypted version for serialization in the libvirt XML template as <disk>. The qube name is available to help construct the device path.


Remove volume.

This can be implemented as a coroutine.


Expands volume, throws qubes.storage.StoragePoolException if given size is less than current_size

This can be implemented as a coroutine.

Parameters:size (int) – new size in bytes

Revert volume to previous revision

This can be implemented as a coroutine.

Parameters:revision – revision to revert volume to, see revisions

Do what ever is needed on start.

This include making a snapshot of template’s volume if snap_on_start is set.

This can be implemented as a coroutine.


Start a volume encrypted with an ephemeral key. This can be implemented as a coroutine.

The default implementation of this method uses cryptsetup(8) with a key taken from /dev/urandom. This is highly secure and works with any storage pool implementation. Volume implementations should override this method if they can provide a secure and more efficient implementation.


Do what ever is needed on stop.

This include committing data if save_on_stop is set.

This can be implemented as a coroutine.


Stop an encrypted, ephemeral volume. This can be implemented as a coroutine.

The default implementation of this method uses cryptsetup(8). Volume implementations that override start_encrypted() MUST override this method as well.


Verifies the volume.

This function is supposed to either return True, or raise an exception.

This can be implemented as a coroutine.


return config data for serialization to qubes.xml


Should this volume be encrypted with an ephemeral key in dom0 (if enabled with encrypted_volatile property)?

name = None

Name of the volume in a domain it’s attached to (like root or private).

pool = None

Pool instance owning this volume


Returns a dict containing revision identifiers and time of their creation

rw = None

Should this volume be writable by domain.

save_on_stop = None

Should volume state be saved or discarded at stop()


Volume size in bytes

snap_on_start = None

Should the volume state be initialized with a snapshot of same-named volume of domain’s template.

source = None

source volume for snap_on_start volumes

usage = 0

disk space used by this volume, can be smaller than size for sparse volumes

vid = None

Volume unique (inside given pool) identifier

class qubes.storage.VolumesCollection(pool)[source]

Bases: object

Convenient collection wrapper for pool.get_volume and pool.list_volumes


Return list of volume IDs


Return list of Volumes


Get __init__ parameters from a driver with out self & name.


Helper method which returns an iso date


Return a list of EntryPoints names

qubes.storage.search_pool_containing_dir(pools, dir_path)[source]

Helper function looking for a pool containing given directory.

This is useful for implementing Pool.included_in method