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 subvolumeunshared_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
Deviceobject when the UUID is available nullwhen 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:
- Runtime path resolution for system subvolumes
- Existence validation
- BTRFS delete-option preparation
- Nested subvolume detection and deletion
- Main subvolume deletion
- BTRFS synchronization
- Quota rescan
- Quota-group detection and optional destruction
- 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:
- Receive the mounted root path of a Linux installation
- Read its
fstabandcrypttab - Filter entries to system directories
- Extract BTRFS subvolume names from mount options
- Resolve the corresponding device, including encrypted mappings
- Create one
Subvolumeobject per detected entry - Return a map keyed by subvolume name
32. Subvolume Deletion Flow
The complete deletion flow is:
- Resolve a runtime path when deleting a system subvolume
- Return success when the path no longer exists
- Prepare
--commit-afterand recursive options - Check for a nested subvolume
- Delete the nested subvolume when confirmed
- Delete the primary subvolume
- Synchronize pending BTRFS deletion work
- Rescan quota information
- Check whether the associated quota group still exists
- Destroy the quota group when necessary
- Perform a final quota rescan
- Perform a final BTRFS synchronization
33. Subvolume Restore Flow
The complete restoration flow is:
- Reject live system-subvolume objects
- Use the stored snapshot subvolume as the source
- Calculate the active destination from the repository mount path
- Verify that the source exists
- Verify that the destination does not exist
- Create a writable BTRFS snapshot at the destination
- 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
crypttabinformation 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 showbefore 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
repois null. - Filesystem existence test:
exists_on_diskchecks 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
pathvalue. - Nested-path assumption: nested-subvolume detection appends the subvolume name to its own path, producing structures such as
@/@. - Option spacing: when
--commit-afteris disabled, appending--recursiveproduces 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.
