Snapshot

1. Overview

The Snapshot class represents a single BastionGuard Backup snapshot and provides the runtime model used to load, inspect, update, tag, validate, and delete it.

The class supports both snapshot engines:

  • RSYNC snapshots – filesystem copies stored below a localhost directory
  • BTRFS snapshots – native subvolume snapshots based on @ and optionally @home

Each object combines filesystem information with metadata loaded from the snapshot control file. It also exposes paths for exclusion files and RSYNC logs, loads the snapshot’s fstab and crypttab, manages tags and comments, and delegates physical deletion to the appropriate backend.

The class integrates the following internal components:

  • SnapshotRepo – repository paths, mount points, and device associations
  • Subvolume – BTRFS subvolume metadata and deletion
  • DeleteFileTask – asynchronous deletion of RSYNC snapshots
  • RsyncTask – parsing and generation of RSYNC change logs
  • FsTabEntry – parsing of the snapshot’s fstab
  • CryptTabEntry – parsing of the snapshot’s crypttab
  • LinuxDistro – detection of the Linux distribution stored inside the snapshot
  • Json – reading and writing snapshot metadata
  • Gee – lists and maps used for tags, exclusions, and subvolumes

2. Class Definition

The class derives from GLib.Object:

public class Snapshot : GLib.Object

A Snapshot object is initialized from an existing snapshot directory and an associated SnapshotRepo.

The object is responsible for loading the snapshot state from disk during construction.


3. Core Snapshot Properties

3.1 Filesystem Identity

The snapshot location and directory name are stored in:

public string path = "";
public string name = "";
  • path – complete filesystem path of the snapshot
  • name – final directory component, normally the snapshot timestamp

A typical snapshot name is:

2026-07-12_10-30-00

3.2 Creation Date

public DateTime date;

The creation date is loaded from the Unix timestamp stored in info.json.

The timestamp is interpreted as UTC and converted to local time:

DateTime date_utc = new DateTime.from_unix_utc(int64.parse(val));
date = date_utc.to_local();

Before metadata is loaded, the constructor initializes the date to Unix epoch time.


3.3 Source System Information

The class stores information about the system from which the snapshot was created:

public string sys_uuid = "";
public string sys_distro = "";
public string app_version = "";
  • sys_uuid – UUID of the source root filesystem
  • sys_distro – distribution description recorded during snapshot creation
  • app_version – BastionGuard Backup version that created the snapshot

3.4 Description and File Count

public string description = "";
public int64 file_count = 0;

The description contains user comments associated with the snapshot.

The file count normally represents the number of entries processed during an RSYNC snapshot. BTRFS snapshots commonly store a value of zero because they are created at the subvolume level.


3.5 Validity and Runtime Flags

public bool valid = true;
public bool live = false;
public bool marked_for_deletion = false;
  • valid – indicates whether the required snapshot metadata is available
  • live – identifies a live or pre-restore BTRFS snapshot
  • marked_for_deletion – mirrors the presence of the snapshot deletion marker

4. Snapshot Collections

The class maintains several collections:

public Gee.ArrayList<string> tags;
public Gee.ArrayList<string> exclude_list;
public Gee.HashMap<string, Subvolume> subvolumes;
public Gee.ArrayList<FsTabEntry> fstab_list;
public Gee.ArrayList<CryptTabEntry> cryttab_list;
  • tags – schedule and classification tags
  • exclude_list – backup exclusions stored with the snapshot
  • subvolumes – BTRFS subvolumes belonging to the snapshot
  • fstab_list – parsed filesystem table from the snapshot
  • cryttab_list – parsed encrypted-volume table from the snapshot

5. BTRFS-Specific State

BTRFS behavior is controlled by:

public bool btrfs_mode = false;

The class also maintains a map of snapshot paths for individual subvolumes:

public Gee.HashMap<string, string> paths;

The map normally contains entries for:

@
@home

Additional mount-path fields are declared for the root and home subvolumes:

public string mount_path_root = "";
public string mount_path_home = "";

In the current implementation, subvolume path resolution is primarily performed through paths and the repository’s mount_paths map.


6. Repository and Deletion Task

Each snapshot retains a reference to its repository:

public SnapshotRepo repo;

RSYNC snapshot deletion is delegated to:

public DeleteFileTask delete_file_task;

The task provides deletion progress, remaining-time estimates, status messages, and cancellation state.


7. Constructor

7.1 Snapshot()

public Snapshot(
    string dir_path,
    bool btrfs_snapshot,
    SnapshotRepo _repo
)

The constructor receives:

  • The snapshot directory path
  • The snapshot engine type
  • The repository containing the snapshot

The initialization sequence is:

  1. Create a Gio.File object for the directory
  2. Read the directory information
  3. Store the path and directory name
  4. Store the BTRFS mode and repository reference
  5. Initialize the date and all collections
  6. Create the RSYNC deletion task
  7. Read the snapshot control file
  8. Read the exclusion list
  9. Read the snapshot’s fstab
  10. Read the snapshot’s crypttab

Filesystem errors are caught and written to the application log.


8. Formatted Date Property

8.1 date_formatted

public string date_formatted

This read-only property formats the snapshot creation date using the application-wide date format:

return date.format(App.date_format);

The resulting value is suitable for display in snapshot lists and dialogs.


9. RSYNC Log Path Properties

The class exposes computed paths for the main RSYNC logs.

9.1 Backup Log

public string rsync_log_file

Returns:

<snapshot>/rsync-log

9.2 Backup Changes Log

public string rsync_changes_log_file

Returns:

<snapshot>/rsync-log-changes

9.3 Restore Log

public string rsync_restore_log_file

Returns:

<snapshot>/rsync-log-restore

9.4 Restore Changes Log

public string rsync_restore_changes_log_file

Returns:

<snapshot>/rsync-log-restore-changes

10. Exclusion File Properties

10.1 Backup Exclusion File

public string exclude_file_for_backup

Returns:

<snapshot>/exclude.list

10.2 Restore Exclusion File

public string exclude_file_for_restore

Returns:

<snapshot>/exclude-restore.list

11. Tag Management

Snapshot tags identify the reason or schedule associated with a snapshot.

Supported values normally include:

ondemand
boot
hourly
daily
weekly
monthly

11.1 taglist

public string taglist

The getter joins all tags into a space-separated string.

Example:

daily weekly monthly

The setter clears the existing list, splits the supplied string by spaces, and adds unique values.


11.2 taglist_short

public string taglist_short

This read-only property converts long tag names into compact display codes:

ondemand → O
boot     → B
hourly   → H
daily    → D
weekly   → W
monthly  → M

For example:

daily weekly monthly

becomes:

D W M

11.3 add_tag()

public void add_tag(string tag)

The supplied value is trimmed and added only when it is not already present.

After insertion, the control file is updated immediately.


11.4 remove_tag()

public void remove_tag(string tag)

The supplied tag is removed when present. The updated tag list is then persisted to info.json.


11.5 has_tag()

public bool has_tag(string tag)

Returns whether the normalized tag exists in the snapshot’s tag list.


12. Snapshot Control File

The primary metadata file for every snapshot is:

<snapshot>/info.json

This file records the snapshot creation time, source system, application version, tags, comments, engine type, and BTRFS subvolume metadata.


13. Reading the Control File

13.1 read_control_file()

public void read_control_file()

The method checks for info.json and loads it through Json.Parser.

If the file is missing, the snapshot is marked invalid:

valid = false;

If the parsed root node or JSON object is unavailable, the snapshot is also marked invalid.


13.2 Loaded Metadata

The following keys are read:

created
sys-uuid
sys-distro
tags
comments
app-version
file_count
live
type
subvolumes

The values are mapped as follows:

  • created → date
  • sys-uuid → sys_uuid
  • sys-distro → sys_distro
  • tags → taglist
  • comments → description
  • app-version → app_version
  • file_count → file_count
  • live → live

13.3 Snapshot Type and Distribution Detection

The metadata key type defaults to:

rsync

The class selects a filesystem root for distribution detection:

  • localhost for RSYNC snapshots
  • @ for BTRFS snapshots

The distribution is detected through:

LinuxDistro.get_dist_info()

14. BTRFS Subvolume Metadata Loading

When info.json contains a subvolumes object, only the following names are accepted:

@
@home

Other subvolume names are ignored.


14.1 Repository Path Translation

A snapshot may span separate root and home BTRFS filesystems.

The path for each subvolume is calculated by replacing the repository’s primary mount path with the mount path associated with that subvolume:

paths[subvol_name] =
    path.replace(
        repo.mount_path,
        repo.mount_paths[subvol_name]
    );

The actual subvolume path is then assembled by appending the subvolume name.


14.2 Subvolume Object Creation

If the calculated directory exists, a new Subvolume object is created and inserted into the snapshot’s map:

subvolumes.set(subvol_name, subvolume);

14.3 Stored Subvolume Fields

Each subvolume is represented in JSON as an ordered array containing:

  1. Subvolume name
  2. Subvolume ID
  3. Total referenced bytes
  4. Unshared bytes
  5. Device UUID

Example logical structure:

"@": [
  "@",
  "256",
  "21474836480",
  "10737418240",
  "filesystem-uuid"
]

The numeric values are stored as strings and parsed into their respective integer fields.


15. Deletion Marker Detection

After loading the control file, the method checks for:

<snapshot>/delete

If the file exists:

marked_for_deletion = true;

The marker allows deletion state to persist across application sessions.


16. Reading the Backup Exclusion List

16.1 read_exclude_list()

public void read_exclude_list()

The method reads:

<snapshot>/exclude.list

Before loading the file, the current exclusion collection is cleared.

Each line is:

  • Trimmed
  • Ignored when empty
  • Added only when it is not already present

If the file is missing:

  • An RSYNC snapshot is marked invalid
  • A BTRFS snapshot remains valid

BTRFS snapshots do not require an RSYNC exclusion list because their contents are captured through native subvolume snapshots.


17. Reading fstab

17.1 read_fstab_file()

public void read_fstab_file()

For RSYNC snapshots, the filesystem table is read from:

<snapshot>/localhost/etc/fstab

For BTRFS snapshots, it is read from:

<snapshot>/@/etc/fstab

Parsing is delegated to:

FsTabEntry.read_file(fstab_path)

The resulting entries are used during restore-target mapping and filesystem configuration repair.


18. Reading crypttab

18.1 read_crypttab_file()

public void read_crypttab_file()

For RSYNC snapshots, the encrypted-volume table is read from:

<snapshot>/localhost/etc/crypttab

For BTRFS snapshots, it is read from:

<snapshot>/@/etc/crypttab

Parsing is delegated to:

CryptTabEntry.read_file(crypttab_path)

The stored data is later used when reconstructing encrypted target mappings during restoration.


19. Updating the Control File

19.1 update_control_file()

public void update_control_file()

The method updates mutable snapshot metadata without rebuilding the complete snapshot.

The following values are replaced:

  • tags
  • comments
  • live
  • subvolumes, when using BTRFS

19.2 BTRFS Subvolume Serialization

For each subvolume, the method writes an array containing:

name
id
total_bytes
unshared_bytes
device_uuid

All values are serialized as JSON strings.


19.3 JSON Rewrite

The JSON generator is configured with:

json.pretty = true;
json.indent = 2;

The existing info.json file is deleted before the updated document is written.

Filesystem and JSON errors are caught and logged.


20. Removing the Control File

20.1 remove_control_file()

public void remove_control_file()

The method deletes:

<snapshot>/info.json

Deletion is delegated to the shared file_delete() utility.

Removing the control file causes the snapshot to be considered invalid during future loading.


21. Creating a Control File

21.1 write_control_file()

public static Snapshot write_control_file(
    string snapshot_path,
    DateTime dt_created,
    string root_uuid,
    string distro_full_name,
    string tag,
    string comments,
    int64 item_count,
    bool is_btrfs,
    bool is_live,
    SnapshotRepo repo,
    bool silent = false
)

This static factory method creates the initial info.json document for a new snapshot.


21.2 Initial Metadata

The following keys are written:

created
sys-uuid
sys-distro
app-version
file_count
tags
comments
live
type

The creation date is converted to UTC and stored as a Unix timestamp:

dt_created.to_utc().to_unix()

The snapshot type is written as:

btrfs
rsync

depending on the selected engine.


21.3 Existing File Replacement

If an info.json file already exists, it is deleted before the new file is written.

Unless silent is enabled, the created control-file path is written to the application log.


21.4 Snapshot Factory Behavior

After writing the file, the method creates and returns a fully initialized Snapshot object:

return new Snapshot(
    snapshot_path,
    is_btrfs,
    repo
);

This causes the newly written metadata, exclusion list, fstab, and crypttab to be loaded immediately.


22. Subvolume Detection

22.1 has_subvolumes()

public bool has_subvolumes()

The method scans the loaded fstab entries and checks whether any mount options contain:

subvol=@

It returns true when an Ubuntu-style BTRFS root subvolume is referenced.

This check is based on the stored filesystem table rather than directly inspecting the subvolumes map.


23. Sorted Subvolume List

23.1 subvolumes_sorted

public Gee.ArrayList<Subvolume> subvolumes_sorted

This read-only property copies all values from the subvolume map into a new list.

The list is sorted alphabetically by subvolume name before being returned.

Typical order:

@
@home

24. Snapshot Removal Dispatcher

24.1 remove()

public bool remove(bool wait)

This method is the common deletion entry point.

If the snapshot directory no longer exists, deletion is considered successful.

Otherwise, the method delegates to:

  • remove_btrfs() for BTRFS snapshots
  • remove_rsync(wait) for RSYNC snapshots

25. RSYNC Snapshot Removal

25.1 remove_rsync()

public bool remove_rsync(bool wait)

The method configures the snapshot’s DeleteFileTask with:

  • The snapshot directory as the destination path
  • A localized deletion status message
  • The estimated first-snapshot file count for progress calculation

The destination path is passed with a trailing slash:

<snapshot-path>/

The deletion task is then started through:

delete_file_task.execute();

25.2 Synchronous Waiting

When wait is true, the method waits while the task status is RUNNING.

During the wait loop it:

  • Sleeps for one second
  • Processes pending GTK events
  • Prints the deletion percentage
  • Prints the estimated remaining time
  • Flushes standard output

After completion, the progress line is cleared and a removal message is written to the log.


26. BTRFS Snapshot Removal

26.1 remove_btrfs()

public bool remove_btrfs()

BTRFS deletion is performed in multiple phases.

  1. Delete every BTRFS subvolume
  2. Delete the corresponding per-filesystem snapshot directories
  3. Delete the primary snapshot directory
  4. Write a success message to the log

26.2 Subvolume Deletion

Each subvolume is removed through:

subvol.remove()

If any subvolume cannot be deleted, the operation stops immediately and returns false.


26.3 Directory Cleanup

Directories are removed only after all subvolume deletions have succeeded.

The method first deletes each path stored in:

paths[subvol.name]

It then removes the primary snapshot path.

Failure during any directory-cleanup step causes the method to return false.


27. Persistent Deletion Marking

27.1 mark_for_deletion()

public void mark_for_deletion()

The method uses the following marker file:

<snapshot>/delete

Its behavior is toggle-based:

  • If the marker does not exist, it is created and marked_for_deletion becomes true
  • If the marker already exists, it is removed and marked_for_deletion becomes false

The deletion state therefore persists even when the application is closed and reopened.


28. RSYNC Log Parsing

28.1 parse_log_file()

public void parse_log_file()

The method creates a temporary RsyncTask and delegates parsing of:

<snapshot>/rsync-log

The parser is responsible for archiving the primary log and generating:

<snapshot>/rsync-log-changes

This secondary file contains a simplified list of filesystem changes associated with the snapshot.


29. Snapshot Directory Layout

29.1 RSYNC Snapshot

A typical RSYNC snapshot contains:

snapshot/
├── info.json
├── exclude.list
├── localhost/
│   ├── etc/
│   │   ├── fstab
│   │   └── crypttab
│   └── ...
├── rsync-log
├── rsync-log-changes
└── delete

The delete file exists only when the snapshot is marked for removal.


29.2 BTRFS Snapshot

A typical BTRFS snapshot contains metadata and one or more native subvolumes:

snapshot/
├── info.json
├── @/
│   ├── etc/
│   │   ├── fstab
│   │   └── crypttab
│   └── ...
├── @home/
└── delete

When @home resides on another BTRFS filesystem, its physical snapshot path is resolved through the repository’s per-subvolume mount map.


30. Snapshot Loading Flow

The complete object-loading flow is:

  1. Read the snapshot directory information
  2. Initialize collections and task objects
  3. Read info.json
  4. Load tags, comments, source-system data, and creation date
  5. Detect the distribution stored inside the snapshot
  6. Reconstruct BTRFS subvolume objects when present
  7. Check for the persistent deletion marker
  8. Load the backup exclusion list
  9. Parse the snapshot’s fstab
  10. Parse the snapshot’s crypttab

31. Runtime and Security Considerations

  • Metadata validation: snapshots without a readable control file are marked invalid.
  • RSYNC validation: an RSYNC snapshot without exclude.list is considered invalid.
  • BTRFS flexibility: BTRFS snapshots do not require an RSYNC exclusion file.
  • Persistent deletion state: a marker file retains deletion selection across sessions.
  • Engine-specific deletion: RSYNC and BTRFS snapshots use separate deletion implementations.
  • Ordered BTRFS cleanup: subvolumes are deleted before their parent directories.
  • Repository awareness: BTRFS paths are translated using repository mount information.
  • Restore metadata: the stored fstab and crypttab support target-system reconstruction.
  • Local-time display: creation timestamps are stored in UTC and converted for presentation.
  • Error containment: filesystem and JSON errors are logged rather than propagated to most callers.

32. Implementation Considerations

  • Constructor error state: a filesystem exception during construction is logged, but the constructor does not explicitly set valid to false.
  • JSON parser state: after a parser exception, the method still attempts to access the parser’s root node.
  • Tag normalization: tags are trimmed but are not converted to a uniform letter case.
  • Empty tag handling: assigning an empty taglist may add an empty string to the collection after splitting the value.
  • Snapshot engine authority: btrfs_mode is supplied to the constructor; the type field from JSON is used for distribution-path selection but does not directly replace the constructor value.
  • Control-file rewrite: update_control_file() deletes the existing metadata file before writing the replacement, without using a temporary-file rename strategy.
  • String-encoded values: numeric and Boolean metadata is frequently stored as JSON strings instead of native JSON numbers or Boolean values.
  • Initial BTRFS metadata: write_control_file() does not initially add the subvolumes object; it is written by a later update_control_file() call.
  • RSYNC deletion result: remove_rsync() returns true without inspecting the final task status or cancellation result.
  • Deletion method naming: mark_for_deletion() toggles the marker instead of only marking the snapshot.
  • Synchronous polling: waiting for RSYNC deletion uses a polling loop with GTK event processing.
  • Subvolume detection: has_subvolumes() inspects fstab options instead of checking the populated subvolume map.
  • Limited subvolume names: metadata loading accepts only @ and @home.
  • Path replacement: BTRFS path translation assumes that the repository mount path occurs in the snapshot path.

33. 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.