Improve Btrfs hook factoring/organization (#251).

This commit is contained in:
Dan Helfman 2024-11-29 09:36:46 -08:00
parent d4a02f73b5
commit 84a0552277

View File

@ -18,6 +18,52 @@ def use_streaming(hook_config, config, log_prefix): # pragma: no cover
return False
def get_filesystem_mount_points(findmnt_command):
'''
Given a findmnt command to run, get all top-level Btrfs filesystem mount points.
'''
findmnt_output = borgmatic.execute.execute_command_and_capture_output(
(
findmnt_command,
'-nt',
'btrfs',
)
)
try:
return tuple(
line.rstrip().split(' ')[0]
for line in findmnt_output.splitlines()
)
except ValueError:
raise ValueError('Invalid {findmnt_command} output')
def get_subvolumes_for_filesystem(btrfs_command, filesystem_mount_point):
'''
Given a Btrfs command to run and a Btrfs filesystem mount point, get the subvolumes for that
filesystem.
'''
btrfs_output = borgmatic.execute.execute_command_and_capture_output(
(
btrfs_command,
'subvolume',
'list',
filesystem_mount_point,
)
)
try:
return tuple(
subvolume_path
for line in btrfs_output.splitlines()
for subvolume_subpath in (line.rstrip().split(' ')[-1],)
for subvolume_path in (os.path.join(filesystem_mount_point, subvolume_subpath),)
)
except ValueError:
raise ValueError('Invalid {btrfs_command} subvolume list output')
def get_subvolumes(btrfs_command, findmnt_command, source_directories=None):
'''
Given a Btrfs command to run and a sequence of configured source directories, find the
@ -28,53 +74,22 @@ def get_subvolumes(btrfs_command, findmnt_command, source_directories=None):
Return the result as a sequence of matching subvolume mount points.
'''
# Find all top-level Btrfs filesystem mount points.
findmnt_output = borgmatic.execute.execute_command_and_capture_output(
(
findmnt_command,
'-nt',
'btrfs',
)
)
try:
filesystem_mount_points = tuple(
line.rstrip().split(' ')[0]
for line in findmnt_output.splitlines()
)
except ValueError:
raise ValueError('Invalid {findmnt_command} output')
source_directories_set = set(source_directories or ())
source_directories_lookup = set(source_directories or ())
subvolumes = []
# For each filesystem mount point, find its subvolumes and match them again the given source
# directories to find the subvolumes to backup. Also try to match the filesystem mount point
# itself.
for mount_point in filesystem_mount_points:
if source_directories is None or mount_point in source_directories_set:
# itself (since it's implicitly a subvolume).
for mount_point in get_filesystem_mount_points(findmnt_command):
if source_directories is None or mount_point in source_directories_lookup:
subvolumes.append(mount_point)
btrfs_output = borgmatic.execute.execute_command_and_capture_output(
(
btrfs_command,
'subvolume',
'list',
mount_point,
)
subvolumes.extend(
subvolume_path
for subvolume_path in get_subvolumes_for_filesystem(btrfs_command, mount_point)
if source_directories is None or subvolume_path in source_directories_lookup
)
try:
subvolumes.extend(
subvolume_path
for line in btrfs_output.splitlines()
for subvolume_subpath in (line.rstrip().split(' ')[-1],)
for subvolume_path in (os.path.join(mount_point, subvolume_subpath),)
if source_directories is None or subvolume_path in source_directories_set
)
except ValueError:
raise ValueError('Invalid {btrfs_command} subvolume list output')
return tuple(subvolumes)
@ -169,10 +184,9 @@ def dump_data_sources(
# Based on the configured source directories, determine Btrfs subvolumes to backup.
btrfs_command = hook_config.get('btrfs_command', 'btrfs')
findmnt_command = hook_config.get('findmnt_command', 'findmnt')
requested_subvolume_paths = get_subvolumes(btrfs_command, findmnt_command, source_directories)
# Snapshot each subvolume, rewriting source directories to use their snapshot paths.
for subvolume_path in requested_subvolume_paths:
for subvolume_path in get_subvolumes(btrfs_command, findmnt_command, source_directories):
logger.debug(f'{log_prefix}: Creating Btrfs snapshot for {subvolume_path} subvolume')
snapshot_path = make_snapshot_path(subvolume_path)