btrbk: add commands "resolve snapshots|targets|latest" (replacing "tree")

pull/57/head
Axel Burri 2015-10-14 17:02:25 +02:00
parent f01784e2d0
commit 4a1b6545f4
1 changed files with 187 additions and 72 deletions

151
btrbk
View File

@ -137,9 +137,9 @@ my %table_formats = (
raw => [ qw( source_url source_host source_path snapshot_path snapshot_name snapshot_preserve target_url target_host target_path target_preserve source_rsh target_rsh ) ],
},
tree => { table => [ qw( source snapshot btrbk_flags received ) ],
long => [ qw( source_host source_path snapshot_path snapshot_name btrbk_flags received_host received_path ) ],
raw => [ qw( source_host source_path snapshot_path snapshot_name btrbk_flags received_host received_path source_rsh ) ],
resolved => { table => [ qw( source snapshot status target ) ],
long => [ qw( type source_host source_subvol snapshot_subvol status target_host target_subvol ) ],
raw => [ qw( type source_host source_path snapshot_path snapshot_name status target_host target_path source_rsh ) ],
},
schedule => { table => [ qw( action target scheme reason ) ],
@ -204,7 +204,9 @@ sub HELP_MESSAGE
print STDERR " run [filter...] perform backup operations as defined in the config file\n";
print STDERR " dryrun [filter...] don't run btrfs commands; show what would be executed\n";
print STDERR " list [filter...] print source/snapshot/target relations\n";
print STDERR " tree [filter...] shows backup tree\n";
print STDERR " resolve snapshots [filter...] shows snapshots and corresponding targets\n";
print STDERR " resolve targets [filter...] shows targets and corresponding snapshots\n";
print STDERR " resolve latest [filter...] shows latest snapshots/targets\n";
print STDERR " info [filter...] print useful filesystem information\n";
print STDERR " origin <subvol> print origin information for subvolume\n";
print STDERR " diff <from> <to> shows new files since subvolume <from> for subvolume <to>\n";
@ -467,11 +469,13 @@ sub vinfo_set_detail($$)
sub vinfo_prefixed_keys($$)
{
my $prefix = shift || die;
my $vinfo = shift || die;
my $vinfo = shift;
return () unless($vinfo);
my %ret;
foreach (qw( URL PATH HOST NAME SUBVOL_PATH )) {
$ret{$prefix . '_' . lc($_)} = $vinfo->{$_};
}
$ret{$prefix . "_subvol"} = $vinfo->{PATH};
$ret{$prefix} = $vinfo->{PRINT};
$ret{$prefix . "_rsh"} = ($vinfo->{RSH} ? join(" ", @{$vinfo->{RSH}}) : undef),
return %ret;
@ -1862,7 +1866,7 @@ sub print_formatted(@)
{
# output: key0="value0" key1="value1" ...
foreach my $row (@$data) {
print "list_type=\"$format_key\" ";
print "format=\"$format_key\" ";
print join(' ', map { "$_=\"" . ($row->{$_} // "") . "\""; } @$keys) . "\n";
}
}
@ -1993,7 +1997,7 @@ MAIN:
WARN 'found option "--progress", but "pv" is not present: (please install "pv")';
$show_progress = 0;
}
my ($action_run, $action_info, $action_tree, $action_diff, $action_origin, $action_config_print, $action_list);
my ($action_run, $action_info, $action_resolve, $action_diff, $action_origin, $action_config_print, $action_list);
my @filter_args;
my $args_allow_group = 0;
my ($args_expected_min, $args_expected_max) = (0, 0);
@ -2012,13 +2016,35 @@ MAIN:
$args_allow_group = 1;
@filter_args = @ARGV;
}
elsif ($command eq "tree") {
$action_tree = 1;
elsif ($command eq "resolve") {
my $subcommand = shift @ARGV // "";
if($subcommand eq "snapshots") {
$action_resolve = $subcommand;
$args_expected_min = 0;
$args_expected_max = 9999;
$args_allow_group = 1;
@filter_args = @ARGV;
}
elsif($subcommand eq "latest") {
$action_resolve = $subcommand;
$args_expected_min = 0;
$args_expected_max = 9999;
$args_allow_group = 1;
@filter_args = @ARGV;
}
elsif($subcommand eq "targets") {
$action_resolve = $subcommand;
$args_expected_min = 0;
$args_expected_max = 9999;
$args_allow_group = 1;
@filter_args = @ARGV;
}
else {
ERROR "Unknown subcommand for \"resolve\" command: $subcommand";
HELP_MESSAGE(0);
exit 2;
}
}
elsif ($command eq "diff") {
$action_diff = 1;
$args_expected_min = $args_expected_max = 2;
@ -2224,7 +2250,7 @@ MAIN:
#
# filter subvolumes matching command line arguments
#
if(($action_run || $action_tree || $action_info || $action_list || $action_config_print) && scalar(@filter_args))
if(($action_run || $action_resolve || $action_info || $action_list || $action_config_print) && scalar(@filter_args))
{
my %match;
foreach my $config_vol (@{$config->{VOLUME}}) {
@ -2669,12 +2695,12 @@ MAIN:
}
if($action_tree)
if($action_resolve)
{
if($action_resolve eq "snapshots") {
#
# print snapshot tree
# print all snapshots and their receive targets
#
# TODO: reverse tree: print all backups from $droot and their corresponding source snapshots
my @tree_out;
my @raw_out;
foreach my $config_vol (@{$config->{VOLUME}})
@ -2687,18 +2713,18 @@ MAIN:
{
next if($config_subvol->{ABORTED});
my $svol = $config_subvol->{svol} || die;
my $snapshot_name = config_key($config_subvol, "snapshot_name") // die;
push @tree_out, "|-- $svol->{PRINT}";
foreach my $snapshot (sort { $a->{cgen} cmp $b->{cgen} } get_snapshot_children($sroot, $svol))
{
my $raw_data = { type => "snapshot",
btrbk_flags => [ ],
vinfo_prefixed_keys("source", $svol),
vinfo_prefixed_keys("snapshot", $snapshot),
snapshot_name => config_key($config_subvol, "snapshot_name"),
snapshot_name => $snapshot_name,
};
if($snapshot->{cgen} == $svol->{gen}) {
push @tree_out, "| ^== $snapshot->{PATH}";
push @{$raw_data->{btrbk_flags}}, "up-to-date";
$raw_data->{status} = "up-to-date";
} else {
push @tree_out, "| ^-- $snapshot->{PATH}";
}
@ -2712,7 +2738,7 @@ MAIN:
push @tree_out, "| | >>> $_->{PRINT}";
push @raw_out, { %$raw_data,
type => "received",
vinfo_prefixed_keys("received", $_),
vinfo_prefixed_keys("target", $_),
};
}
}
@ -2739,7 +2765,96 @@ MAIN:
print join("\n", @tree_out);
}
else {
print_formatted("tree", \@raw_out);
print_formatted("resolved", \@raw_out);
}
}
elsif($action_resolve eq "targets")
{
#
# print all targets and their corresponding source snapshots
#
my @raw_targets;
foreach my $config_vol (@{$config->{VOLUME}}) {
next if($config_vol->{ABORTED});
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 $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;
foreach my $target_vol (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } values %{vinfo_subvol_list($droot)}) {
my $filename_info = parse_filename($target_vol->{SUBVOL_PATH}, $snapshot_name, ($config_target->{target_type} eq "raw"));
next unless($filename_info); # ignore non-btrbk files
my $parent_snapshot;
foreach (get_snapshot_children($sroot, $svol)) {
if($_->{uuid} eq $target_vol->{received_uuid}) {
$parent_snapshot = $_;
last;
}
}
if($parent_snapshot) {
push @raw_targets, { type => "received",
vinfo_prefixed_keys("target", $target_vol),
vinfo_prefixed_keys("snapshot", $parent_snapshot),
vinfo_prefixed_keys("source", $svol),
status => ($parent_snapshot->{cgen} == $svol->{gen}) ? "up-to-date" : undef,
};
}
else {
push @raw_targets, { type => "received",
vinfo_prefixed_keys("target", $target_vol),
vinfo_prefixed_keys("source", $svol),
};
}
}
}
}
}
print_formatted("resolved", \@raw_targets);
}
elsif($action_resolve eq "latest")
{
#
# print latest common
#
my @raw_latest;
foreach my $config_vol (@{$config->{VOLUME}}) {
next if($config_vol->{ABORTED});
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;
foreach my $config_target (@{$config_subvol->{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);
if ($latest_common_src && $latest_common_target) {
push @raw_latest, { type => "latest_common",
status => ($latest_common_src->{cgen} == $svol->{gen}) ? "up-to-date" : undef,
vinfo_prefixed_keys("source", $svol),
vinfo_prefixed_keys("snapshot", $latest_common_src),
vinfo_prefixed_keys("target", $latest_common_target),
};
$found = 1;
}
}
unless($found) {
my $latest_snapshot = get_latest_snapshot_child($sroot, $svol);
push @raw_latest, { type => "latest_snapshot",
status => ($latest_snapshot->{cgen} == $svol->{gen}) ? "up-to-date" : undef,
vinfo_prefixed_keys("source", $svol),
vinfo_prefixed_keys("snapshot", $latest_snapshot), # all unset if no $latest_snapshot
};
}
}
}
print_formatted("resolved", \@raw_latest);
}
else {
die;
}
exit exit_status($config);
}