qubes.storage – Qubes data storage

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

Domain’s storage volumes:

  • root - this is where the operating system is installed. The volume is available in read-write mode to all domain classes. In order to implement an untrusted storage domain, in the future, it could be made read-only for AppVM and DispVM. Doing so, however, will cause such VMs to set up a device-mapper based copy-on-write layer redirecting writes to the volatile volume, whose storage driver may already be doing 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 is located. The volume is available in read-write mode to all domain classes (including DispVM, but the data written there is discarded on domain shutdown).

  • volatile - this is used for any non-persistent data. This includes 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 in read-only mode and should be provided by a storage pool respecting qubes.vm.qubesvm.QubesVM.kernel property.

Storage pool concept

The storage pool is responsible for managing its volumes. Qubes OS has a 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 allows to implement a variety of other drivers (like additionally encrypted storage, external disk, drivers using special features of some filesystems, etc.)

Most of the storage API is focused on storage volumes. Each volume has at least those properties:

  • rw - should the volume be available in read-only or read-write mode to the domain

  • snap_on_start - should the domain start with its own state of the volume, or rather with a snapshot of its template volume (pointed by a source property). This can be set to True only if a domain has a template property (AppVM and DispVM). If the domain’s template is already running, the snapshot should be made out of the template’s before its startup.

  • save_on_stop - should the volume’s state be saved or discarded on domain’s stop. In either case, while the domain is running, volume’s current state should not be committed immediately. This allows to create snapshots of the volume’s state from before domain’s start (see snap_on_start).

  • revisions_to_keep - the number of volume revisions to keep. If greater than zero, at each domain stop (and if save_on_stop is True) a new revision is saved and old ones exceeding the 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 a 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 the volume is automatically encrypted with an ephemeral key. This can be set only on volumes which have both snap_on_start and save_on_stop set to False, namely - volatile volume. This property of 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.

The storage pool driver may define additional properties.

Storage pool driver API

The storage pool driver needs 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: 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.

async destroy()[source]

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

This can be implemented as a coroutine.

get_volume(vid)[source]

Return a volume with vid from this pool

Raises:

KeyError – if no volume is found

included_in(app)[source]

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.

list_volumes()[source]

Return a list of volumes managed by this pool

async setup()[source]

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

This can be implemented as a coroutine.

property config

Returns the pool config to be written to qubes.xml

property size

Storage pool size in bytes, or None if unknown

property usage

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

property usage_details

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

property volumes

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

block_devices()[source]

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

async clone(src_vm)[source]

Clone volumes from the specified vm

async clone_volume(src_vm, name)[source]

Clone single volume from the specified vm

Parameters:
  • src_vm (QubesVM) – source VM

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

:return cloned volume object

async create()[source]

Creates volumes on disk

detach(volume)[source]

Detach a volume from domain

async export(volume)[source]

Helper function to export volume

async export_end(volume, export_path)[source]

Cleanup after exporting data from the volume

Parameters:
  • volume – volume that was exported

  • export_path – path returned by the export() call

get_disk_utilization()[source]

Returns summed up disk utilization for all domain volumes

async import_data(volume, size)[source]

Helper function to import volume data.

Size:

new size in bytes, or None if using old size

async 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

async remove()[source]

Remove all the volumes.

Errors on removal are catched and logged.

async resize(volume, size)[source]

Resizes volume a read-writable volume

async start()[source]

Execute the start method on each volume

async stop()[source]

Execute the stop method on each volume

unused_frontend()[source]

Find an unused device name

async verify()[source]

Verify that the storage is sane.

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

drive

Additional drive (currently used only by HVM)

property kernels_dir

Directory where kernel resides.

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

property outdated_volumes

Returns a list of outdated volumes

property used_frontends

Used device names

vm

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

block_device()[source]

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

async create()[source]

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

async export()[source]

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.

async export_end(path)[source]

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()

async import_data(size)[source]

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

async import_data_end(success)[source]

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

async import_volume(src_volume)[source]

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.

is_dirty()[source]

Return True if volume was not properly shutdown and committed.

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

is_outdated()[source]

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.

async remove()[source]

Remove volume.

This can be implemented as a coroutine.

async resize(size)[source]

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

async revert(revision=None)[source]

Revert volume to previous revision

This can be implemented as a coroutine.

Parameters:

revision – revision to revert volume to, see revisions

async start()[source]

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.

async start_encrypted(name)[source]

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.

async stop()[source]

Do what ever is needed on stop.

This include committing data if save_on_stop is set.

This can be implemented as a coroutine.

async stop_encrypted(name)[source]

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.

async verify()[source]

Verifies the volume.

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

This can be implemented as a coroutine.

property config

return config data for serialization to qubes.xml

property ephemeral

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

name

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

pool

Pool instance owning this volume

property revisions

Returns a dict containing revision identifiers and time of their creation

rw

Should this volume be writable by domain.

save_on_stop

Should volume state be saved or discarded at stop()

property size

Volume size in bytes

snap_on_start

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

source

source volume for snap_on_start volumes

usage = 0

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

vid

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

keys()[source]

Return list of volume IDs

values()[source]

Return list of Volumes

qubes.storage.driver_parameters(name)[source]

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

qubes.storage.isodate(seconds)[source]

Helper method which returns an iso date

qubes.storage.pool_drivers()[source]

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