Subvolume

Subvolume

1. Overview

The Subvolume class represents a BTRFS subvolume used by BastionGuard Backup.

A subvolume object can describe either:

  • A live system subvolume, such as @ or @home
  • A subvolume stored inside a BTRFS snapshot repository

The class stores the subvolume name, filesystem path, device UUID, BTRFS identifier, space-consumption information, repository association, and mount location.

It also provides operations for:

  • Detecting system subvolumes from fstab
  • Resolving the underlying block device
  • Checking whether the subvolume exists
  • Deleting BTRFS snapshot subvolumes
  • Removing nested subvolumes
  • Synchronizing BTRFS deletion operations
  • Cleaning associated quota groups
  • Restoring a snapshot subvolume as an active system subvolume

2. Class Definition

The class derives from GLib.Object:

public class Subvolume : GLib.Object

Each instance models one BTRFS subvolume and may optionally reference the SnapshotRepo that contains it.


3. Dependencies

The class uses several TeeJee utility modules:

  • TeeJee.Logging – debug, operational, and error logging
  • TeeJee.FileSystem – path construction, directory checks, and size formatting
  • TeeJee.ProcessHelper – synchronous execution of BTRFS commands
  • TeeJee.GtkHelper – GTK-related helper integration
  • TeeJee.System – system and device abstractions
  • TeeJee.Misc – shared utility functions

The class also interacts with:

  • SnapshotRepo – repository mount paths and parent context
  • Device – block-device lookup by UUID
  • FsTabEntry – system filesystem-table parsing
  • CryptTabEntry – encrypted-device configuration parsing
  • Main – BTRFS command capabilities and runtime configuration

4. Core Properties

4.1 Device UUID

public string device_uuid;

The device_uuid property identifies the filesystem containing the subvolume.

The UUID is used to resolve the corresponding Device object when device-level information or operations are required.


4.2 Subvolume Name

public string name = "";

The name property contains the logical BTRFS subvolume name.

The primary supported system names are:

@
@home
  • @ – root filesystem subvolume
  • @home – user home-directory subvolume

4.3 Subvolume Path

public string path = "";

The path property contains the filesystem path of the represented subvolume.

For snapshot subvolumes, this is normally the path of the stored snapshot copy.

For live system subvolumes, the deletion routine recalculates the path using BastionGuard’s runtime backup mounts.


4.4 BTRFS Subvolume ID

public long id = -1;

The id property stores the numeric BTRFS subvolume identifier.

The default value:

-1

indicates that the identifier has not yet been discovered.

The ID is also used to construct the associated quota-group identifier:

0/<subvolume-id>

4.5 Space Information

public int64 total_bytes = 0;
public int64 unshared_bytes = 0;
  • total_bytes – total referenced space associated with the subvolume
  • unshared_bytes – space referenced exclusively by the subvolume

These values are normally populated through BTRFS quota-group queries performed by the main repository controller.


4.6 Mount Path

public string mount_path = "";

The mount_path property contains the top-level mounted filesystem path used for BTRFS synchronization and quota operations.

When the subvolume belongs to a repository, the value is obtained from:

repo.mount_paths[name]

4.7 Parent Repository

public SnapshotRepo? repo;

The optional repo reference identifies the snapshot repository containing the subvolume.

  • A non-null repository indicates a snapshot subvolume
  • A null repository indicates a live system subvolume

5. Constructor

5.1 Subvolume()

public Subvolume(
    string name,
    string path,
    string parent_dev_uuid,
    SnapshotRepo? parent_repo
)

The constructor initializes:

  • The logical subvolume name
  • The current filesystem path
  • The parent-device UUID
  • The optional repository reference

When a repository is supplied, the subvolume mount path is loaded from the repository’s per-subvolume mount map:

this.mount_path = repo.mount_paths[name];

6. Formatted Space Properties

6.1 total_formatted

public string total_formatted

This read-only property converts total_bytes into a human-readable file-size string:

return format_file_size(total_bytes);

Example values may include:

512 MB
8.4 GB
125 GB

6.2 unshared_formatted

public string unshared_formatted

This read-only property converts unshared_bytes into a human-readable value.

The result represents the approximate amount of space that can be released when the subvolume is deleted, subject to BTRFS sharing and quota behavior.


7. Device Resolution

7.1 get_device()

public Device? get_device()

The method resolves the block device containing the subvolume through:

Device.get_device_by_uuid(device_uuid)

It returns:

  • A matching Device object when the UUID is available
  • null when no matching device can be found

8. Filesystem Existence Check

8.1 exists_on_disk

public bool exists_on_disk

This read-only property checks whether the current subvolume path exists as a directory:

return dir_exists(path);

The property verifies path presence only. It does not execute btrfs subvolume show to confirm that the directory is an actual BTRFS subvolume.


9. System Subvolume Detection

9.1 is_system_subvolume

public bool is_system_subvolume

A subvolume is classified as a live system subvolume when it has no associated repository:

return (repo == null);

This distinction affects deletion and restoration behavior.

  • System subvolumes receive runtime-generated deletion paths
  • System subvolumes cannot be restored through restore()
  • Repository subvolumes can be recreated as active system subvolumes

10. System Subvolume Discovery

10.1 detect_subvolumes_for_system_by_path()

public static Gee.HashMap<string, Subvolume>
detect_subvolumes_for_system_by_path(
    string system_path,
    SnapshotRepo? repo,
    Gtk.Window? parent_window
)

This static method detects BTRFS system subvolumes by examining the filesystem configuration of a mounted Linux installation.

The method returns a map keyed by subvolume name.


10.2 Configuration Files

The method reads:

<system-path>/etc/fstab
<system-path>/etc/crypttab

Parsing is delegated to:

FsTabEntry.read_file()
CryptTabEntry.read_file()

The crypttab entries allow encrypted filesystem references found in fstab to be resolved.


10.3 System Directory Filtering

Only filesystem-table entries representing system directories are processed:

if (!item.is_for_system_directory()){
    continue;
}

This excludes unrelated data volumes and non-system mount points.


10.4 Subvolume Option Detection

An entry is considered a BTRFS subvolume mapping when:

item.subvolume_name().length > 0

The subvolume name is extracted from the filesystem mount options, typically from values such as:

subvol=@
subvol=@home

10.5 Device Resolution

The device associated with each filesystem-table entry is resolved through:

item.resolve_device(crypttab, parent_window)

This process can use the encrypted-volume table and the supplied GTK parent window when device unlocking or user interaction is required.

If resolution fails, the subvolume is still created with an empty device UUID.


10.6 Detected Subvolume Object

A detected entry is converted into:

new Subvolume(
    item.subvolume_name(),
    item.mount_point,
    dev_uuid,
    repo
)

The resulting object is inserted into the returned map under its subvolume name.


11. Diagnostic Output

11.1 print_info()

public void print_info()

The method writes the principal subvolume properties to the debug log:

name
device UUID
subvolume ID
filesystem path

The output format is:

name=<name>, uuid=<uuid>, id=<id>, path=<path>

12. Subvolume Deletion

12.1 remove()

public bool remove()

The remove() method deletes the represented BTRFS subvolume.

The method supports both live system subvolumes and subvolumes belonging to stored snapshots.

The operation performs:

  1. Runtime path resolution for system subvolumes
  2. Existence validation
  3. BTRFS delete-option preparation
  4. Nested subvolume detection and deletion
  5. Main subvolume deletion
  6. BTRFS synchronization
  7. Quota rescan
  8. Quota-group detection and optional destruction
  9. Final quota rescan and synchronization

13. System Subvolume Deletion Paths

When the object represents a live system subvolume, its path is replaced before deletion.

For the root subvolume:

<App.mount_point_app>/backup/@

For the home subvolume:

<App.mount_point_app>/backup-home/@home

This allows the active BTRFS top-level filesystems to be mounted separately before the old system subvolumes are removed.


14. Missing Subvolume Behavior

If the target path does not exist:

if (!dir_exists(path)){
    return true;
}

A missing subvolume is treated as an already completed deletion rather than an error.

This makes the removal operation idempotent for paths that have already been deleted.


15. BTRFS Delete Options

The base deletion option is controlled by:

App.use_option_raw

When enabled, the command includes:

--commit-after

The option requests a filesystem transaction commit after deletion.


15.1 Recursive Deletion

When the installed btrfs-progs version supports recursive subvolume deletion, the command also includes:

--recursive

The capability is exposed through:

Main.btrfs_version__can_recursive_delete

The final command may therefore use:

btrfs subvolume delete --commit-after --recursive '<path>'

16. Nested Subvolume Detection

Before deleting the main path, the method checks for a possible nested subvolume:

<subvolume-path>/<subvolume-name>

For example, a path ending in @ may contain another child path named @.

The nested path is first checked as a normal directory.

If present, its BTRFS status is verified through:

btrfs subvolume show '<nested-path>'

16.1 Confirmed Nested Subvolume

If btrfs subvolume show succeeds, the nested subvolume is deleted before the parent:

btrfs subvolume delete <options> '<nested-path>'

Failure stops the entire deletion operation and returns false.


16.2 Ordinary Nested Directory

If the nested path exists but is not a BTRFS subvolume, it is not deleted separately.

The condition is written to the debug log and the main removal process continues.


17. Main Subvolume Deletion

The primary deletion command is:

btrfs subvolume delete <options> '<path>'

Standard output, standard error, and the return status are captured through exec_sync().

If the command returns a non-zero value:

  • The BTRFS error output is logged
  • A localized failure message is logged
  • The method returns false

A successful deletion produces a confirmation containing the subvolume name and ID.


18. Quota-Aware Cleanup

Additional cleanup is performed when BTRFS quota groups are enabled:

App.btrfs_qgroups_enabled

The quota-cleanup phase requires:

  • A positive subvolume ID
  • A non-null repository reference

Live system subvolumes without a repository do not enter this phase.


19. Waiting for BTRFS Deletion

The method waits for pending deletion work through:

btrfs subvolume sync <mount-path>

The command is repeated until it returns success.

Between attempts, the process sleeps for one second:

sleep(1000);

This ensures that asynchronous BTRFS cleanup has finished before quota-group operations begin.


20. Initial Quota Rescan

After synchronization, the method executes:

btrfs quota rescan <mount-path>

The rescan is repeated until it completes successfully.

This refreshes BTRFS quota information after the subvolume has been removed.


21. Quota-Group Identification

The expected quota-group identifier is constructed as:

0/<subvolume-id>

Example:

0/256

The class checks whether the quota group still exists through:

btrfs qgroup show -f '<repository-mount-path>'

The output is searched for the generated quota-group identifier.


22. Automatic Quota-Group Removal

BTRFS may automatically remove a quota group when its associated subvolume is deleted.

If the expected identifier is no longer present, the class logs that the quota group has already been removed and skips explicit destruction.

This avoids reporting a normal automatic cleanup event as a fatal error.


23. Explicit Quota-Group Destruction

If the quota group still exists, the method executes:

btrfs qgroup destroy 0/<id> '<repository-mount-path>'

A successful command produces a destruction confirmation.

A failed command is treated as non-fatal because the quota group may have been removed between the existence check and the destruction attempt.

This handles the race between BTRFS automatic cleanup and explicit application cleanup.


24. Final Quota Rescan and Sync

After quota-group processing, the method performs a second quota rescan:

btrfs quota rescan <mount-path>

It then performs a final subvolume synchronization:

btrfs subvolume sync <mount-path>

Both commands are retried until they succeed.

The method returns true only after the complete deletion and quota-cleanup workflow finishes.


25. Subvolume Restoration

25.1 restore()

public bool restore()

The restore() method recreates an active system subvolume from a subvolume stored inside a snapshot.

Restoration is implemented by creating a new writable BTRFS snapshot of the stored source subvolume.


26. System Subvolume Restriction

The method refuses to restore objects classified as live system subvolumes:

if (is_system_subvolume){
    return false;
}

Only subvolumes associated with a SnapshotRepo can act as restoration sources.


27. Restore Source and Destination

The restore source is the stored snapshot path:

string src_path = path;

The destination is calculated by combining the repository filesystem mount with the subvolume name:

string dst_path = path_combine(mount_path, name);

Typical destinations are:

<root-filesystem-mount>/@
<home-filesystem-mount>/@home

28. Restore Validation

Before creating the active subvolume, the method verifies that the source exists.

A missing source produces:

Not Found: <source-path>

The method also verifies that the destination does not already exist.

An existing destination produces:

Subvolume exists at destination: <destination-path>

In either case, restoration stops and returns false.


29. BTRFS Restore Command

The active subvolume is created with:

btrfs subvolume snapshot \
'<source-path>' \
'<destination-path>'

The command creates a writable BTRFS snapshot rather than moving or copying the stored source subvolume.

This preserves the original snapshot while producing a new active system subvolume.


30. Restore Error Handling

If the BTRFS snapshot command fails, the method logs:

  • The captured standard error
  • The numeric BTRFS return status
  • The name of the subvolume that could not be restored

The operation then returns false.

On success, the method logs:

Restored system subvolume: <name>

and returns true.


31. System Subvolume Discovery Flow

The complete detection flow is:

  1. Receive the mounted root path of a Linux installation
  2. Read its fstab and crypttab
  3. Filter entries to system directories
  4. Extract BTRFS subvolume names from mount options
  5. Resolve the corresponding device, including encrypted mappings
  6. Create one Subvolume object per detected entry
  7. Return a map keyed by subvolume name

32. Subvolume Deletion Flow

The complete deletion flow is:

  1. Resolve a runtime path when deleting a system subvolume
  2. Return success when the path no longer exists
  3. Prepare --commit-after and recursive options
  4. Check for a nested subvolume
  5. Delete the nested subvolume when confirmed
  6. Delete the primary subvolume
  7. Synchronize pending BTRFS deletion work
  8. Rescan quota information
  9. Check whether the associated quota group still exists
  10. Destroy the quota group when necessary
  11. Perform a final quota rescan
  12. Perform a final BTRFS synchronization

33. Subvolume Restore Flow

The complete restoration flow is:

  1. Reject live system-subvolume objects
  2. Use the stored snapshot subvolume as the source
  3. Calculate the active destination from the repository mount path
  4. Verify that the source exists
  5. Verify that the destination does not exist
  6. Create a writable BTRFS snapshot at the destination
  7. Report success or captured BTRFS errors

34. Runtime and Security Considerations

  • Device identity: subvolumes retain the filesystem UUID required to resolve their backing device.
  • Encrypted-volume support: system discovery can use crypttab information and interactive device resolution.
  • Existence validation: deletion and restoration check source or destination paths before executing BTRFS commands.
  • Nested-subvolume safety: a nested path is verified with btrfs subvolume show before it is deleted as a subvolume.
  • Idempotent deletion: a missing target path is treated as already removed.
  • Version-aware behavior: recursive deletion is enabled only when supported by the installed BTRFS tools.
  • Quota consistency: deletion waits for BTRFS synchronization and quota rescans before completing.
  • Quota race handling: failure to destroy an already removed quota group is treated as non-fatal.
  • Snapshot preservation: restoration creates a new BTRFS snapshot and leaves the stored source unchanged.
  • Destination protection: restoration never overwrites an existing active subvolume.

35. Implementation Considerations

  • Repository map access: the constructor directly accesses repo.mount_paths[name] and assumes that the requested key exists.
  • System classification: subvolume type is inferred exclusively from whether repo is null.
  • Filesystem existence test: exists_on_disk checks only for a directory and does not verify BTRFS subvolume metadata.
  • Limited system names: runtime deletion-path adjustment explicitly handles only @ and @home.
  • Mutable path: deleting a system subvolume permanently replaces the object’s original path value.
  • Nested-path assumption: nested-subvolume detection appends the subvolume name to its own path, producing structures such as @/@.
  • Option spacing: when --commit-after is disabled, appending --recursive produces an option string with leading whitespace.
  • Command quoting: subvolume source and destination paths are quoted, while some synchronization and quota-rescan mount paths are inserted without shell quoting.
  • Retry limits: BTRFS sync and quota-rescan loops have no timeout or maximum retry count.
  • Blocking behavior: quota synchronization runs synchronously and can block the calling thread for an extended period.
  • Quota cleanup scope: quota-group cleanup is performed only for repository subvolumes with a positive ID.
  • Non-fatal qgroup failure: all quota-group destruction failures are treated as likely race conditions and do not fail the deletion operation.
  • No restoration rollback: the method does not remove a partially created destination if an unexpected post-command condition occurs.
  • Writable restore: the BTRFS snapshot command does not use -r, so restored system subvolumes are writable.
  • Device-resolution fallback: unresolved filesystem-table devices create subvolume objects with an empty UUID.

36. Licensing

The source file is distributed under the GNU General Public License, version 2 or, at the user’s option, any later version.

The original copyright notice identifies:

Copyright 2012-2018 Tony George
<teejeetech@gmail.com>

The software is provided without warranty, including implied warranties of merchantability or fitness for a particular purpose.