btrbk: add support for deleting directories in snapshots and set them read-only

pull/648/head
Maksim Nikulin 2026-05-31 20:41:52 +03:00
parent 2bda102f94
commit cff6c47900
3 changed files with 76 additions and 11 deletions

57
btrbk
View File

@ -152,6 +152,7 @@ my %config_options = (
send_compressed_data => { default => undef, accept => [qw( yes no )] }, # NOTE: implies send_protocol=2
snapshot_qgroup_destroy => { default => undef, accept => [qw( yes no )], context => [qw( global volume subvolume )] },
snapshot_delete_dir => { default => undef, accept_file => { relative => 1 }, allow_multiple => 1, context => [qw( global volume subvolume )] },
target_qgroup_destroy => { default => undef, accept => [qw( yes no )] },
archive_qgroup_destroy => { default => undef, accept => [qw( yes no )], context => [qw( global )] },
@ -291,10 +292,12 @@ my @btrfs_cmd = (
"btrfs receive",
"btrfs filesystem usage",
"btrfs qgroup destroy",
"btrfs property set",
);
my @system_cmd = (
"readlink",
"test",
"rm",
);
my %backend_cmd_map = (
"btrfs-progs-btrbk" => { map +( $_ => [ s/ /-/gr ] ), @btrfs_cmd },
@ -1365,10 +1368,11 @@ sub btrfs_subvolume_find_new($$;$)
# returns $target, or undef on error
sub btrfs_subvolume_snapshot($$)
sub btrfs_subvolume_snapshot($$;$)
{
my $svol = shift || die;
my $target_vol = shift // die;
my $readonly = shift // 1;
my $target_path = $target_vol->{PATH} // die;
my $src_path = $svol->{PATH} // die;
INFO "[snapshot] source: $svol->{PRINT}";
@ -1377,7 +1381,8 @@ sub btrfs_subvolume_snapshot($$)
vinfo_prefixed_keys("target", $target_vol),
vinfo_prefixed_keys("source", $svol),
);
my $ret = run_cmd(cmd => vinfo_cmd($svol, "btrfs subvolume snapshot", '-r', { unsafe => $src_path }, { unsafe => $target_path } ),
my @ro_flag = $readonly ? ('-r') : ();
my $ret = run_cmd(cmd => vinfo_cmd($svol, "btrfs subvolume snapshot", @ro_flag, { unsafe => $src_path }, { unsafe => $target_path } ),
rsh => vinfo_rsh($svol),
filter_stderr => \&_btrfs_filter_stderr,
);
@ -1390,6 +1395,23 @@ sub btrfs_subvolume_snapshot($$)
}
sub btrfs_subvolume_set_readonly($)
{
my $vol = shift // die;
my $path = $vol->{PATH} // die;
INFO "[snapshot] set readonly: $vol->{PRINT}";
my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs property set", { unsafe => $path }, 'ro', 'true'),
rsh => vinfo_rsh($vol),
filter_stderr => \&_btrfs_filter_stderr,
);
unless(defined($ret)) {
ERROR "Failed to set snapshot read-only: $vol->{PRINT}", @stderr;
return undef;
}
return $vol;
}
sub btrfs_subvolume_delete($@)
{
my $vol = shift // die;
@ -6697,8 +6719,31 @@ MAIN:
# finally create the snapshot
INFO "Creating subvolume snapshot for: $svol->{PRINT}";
my $snapshot = vinfo_child($snaproot, "$snapshot_name");
if(btrfs_subvolume_snapshot($svol, $snapshot))
my @delete_dirs = @{config_key($svol, "snapshot_delete_dir") // []};
my $create_rw = scalar(@delete_dirs) ? 1 : 0;
if(btrfs_subvolume_snapshot($svol, $snapshot, !$create_rw))
{
my $snapshot_ok = 1;
if($create_rw) {
foreach my $dir (@delete_dirs) {
my $dir_path = $snapshot->{PATH} . '/' . $dir;
INFO "[snapshot] delete dir: $dir_path";
my $ret = run_cmd(cmd => vinfo_cmd($snapshot, "rm", '-rf', { unsafe => $dir_path } ),
rsh => vinfo_rsh($snapshot),
);
unless(defined($ret)) {
ERROR "Failed to delete directory \"$dir\" in snapshot: $snapshot->{PRINT}";
$snapshot_ok = 0;
last;
}
}
if($snapshot_ok) {
unless(btrfs_subvolume_set_readonly($snapshot)) {
$snapshot_ok = 0;
}
}
}
if($snapshot_ok) {
vinfo_inject_child($snaproot, $snapshot, {
parent_uuid => $svol->{node}{uuid},
received_uuid => '-',
@ -6708,6 +6753,12 @@ MAIN:
});
$svol->{SNAPSHOT_CREATED} = $snapshot;
}
else {
btrfs_subvolume_delete($snapshot);
ABORTED($svol, "Failed to process snapshot: $svol->{PRINT} -> $snapshot->{PRINT}");
WARN "Skipping subvolume section: " . ABORTED_TEXT($svol);
}
}
else {
ABORTED($svol, "Failed to create snapshot: $svol->{PRINT} -> $snapshot->{PRINT}");
WARN "Skipping subvolume section: " . ABORTED_TEXT($svol);

View File

@ -143,6 +143,16 @@ Note that using ``long-iso'' has implications on the scheduling, see
btrbk is taking care of snapshot creation). Defaults to
``always''.
*snapshot_delete_dir* <directory>::
Delete directory '<directory>' inside newly created snapshots
(relative to the snapshot root). This option can be set multiple
times, and is available in global, volume and subvolume context.
+
--
If set, btrbk creates the snapshot as read-write, deletes all
configured directories, and then sets the snapshot to read-only.
--
*incremental* yes|no|strict::
If set, incremental backups are created. If set to ``strict'',
non-incremental (initial) backups are never created, and

4
ssh_filter_btrbk.sh Executable file → Normal file
View File

@ -147,6 +147,8 @@ while [ "$#" -ge 1 ]; do
-s|--source)
allow_cmd "${sudo_prefix}btrfs subvolume snapshot"
allow_cmd "${sudo_prefix}btrfs send"
allow_exact_cmd "${sudo_prefix}btrfs property set ${file_arg_match} ro true" # set snapshot read-only
allow_exact_cmd "${sudo_prefix}rm -rf ${file_arg_match}" # delete dirs in snapshot
;;
-t|--target)
@ -169,6 +171,8 @@ while [ "$#" -ge 1 ]; do
--snapshot)
allow_cmd "${sudo_prefix}btrfs subvolume snapshot"
allow_exact_cmd "${sudo_prefix}btrfs property set ${file_arg_match} ro true" # set snapshot read-only
allow_exact_cmd "${sudo_prefix}rm -rf ${file_arg_match}" # delete dirs in snapshot
;;
--send)