btrbk: adapt all actions to use vinfo_subsection()

pull/73/head
Axel Burri 2016-03-07 21:54:51 +01:00
parent fbbd82114d
commit f770488d85
1 changed files with 66 additions and 121 deletions

187
btrbk
View File

@ -598,9 +598,7 @@ sub config_key($$;@)
my $node = shift || die; my $node = shift || die;
my $key = shift || die; my $key = shift || die;
my %opts = @_; my %opts = @_;
$node = $node->{CONFIG} if($node->{CONFIG}); # accept vinfo for $node
# accept vinfo as $node
$node = $node->{CONFIG} if($node->{CONFIG});
TRACE "config_key: context=$node->{CONTEXT}, key=$key"; TRACE "config_key: context=$node->{CONTEXT}, key=$key";
@ -629,6 +627,7 @@ sub config_dump_keys($;@)
my %opts = @_; my %opts = @_;
my @ret; my @ret;
my $maxlen = 0; my $maxlen = 0;
$config = $config->{CONFIG} if($config->{CONFIG}); # accept vinfo for $config
foreach my $key (sort keys %config_options) foreach my $key (sort keys %config_options)
{ {
@ -2352,17 +2351,12 @@ sub print_formatted(@)
} }
sub exit_status($) sub exit_status
{ {
my $config = shift; my $config = shift;
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $subsection (@{$config->{SUBSECTION}}) {
return 10 if($config_vol->{ABORTED} && ($config_vol->{ABORTED} ne "USER_SKIP")); return 10 if($subsection->{ABORTED} && ($subsection->{ABORTED} ne "USER_SKIP"));
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) { return 10 if(exit_status($subsection));
return 10 if($config_subvol->{ABORTED} && ($config_subvol->{ABORTED} ne "USER_SKIP"));
foreach my $config_target (@{$config_subvol->{TARGET}}) {
return 10 if($config_target->{ABORTED} && ($config_target->{ABORTED} ne "USER_SKIP"));
}
}
} }
return 0; return 0;
} }
@ -2723,13 +2717,13 @@ MAIN:
if(($action_run || $action_clean || $action_resolve || $action_usage || $action_list || $action_config_print) && scalar(@filter_args)) if(($action_run || $action_clean || $action_resolve || $action_usage || $action_list || $action_config_print) && scalar(@filter_args))
{ {
my %match; my %match;
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume', 1)) {
my $vol_url = $config_vol->{url} // die; my $vol_url = $sroot->{URL};
my $found_vol = 0; my $found_vol = 0;
foreach my $filter (@filter_args) { foreach my $filter (@filter_args) {
if(($vol_url eq $filter) || (map { ($filter eq $_) || () } @{$config_vol->{group}})) { if(($vol_url eq $filter) || (map { ($filter eq $_) || () } @{$sroot->{CONFIG}->{group}})) {
TRACE "filter argument \"$filter\" matches volume: $vol_url\n"; TRACE "filter argument \"$filter\" matches volume: $vol_url\n";
$match{$filter} = ($vol_url eq $filter) ? "volume=" . vinfo($vol_url, $config_vol)->{PRINT} : "group=$filter"; $match{$filter} = ($vol_url eq $filter) ? "volume=$sroot->{PRINT}" : "group=$filter";
$found_vol = 1; $found_vol = 1;
# last; # need to cycle through all filter_args for correct %match # last; # need to cycle through all filter_args for correct %match
} }
@ -2737,13 +2731,13 @@ MAIN:
next if($found_vol); next if($found_vol);
my @filter_subvol; my @filter_subvol;
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) { foreach my $svol (vinfo_subsection($sroot, 'subvolume', 1)) {
my $subvol_url = $config_subvol->{url} // die; my $subvol_url = $svol->{URL};
my $found_subvol = 0; my $found_subvol = 0;
foreach my $filter (@filter_args) { foreach my $filter (@filter_args) {
if(($subvol_url eq $filter) || (map { ($filter eq $_) || () } @{$config_subvol->{group}})) { if(($subvol_url eq $filter) || (map { ($filter eq $_) || () } @{$svol->{CONFIG}->{group}})) {
TRACE "filter argument \"$filter\" matches subvolume: $subvol_url\n"; TRACE "filter argument \"$filter\" matches subvolume: $subvol_url\n";
$match{$filter} = ($subvol_url eq $filter) ? "subvolume=" . vinfo($subvol_url, $config_subvol)->{PRINT} : "group=$filter"; $match{$filter} = ($subvol_url eq $filter) ? "subvolume=$svol->{PRINT}" : "group=$filter";
$found_subvol = 1; $found_subvol = 1;
$found_vol = 1; $found_vol = 1;
# last; # need to cycle through all filter_args for correct %match # last; # need to cycle through all filter_args for correct %match
@ -2751,16 +2745,16 @@ MAIN:
} }
next if($found_subvol); next if($found_subvol);
my $snapshot_name = $config_subvol->{snapshot_name} // die; my $snapshot_name = config_key($svol, "snapshot_name") // die;
foreach my $config_target (@{$config_subvol->{TARGET}}) { foreach my $droot (vinfo_subsection($svol, 'target', 1)) {
my $target_url = $config_target->{url} // die; my $target_url = $droot->{URL};
my $found_target = 0; my $found_target = 0;
foreach my $filter (@filter_args) { foreach my $filter (@filter_args) {
if(($filter eq $target_url) || if(($filter eq $target_url) ||
($filter eq "$target_url/$snapshot_name") || ($filter eq "$target_url/$snapshot_name") ||
(map { ($filter eq $_) || () } @{$config_target->{group}})) { (map { ($filter eq $_) || () } @{$droot->{CONFIG}->{group}})) {
TRACE "filter argument \"$filter\" matches target: $target_url\n"; TRACE "filter argument \"$filter\" matches target: $target_url\n";
$match{$filter} = ($target_url eq $filter) ? "target=" . vinfo($target_url, $config_target)->{PRINT} : "group=$filter"; $match{$filter} = ($target_url eq $filter) ? "target=$droot->{PRINT}" : "group=$filter";
$found_target = 1; $found_target = 1;
$found_subvol = 1; $found_subvol = 1;
$found_vol = 1; $found_vol = 1;
@ -2769,17 +2763,17 @@ MAIN:
} }
unless($found_target) { unless($found_target) {
DEBUG "No match on filter command line argument, skipping target: $target_url"; DEBUG "No match on filter command line argument, skipping target: $target_url";
ABORTED($config_target, "USER_SKIP"); ABORTED($droot, "USER_SKIP");
} }
} }
unless($found_subvol) { unless($found_subvol) {
DEBUG "No match on filter command line argument, skipping subvolume: $subvol_url"; DEBUG "No match on filter command line argument, skipping subvolume: $subvol_url";
ABORTED($config_subvol, "USER_SKIP"); ABORTED($svol, "USER_SKIP");
} }
} }
unless($found_vol) { unless($found_vol) {
DEBUG "No match on filter command line argument, skipping volume: $vol_url"; DEBUG "No match on filter command line argument, skipping volume: $vol_url";
ABORTED($config_vol, "USER_SKIP"); ABORTED($sroot, "USER_SKIP");
} }
} }
# make sure all args have a match # make sure all args have a match
@ -2801,9 +2795,7 @@ MAIN:
# #
my @data; my @data;
my %processed; my %processed;
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED});
my $sroot = vinfo($config_vol->{url}, $config_vol);
unless($processed{$sroot->{URL}}) { unless($processed{$sroot->{URL}}) {
my $usage = btrfs_filesystem_usage($sroot) // {}; my $usage = btrfs_filesystem_usage($sroot) // {};
push @data, { %$usage, push @data, { %$usage,
@ -2814,13 +2806,9 @@ MAIN:
} }
} }
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED}); foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
my $sroot = vinfo($config_vol->{url}, $config_vol); foreach my $droot (vinfo_subsection($svol, 'target')) {
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) {
next if($config_subvol->{ABORTED});
foreach my $config_target (@{$config_subvol->{TARGET}}) {
my $droot = vinfo($config_target->{url}, $config_target);
unless($processed{$droot->{URL}}) { unless($processed{$droot->{URL}}) {
my $usage = btrfs_filesystem_usage($droot) // {}; my $usage = btrfs_filesystem_usage($droot) // {};
push @data, { %$usage, push @data, { %$usage,
@ -2845,25 +2833,16 @@ MAIN:
# #
my @out; my @out;
push @out, config_dump_keys($config, skip_defaults => 1); push @out, config_dump_keys($config, skip_defaults => 1);
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED});
my $sroot = vinfo($config_vol->{url}, $config_vol);
push @out, "\nvolume $sroot->{URL}"; push @out, "\nvolume $sroot->{URL}";
push @out, config_dump_keys($config_vol, prefix => "\t", resolve => $resolve); push @out, config_dump_keys($sroot, prefix => "\t", resolve => $resolve);
foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) {
next if($config_subvol->{ABORTED});
my $svol = vinfo_child($sroot, $config_subvol->{rel_path});
# push @out, "\n subvolume $svol->{URL}"; # push @out, "\n subvolume $svol->{URL}";
push @out, "\n\tsubvolume $svol->{SUBVOL_PATH}"; push @out, "\n\tsubvolume $svol->{SUBVOL_PATH}";
push @out, config_dump_keys($config_subvol, prefix => "\t\t", resolve => $resolve); push @out, config_dump_keys($svol, prefix => "\t\t", resolve => $resolve);
foreach my $droot (vinfo_subsection($svol, 'target')) {
foreach my $config_target (@{$config_subvol->{TARGET}}) push @out, "\n\t\ttarget $droot->{CONFIG}->{target_type} $droot->{URL}";
{ push @out, config_dump_keys($droot, prefix => "\t\t\t", resolve => $resolve);
next if($config_target->{ABORTED});
my $droot = vinfo($config_target->{url}, $config_target);
push @out, "\n\t\ttarget $config_target->{target_type} $droot->{URL}";
push @out, config_dump_keys($config_target, prefix => "\t\t\t", resolve => $resolve);
} }
} }
} }
@ -2889,31 +2868,24 @@ MAIN:
# #
# print configuration lines, machine readable # print configuration lines, machine readable
# #
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED});
my $sroot = vinfo($config_vol->{url}, $config_vol);
my $volh = { vinfo_prefixed_keys("volume", $sroot) }; my $volh = { vinfo_prefixed_keys("volume", $sroot) };
push @vol_data, $volh; push @vol_data, $volh;
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) { foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
next if($config_subvol->{ABORTED});
my $svol = vinfo_child($sroot, $config_subvol->{rel_path});
my $subvolh = { %$volh, my $subvolh = { %$volh,
vinfo_prefixed_keys("source", $svol), vinfo_prefixed_keys("source", $svol),
snapshot_path => $sroot->{PATH} . (config_key($config_subvol, "snapshot_dir", prefix => '/') // ""), snapshot_path => $sroot->{PATH} . (config_key($svol, "snapshot_dir", prefix => '/') // ""),
snapshot_name => config_key($config_subvol, "snapshot_name"), snapshot_name => config_key($svol, "snapshot_name"),
snapshot_preserve => format_preserve_matrix(config => $config_subvol, prefix => "snapshot"), snapshot_preserve => format_preserve_matrix(config => $svol->{CONFIG}, prefix => "snapshot"),
}; };
push @subvol_data, $subvolh; push @subvol_data, $subvolh;
my $found = 0; my $found = 0;
foreach my $config_target (@{$config_subvol->{TARGET}}) foreach my $droot (vinfo_subsection($svol, 'target')) {
{
next if($config_target->{ABORTED});
my $droot = vinfo($config_target->{url}, $config_target);
my $targeth = { %$subvolh, my $targeth = { %$subvolh,
vinfo_prefixed_keys("target", $droot), vinfo_prefixed_keys("target", $droot),
target_preserve => format_preserve_matrix(config => $config_target, prefix => "target"), target_preserve => format_preserve_matrix(config => $droot->{CONFIG}, prefix => "target"),
}; };
if($action_list eq "target") { if($action_list eq "target") {
next if($target_uniq{$droot->{URL}}); next if($target_uniq{$droot->{URL}});
@ -3051,7 +3023,7 @@ MAIN:
} }
} }
if(ABORTED($droot)) { if(ABORTED($droot)) {
WARN "Skipping target \"$droot->{PRINT}\": " . ABORTED($droot); WARN "Skipping target \"$droot->{PRINT}\": " . ABORTED($droot);
next; next;
} }
DEBUG "Found " . scalar(keys %subvol_list) . " raw subvolume backups of: $svol->{PRINT}"; DEBUG "Found " . scalar(keys %subvol_list) . " raw subvolume backups of: $svol->{PRINT}";
@ -3145,9 +3117,9 @@ MAIN:
my $lines = []; my $lines = [];
_origin_tree("", $vol->{uuid}, $lines); _origin_tree("", $vol->{uuid}, $lines);
print_header(title => "Origin Tree", print_header(title => "Origin Tree",
config => $config, config => $config,
time => $start_time, time => $start_time,
legend => [ legend => [
"^-- : received from subvolume", "^-- : received from subvolume",
"newline : parent subvolume", "newline : parent subvolume",
@ -3182,13 +3154,9 @@ MAIN:
# #
# print all snapshots and their receive targets # print all snapshots and their receive targets
# #
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED}); foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
my $sroot = $config_vol->{sroot} || die; my $snapshot_name = config_key($svol, "snapshot_name") // die;
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) {
next if($config_subvol->{ABORTED});
my $svol = $config_subvol->{svol} || die;
my $snapshot_name = config_key($config_subvol, "snapshot_name") // die;
foreach my $snapshot (sort { $a->{cgen} <=> $b->{cgen} } get_snapshot_children($sroot, $svol)) { foreach my $snapshot (sort { $a->{cgen} <=> $b->{cgen} } get_snapshot_children($sroot, $svol)) {
my $snapshot_data = { type => "snapshot", my $snapshot_data = { type => "snapshot",
status => ($snapshot->{cgen} == $svol->{gen}) ? "up-to-date" : undef, status => ($snapshot->{cgen} == $svol->{gen}) ? "up-to-date" : undef,
@ -3197,9 +3165,7 @@ MAIN:
snapshot_name => $snapshot_name, snapshot_name => $snapshot_name,
}; };
my $found = 0; my $found = 0;
foreach my $config_target (@{$config_subvol->{TARGET}}) { foreach my $droot (vinfo_subsection($svol, 'target')) {
next if($config_target->{ABORTED});
my $droot = $config_target->{droot} || die;
$droot_compat{$droot->{URL}} = 1 if($droot->{BTRFS_PROGS_COMPAT}); $droot_compat{$droot->{URL}} = 1 if($droot->{BTRFS_PROGS_COMPAT});
foreach (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } get_receive_targets($droot, $snapshot)) { foreach (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } get_receive_targets($droot, $snapshot)) {
push @data, { %$snapshot_data, push @data, { %$snapshot_data,
@ -3219,13 +3185,9 @@ MAIN:
# #
# print all targets and their corresponding source snapshots # print all targets and their corresponding source snapshots
# #
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED}); foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
my $sroot = $config_vol->{sroot} || die; my $snapshot_name = config_key($svol, "snapshot_name") // die;
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) {
next if($config_subvol->{ABORTED});
my $svol = $config_subvol->{svol} || die;
my $snapshot_name = config_key($config_subvol, "snapshot_name") // die;
my @snapshot_children = get_snapshot_children($sroot, $svol); my @snapshot_children = get_snapshot_children($sroot, $svol);
my $stats_snapshot_uptodate = ""; my $stats_snapshot_uptodate = "";
foreach my $snapshot (@snapshot_children) { foreach my $snapshot (@snapshot_children) {
@ -3237,9 +3199,7 @@ MAIN:
push @stats_data, [ $svol->{PRINT}, sprintf("%4u snapshots$stats_snapshot_uptodate", scalar(@snapshot_children)) ]; push @stats_data, [ $svol->{PRINT}, sprintf("%4u snapshots$stats_snapshot_uptodate", scalar(@snapshot_children)) ];
$stats_snapshots_total += scalar(@snapshot_children); # NOTE: this adds ALL snaphot children under $sroot (not only the ones created by btrbk!) $stats_snapshots_total += scalar(@snapshot_children); # NOTE: this adds ALL snaphot children under $sroot (not only the ones created by btrbk!)
foreach my $config_target (@{$config_subvol->{TARGET}}) { foreach my $droot (vinfo_subsection($svol, 'target')) {
next if($config_target->{ABORTED});
my $droot = $config_target->{droot} || die;
$droot_compat{$droot->{URL}} = 1 if($droot->{BTRFS_PROGS_COMPAT}); $droot_compat{$droot->{URL}} = 1 if($droot->{BTRFS_PROGS_COMPAT});
my $stats_received = 0; my $stats_received = 0;
my $stats_orphaned = 0; my $stats_orphaned = 0;
@ -3278,7 +3238,7 @@ MAIN:
} }
else { else {
# don't display all subvolumes in $droot, only the ones matching snapshot_name # don't display all subvolumes in $droot, only the ones matching snapshot_name
if(parse_filename($target_vol->{SUBVOL_PATH}, $snapshot_name, ($config_target->{target_type} eq "raw"))) { if(parse_filename($target_vol->{SUBVOL_PATH}, $snapshot_name, ($droot->{CONFIG}->{target_type} eq "raw"))) {
if($incomplete_backup) { $stats_incomplete++; } else { $stats_orphaned++; } if($incomplete_backup) { $stats_incomplete++; } else { $stats_orphaned++; }
push @data, { type => "received", push @data, { type => "received",
status => ($incomplete_backup ? "incomplete" : "orphaned"), status => ($incomplete_backup ? "incomplete" : "orphaned"),
@ -3310,16 +3270,10 @@ MAIN:
# #
# print latest common # print latest common
# #
foreach my $config_vol (@{$config->{VOLUME}}) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
next if($config_vol->{ABORTED}); foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
my $sroot = $config_vol->{sroot} || die;
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) {
next if($config_subvol->{ABORTED});
my $svol = $config_subvol->{svol} || die;
my $found = 0; my $found = 0;
foreach my $config_target (@{$config_subvol->{TARGET}}) { foreach my $droot (vinfo_subsection($svol, 'target')) {
next if($config_target->{ABORTED});
my $droot = $config_target->{droot} || die;
my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot); my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot);
if ($latest_common_src && $latest_common_target) { if ($latest_common_src && $latest_common_target) {
push @data, { type => "latest_common", push @data, { type => "latest_common",
@ -3351,9 +3305,9 @@ MAIN:
WARN " - target: $_" foreach(sort keys %droot_compat); WARN " - target: $_" foreach(sort keys %droot_compat);
} }
if($action_resolve eq "stats") { if($action_resolve eq "stats") {
print_header(title => "Statistics", print_header(title => "Statistics",
config => $config, config => $config,
time => $start_time, time => $start_time,
); );
print_table(\@stats_data, " "); print_table(\@stats_data, " ");
@ -3383,19 +3337,10 @@ MAIN:
# identify and delete incomplete backups # identify and delete incomplete backups
# #
my @out; my @out;
foreach my $config_vol (@{$config->{VOLUME}}) foreach my $sroot (vinfo_subsection($config, 'volume')) {
{ foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
next if($config_vol->{ABORTED}); my $snapshot_name = config_key($svol, "snapshot_name") // die;
my $sroot = $config_vol->{sroot} || die; foreach my $droot (vinfo_subsection($svol, 'target')) {
foreach my $config_subvol (@{$config_vol->{SUBVOLUME}})
{
next if($config_subvol->{ABORTED});
my $svol = $config_subvol->{svol} || die;
my $snapshot_name = config_key($config_subvol, "snapshot_name") // die;
foreach my $config_target (@{$config_subvol->{TARGET}})
{
next if($config_target->{ABORTED});
my $droot = $config_target->{droot} || die;
if($droot->{BTRFS_PROGS_COMPAT}) { if($droot->{BTRFS_PROGS_COMPAT}) {
WARN "btrfs_progs_compat is set, skipping cleanup of target: $droot->{PRINT}"; WARN "btrfs_progs_compat is set, skipping cleanup of target: $droot->{PRINT}";
next; next;
@ -3413,15 +3358,15 @@ MAIN:
push @out, "--- $target_vol->{PRINT}"; push @out, "--- $target_vol->{PRINT}";
} }
} }
my $ret = btrfs_subvolume_delete(\@delete, commit => config_key($config_target, "btrfs_commit_delete"), type => "delete_garbled"); my $ret = btrfs_subvolume_delete(\@delete, commit => config_key($droot, "btrfs_commit_delete"), type => "delete_garbled");
if(defined($ret)) { if(defined($ret)) {
INFO "Deleted $ret incomplete backups in: $droot->{PRINT}/$snapshot_name.*"; INFO "Deleted $ret incomplete backups in: $droot->{PRINT}/$snapshot_name.*";
$droot->{SUBVOL_DELETED} //= []; $droot->{SUBVOL_DELETED} //= [];
push @{$droot->{SUBVOL_DELETED}}, @delete; push @{$droot->{SUBVOL_DELETED}}, @delete;
} }
else { else {
ABORTED($config_target, "Failed to delete incomplete target subvolume"); ABORTED($droot, "Failed to delete incomplete target subvolume");
push @out, "!!! Target \"$droot->{PRINT}\" aborted: $config_target->{ABORTED}"; push @out, "!!! Target \"$droot->{PRINT}\" aborted: $abrt";
} }
push(@out, "<no_action>") unless(scalar(@delete)); push(@out, "<no_action>") unless(scalar(@delete));
push(@out, ""); push(@out, "");
@ -3447,9 +3392,9 @@ MAIN:
$output_format ||= "custom"; $output_format ||= "custom";
if($output_format eq "custom") if($output_format eq "custom")
{ {
print_header(title => "Cleanup Summary", print_header(title => "Cleanup Summary",
config => $config, config => $config,
time => $start_time, time => $start_time,
legend => [ legend => [
"--- deleted subvolume (incomplete backup)", "--- deleted subvolume (incomplete backup)",
], ],