btrbk: use global stderr (replace err); refeactor filter_stderr, add fatal_stderr

if fatal_stderr is set and returns true, the command exit code is set
to -1, resulting in run_cmd() returning undef.
pull/299/head
Axel Burri 2019-08-19 13:04:44 +02:00
parent 360c8918bb
commit 485bc3ab0c
1 changed files with 132 additions and 134 deletions

266
btrbk
View File

@ -285,7 +285,6 @@ my $quiet;
my @exclude_vf; my @exclude_vf;
my $do_dumper; my $do_dumper;
my $show_progress = 0; my $show_progress = 0;
my $err = "";
my $output_format; my $output_format;
my $lockfile; my $lockfile;
my $tlog_fh; my $tlog_fh;
@ -294,6 +293,7 @@ my $current_transaction;
my @transaction_log; my @transaction_log;
my %config_override; my %config_override;
my @tm_now; # current localtime ( sec, min, hour, mday, mon, year, wday, yday, isdst ) my @tm_now; # current localtime ( sec, min, hour, mday, mon, year, wday, yday, isdst )
my @stderr; # stderr of last run_cmd
my %warn_once; my %warn_once;
my %kdf_vars; my %kdf_vars;
my $kdf_session_key; my $kdf_session_key;
@ -687,16 +687,15 @@ sub run_cmd(@)
my @cmd_pipe_in = (ref($_[0]) eq "HASH") ? @_ : { @_ }; my @cmd_pipe_in = (ref($_[0]) eq "HASH") ? @_ : { @_ };
die unless(scalar(@cmd_pipe_in)); die unless(scalar(@cmd_pipe_in));
$err = ""; @stderr = ();
my $destructive = 0; my $destructive = 0;
my $catch_stderr = 0;
my $exitcode_loglevel = "debug";
my $filter_stderr = undef;
my @cmd_pipe; my @cmd_pipe;
my @unsafe_cmd; my @unsafe_cmd;
my $compressed = undef; my $compressed = undef;
my $stream_options = $cmd_pipe_in[0]->{stream_options} // {}; my $stream_options = $cmd_pipe_in[0]->{stream_options} // {};
my @filter_stderr;
my $fatal_stderr;
my $has_rsh; my $has_rsh;
$cmd_pipe_in[0]->{stream_source} = 1; $cmd_pipe_in[0]->{stream_source} = 1;
@ -706,10 +705,9 @@ sub run_cmd(@)
{ {
die if(defined($href->{cmd_text})); die if(defined($href->{cmd_text}));
$catch_stderr = 1 if($href->{catch_stderr}); push @filter_stderr, ((ref($href->{filter_stderr}) eq "ARRAY") ? @{$href->{filter_stderr}} : $href->{filter_stderr}) if($href->{filter_stderr});
$filter_stderr = $href->{filter_stderr} if($href->{filter_stderr}); # NOTE: last filter wins! $fatal_stderr = $href->{fatal_stderr} if($href->{fatal_stderr});
$destructive = 1 unless($href->{non_destructive}); $destructive = 1 unless($href->{non_destructive});
$exitcode_loglevel = $href->{exitcode_loglevel} if($href->{exitcode_loglevel});
$has_rsh = 1 if($href->{rsh}); $has_rsh = 1 if($href->{rsh});
if($href->{check_unsafe}) { if($href->{check_unsafe}) {
@ -819,15 +817,15 @@ sub run_cmd(@)
# execute command # execute command
my ($pid, $out_fh, $err_fh, @stdout, @stderr); my ($pid, $out_fh, $err_fh, @stdout);
$err_fh = gensym; $err_fh = gensym;
if(eval_quiet { $pid = open3(undef, $out_fh, $err_fh, $cmd); }) { if(eval_quiet { $pid = open3(undef, $out_fh, $err_fh, $cmd); }) {
chomp(@stdout = readline($out_fh)); chomp(@stdout = readline($out_fh));
chomp(@stderr = readline($err_fh)); chomp(@stderr = readline($err_fh));
waitpid($pid, 0); waitpid($pid, 0);
if($loglevel >= 4) { if($loglevel >= 4) {
TRACE "[stdout] $_" foreach(@stdout); TRACE map("[stdout] $_", @stdout);
TRACE "[stderr] $_" foreach(@stderr); TRACE map("[stderr] $_", @stderr);
} }
} }
else { else {
@ -847,26 +845,22 @@ sub run_cmd(@)
} }
my $exitcode = $? >> 8; my $exitcode = $? >> 8;
my @filtered_err; # call hooks: fatal_stderr, filter_stderr
if($filter_stderr) { if(($exitcode == 0) && $fatal_stderr) {
@filtered_err = map { &{$filter_stderr} ($exitcode) // () } @stderr; $exitcode = -1 if(grep &{$fatal_stderr}(), @stderr);
} }
elsif($has_rsh && ($exitcode == 255)) { foreach my $filter_fn (@filter_stderr) {
# SSH returns exit status 255 if an error occurred. @stderr = map { &{$filter_fn} ($exitcode); $_ // () } @stderr;
@filtered_err = map { { error => $_ } } @stderr;
$exitcode_loglevel = "error";
} }
if($exitcode || (grep { $_->{fatal} } @filtered_err)) { unshift @stderr, "sh: $cmd";
my $log_text = "Command execution failed (exitcode=$exitcode): `$cmd`";
$exitcode_loglevel = "error" if(scalar(@filtered_err)); if($exitcode) {
if($exitcode_loglevel eq "error") { ERROR $log_text; } my @log_text = ("Command execution failed (exitcode=$exitcode): `$cmd`", map("... $_", @stderr));
elsif($exitcode_loglevel eq "warn") { WARN $log_text; } if($has_rsh && ($exitcode == 255)) { # SSH returns exit status 255 if an error occurred.
else { DEBUG $log_text; } ERROR @log_text;
foreach(@filtered_err) { } else {
ERROR "... $_->{error}" if(defined($_->{error})); DEBUG @log_text;
WARN "... $_->{warn}" if(defined($_->{warn}));
DEBUG "... $_->{debug}" if(defined($_->{debug}));
} }
return undef; return undef;
} }
@ -877,13 +871,25 @@ sub run_cmd(@)
} }
sub _btrfs_filter_stderr
{
if(/^usage: / || /(unrecognized|invalid) option/) {
WARN_ONCE "Using unsupported btrfs-progs < v$BTRFS_PROGS_MIN";
}
# strip error prefix (we print our own)
# note that this also affects ssh_filter_btrbk.sh error strings
s/^ERROR: //;
}
sub btrfs_filesystem_show($) sub btrfs_filesystem_show($)
{ {
my $vol = shift || die; my $vol = shift || die;
my $path = $vol->{PATH} // die; my $path = $vol->{PATH} // die;
return run_cmd( cmd => vinfo_cmd($vol, "btrfs filesystem show", { unsafe => $path } ), return run_cmd( cmd => vinfo_cmd($vol, "btrfs filesystem show", { unsafe => $path } ),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1 non_destructive => 1,
filter_stderr => \&_btrfs_filter_stderr,
); );
} }
@ -894,7 +900,8 @@ sub btrfs_filesystem_df($)
my $path = $vol->{PATH} // die; my $path = $vol->{PATH} // die;
return run_cmd( cmd => vinfo_cmd($vol, "btrfs filesystem df", { unsafe => $path }), return run_cmd( cmd => vinfo_cmd($vol, "btrfs filesystem df", { unsafe => $path }),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1 non_destructive => 1,
filter_stderr => \&_btrfs_filter_stderr,
); );
} }
@ -905,8 +912,14 @@ sub btrfs_filesystem_usage($)
my $path = $vol->{PATH} // die; my $path = $vol->{PATH} // die;
my $ret = run_cmd( cmd => vinfo_cmd($vol, "btrfs filesystem usage", { unsafe => $path } ), my $ret = run_cmd( cmd => vinfo_cmd($vol, "btrfs filesystem usage", { unsafe => $path } ),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1 non_destructive => 1,
filter_stderr => \&_btrfs_filter_stderr,
); );
unless(defined($ret)) {
ERROR "Failed to fetch btrfs filesystem usage for: $vol->{PRINT}", map("... $_", @stderr);
return undef;
}
return undef unless(defined($ret)); return undef unless(defined($ret));
my %detail; my %detail;
@ -978,21 +991,7 @@ sub btrfs_subvolume_show($;@)
my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume show", @cmd_options, { unsafe => $path }), my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume show", @cmd_options, { unsafe => $path }),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1, non_destructive => 1,
catch_stderr => 1, # hack for shell-based run_cmd() filter_stderr => \&_btrfs_filter_stderr,
filter_stderr => sub {
return undef unless($_[0]); # do nothing if exitcode=0
if(s/^ERROR: //) { # catch errors from btrfs-progs as well as ssh_filter_btrbk.sh
$err = $_;
return { error => $_, fatal => 1 } if(/^ssh_filter_btrbk.sh/); # "fatal" causes run_cmd to return undef
} elsif(/(unrecognized|invalid) option/) {
WARN_ONCE "$_ (maybe using unsupported btrfs-progs < v$BTRFS_PROGS_MIN " . ($vol->{HOST} ? "on host=$vol->{HOST} " : "") . "?)";
} else {
DEBUG "Unparsed error: $_";
$err ||= $_;
}
# soft fail: $err can be displayed as a user-friendly WARNING
return undef;
},
); );
return undef unless(defined($ret)); return undef unless(defined($ret));
@ -1103,6 +1102,7 @@ sub btrfs_subvolume_list_readonly_flag($)
my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume list", '-a', '-r', { unsafe => $path } ), my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume list", '-a', '-r', { unsafe => $path } ),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1, non_destructive => 1,
filter_stderr => \&_btrfs_filter_stderr,
); );
return undef unless(defined($ret)); return undef unless(defined($ret));
@ -1135,6 +1135,7 @@ sub btrfs_subvolume_list($;@)
my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume list", @filter_options, @display_options, { unsafe => $path } ), my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume list", @filter_options, @display_options, { unsafe => $path } ),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1, non_destructive => 1,
filter_stderr => \&_btrfs_filter_stderr,
); );
return undef unless(defined($ret)); return undef unless(defined($ret));
@ -1219,9 +1220,10 @@ sub btrfs_subvolume_find_new($$;$)
my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume find-new", { unsafe => $path }, $lastgen ), my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs subvolume find-new", { unsafe => $path }, $lastgen ),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1, non_destructive => 1,
filter_stderr => \&_btrfs_filter_stderr,
); );
unless(defined($ret)) { unless(defined($ret)) {
ERROR "Failed to fetch modified files for: $vol->{PRINT}"; ERROR "Failed to fetch modified files for: $vol->{PRINT}", map("... $_", @stderr);
return undef; return undef;
} }
@ -1285,10 +1287,11 @@ sub btrfs_subvolume_snapshot($$)
); );
my $ret = run_cmd(cmd => vinfo_cmd($svol, "btrfs subvolume snapshot", '-r', { unsafe => $src_path }, { unsafe => $target_path } ), my $ret = run_cmd(cmd => vinfo_cmd($svol, "btrfs subvolume snapshot", '-r', { unsafe => $src_path }, { unsafe => $target_path } ),
rsh => vinfo_rsh($svol), rsh => vinfo_rsh($svol),
filter_stderr => \&_btrfs_filter_stderr,
); );
end_transaction("snapshot", defined($ret)); end_transaction("snapshot", defined($ret));
unless(defined($ret)) { unless(defined($ret)) {
ERROR "Failed to create btrfs subvolume snapshot: $svol->{PRINT} -> $target_path"; ERROR "Failed to create snapshot: $svol->{PRINT} -> $target_path", map("... $_", @stderr);
return undef; return undef;
} }
return $target_vol; return $target_vol;
@ -1338,45 +1341,35 @@ sub btrfs_subvolume_delete($@)
} }
$ret = run_cmd(cmd => ['rm', '-f', @cmd_target_paths ], $ret = run_cmd(cmd => ['rm', '-f', @cmd_target_paths ],
rsh => vinfo_rsh($targets->[0]), rsh => vinfo_rsh($targets->[0]),
catch_stderr => 1, # hack for shell-based run_cmd()
filter_stderr => sub {
# catch errors from "rm -f"
if(/^rm: cannot remove '($file_match)':/) {
my $catch = $1; # make sure $catch matches $vol->{PATH}
$catch =~ s/\.info$//;
$catch =~ s/\.split_[a-z][a-z]$//;
$err_catch{$catch} //= [];
push(@{$err_catch{$catch}}, $_);
return undef; # no error messages in run_cmd
}
else {
# show errors in run_cmd; force "Command execution failed" error message
return { error => $_, fatal => 1 };
}
},
); );
unless(defined($ret)) {
foreach(@stderr) {
next unless(/^rm: cannot remove '($file_match)':/);
my $catch = $1; # make sure $catch matches $vol->{PATH}
$catch =~ s/\.info$//;
$catch =~ s/\.split_[a-z][a-z]$//;
$err_catch{$catch} //= [];
push(@{$err_catch{$catch}}, $_);
}
}
} }
else { else {
my @cmd_target_paths = map { { unsafe => $_->{PATH} } } @$targets; my @cmd_target_paths = map { { unsafe => $_->{PATH} } } @$targets;
my $unparsed_errors;
my @options; my @options;
@options = ("--commit-$commit") if($commit); @options = ("--commit-$commit") if($commit);
$ret = run_cmd(cmd => vinfo_cmd($targets->[0], "btrfs subvolume delete", @options, @cmd_target_paths ), $ret = run_cmd(cmd => vinfo_cmd($targets->[0], "btrfs subvolume delete", @options, @cmd_target_paths ),
rsh => vinfo_rsh($targets->[0]), rsh => vinfo_rsh($targets->[0]),
catch_stderr => 1, # hack for shell-based run_cmd() fatal_stderr => sub { m/^ERROR: /; }, # probably not needed, "btrfs sub delete" returns correct exit status
filter_stderr => sub { filter_stderr => \&_btrfs_filter_stderr,
return undef unless(s/^ERROR: //); # strip ERROR prefix
if(/'($file_match)'/ || /: ($file_match)$/ || /($file_match):/) {
# NOTE: as of btrfs-progs-4.16, this does not catch anything
$err_catch{$1} //= [];
push(@{$err_catch{$1}}, $_);
} else {
$unparsed_errors = 1;
}
return undef; # no error messages in run_cmd
},
); );
$ret = undef if($unparsed_errors); unless(defined($ret)) {
foreach(@stderr) {
next unless(/'($file_match)'/ || /: ($file_match)$/ || /($file_match):/);
# NOTE: as of btrfs-progs-4.16, this does not catch anything
$err_catch{$1} //= [];
push(@{$err_catch{$1}}, $_);
}
}
} }
if(defined($ret)) { if(defined($ret)) {
@ -1388,7 +1381,7 @@ sub btrfs_subvolume_delete($@)
foreach my $check_target (@$targets) { foreach my $check_target (@$targets) {
my $err_ary = $err_catch{$check_target->{PATH}}; my $err_ary = $err_catch{$check_target->{PATH}};
if($err_ary) { if($err_ary) {
ERROR "Failed to delete subvolume \"$check_target->{PRINT}\": $_" foreach(@$err_ary); ERROR map("Failed to delete subvolume \"$check_target->{PRINT}\": $_", @$err_ary);
$catch_count++; $catch_count++;
} }
else { else {
@ -1398,9 +1391,9 @@ sub btrfs_subvolume_delete($@)
@deleted = () if($catch_count != (scalar keys %err_catch)); @deleted = () if($catch_count != (scalar keys %err_catch));
} }
unless(scalar(@deleted)) { unless(scalar(@deleted)) {
ERROR "Failed to match error messages from delete command, assuming nothing deleted:"; ERROR "Failed to match error messages from delete command, assuming nothing deleted", map("... $_", @stderr);
ERROR "... possibly not deleted subvolume: $_" foreach(map( { $_->{PRINT} } @$targets)); ERROR map("Possibly not deleted subvolume: $_->{PRINT}", @$targets);
ERROR "... consider running 'btrbk prune -n'"; ERROR "Consider running 'btrbk prune -n'";
} }
} }
@ -1426,10 +1419,11 @@ sub btrfs_qgroup_destroy($@)
vinfo_prefixed_keys("target", $vol)); vinfo_prefixed_keys("target", $vol));
my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs qgroup destroy", $qgroup_id, { unsafe => $path }), my $ret = run_cmd(cmd => vinfo_cmd($vol, "btrfs qgroup destroy", $qgroup_id, { unsafe => $path }),
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
filter_stderr => \&_btrfs_filter_stderr,
); );
end_transaction($opts{type} // "qgroup_destroy", defined($ret)); end_transaction($opts{type} // "qgroup_destroy", defined($ret));
unless(defined($ret)) { unless(defined($ret)) {
ERROR "Failed to destroy qgroup \"$qgroup_id\" for subvolume: $vol->{PRINT}"; ERROR "Failed to destroy qgroup \"$qgroup_id\" for subvolume: $vol->{PRINT}", map("... $_", @stderr);
return undef; return undef;
} }
return $vol; return $vol;
@ -1472,21 +1466,14 @@ sub btrfs_send_receive($$;$$$)
rsh => vinfo_rsh($snapshot, disable_compression => $stream_options->{stream_compress}), rsh => vinfo_rsh($snapshot, disable_compression => $stream_options->{stream_compress}),
name => "btrfs send", name => "btrfs send",
stream_options => $stream_options, stream_options => $stream_options,
exitcode_loglevel => "error", # print error message if exitcode != 0 filter_stderr => [ \&_btrfs_filter_stderr, sub { $_ = undef if(/^At subvol/) } ],
catch_stderr => 1, # hack for shell-based run_cmd()
}; };
push @cmd_pipe, { push @cmd_pipe, {
cmd => vinfo_cmd($target, "btrfs receive", @receive_options, { unsafe => $target_path . '/' } ), cmd => vinfo_cmd($target, "btrfs receive", @receive_options, { unsafe => $target_path . '/' } ),
rsh => vinfo_rsh($target, disable_compression => $stream_options->{stream_compress}), rsh => vinfo_rsh($target, disable_compression => $stream_options->{stream_compress}),
name => "btrfs receive", name => "btrfs receive",
catch_stderr => 1, # hack for shell-based run_cmd() fatal_stderr => sub { m/^ERROR: /; }, # NOTE: btrfs-progs < 4.11: if "btrfs send" fails, "btrfs receive" returns 0!
filter_stderr => sub {
# NOTE: btrfs-progs < 4.11: if "btrfs send" fails, "btrfs receive" returns 0!
return { error => $_, fatal => 1 } if(s/^ERROR: //); # "fatal" causes run_cmd to return undef
return { warn => $_ } if(s/^WARNING: //);
return undef;
},
}; };
my $send_receive_error = 0; my $send_receive_error = 0;
@ -1496,7 +1483,9 @@ sub btrfs_send_receive($$;$$$)
vinfo_prefixed_keys("parent", $parent), vinfo_prefixed_keys("parent", $parent),
); );
my $ret = run_cmd(@cmd_pipe); my $ret = run_cmd(@cmd_pipe);
my @cmd_err;
unless(defined($ret)) { unless(defined($ret)) {
@cmd_err = @stderr; # save for later
$send_receive_error = 1; $send_receive_error = 1;
} }
@ -1522,21 +1511,21 @@ sub btrfs_send_receive($$;$$$)
unless($send_receive_error) { unless($send_receive_error) {
# plausibility checks on target detail # plausibility checks on target detail
unless($detail->{readonly}) { unless($detail->{readonly}) {
ERROR "[send/receive] target is not readonly: $vol_received->{PRINT}"; push @cmd_err, "target is not readonly: $vol_received->{PRINT}";
$send_receive_error = 1; $send_receive_error = 1;
} }
if($detail->{received_uuid} && ($detail->{received_uuid} eq '-')) { if($detail->{received_uuid} && ($detail->{received_uuid} eq '-')) {
# NOTE: received_uuid is not in @required_keys (needs btrfs-progs >= 4.1 (BTRFS_PROGS_MIN)) # NOTE: received_uuid is not in @required_keys (needs btrfs-progs >= 4.1 (BTRFS_PROGS_MIN))
# so we only check it if it's really present # so we only check it if it's really present
ERROR "[send/receive] received_uuid is not set on target: $vol_received->{PRINT}"; push @cmd_err, "received_uuid is not set on target: $vol_received->{PRINT}";
$send_receive_error = 1; $send_receive_error = 1;
} }
if($parent && ($detail->{parent_uuid} eq '-')) { if($parent && ($detail->{parent_uuid} eq '-')) {
ERROR "[send/receive] parent_uuid is not set on target: $vol_received->{PRINT}"; push @cmd_err, "parent_uuid is not set on target: $vol_received->{PRINT}";
$send_receive_error = 1; $send_receive_error = 1;
} }
if((not $parent) && ($detail->{parent_uuid} ne '-')) { if((not $parent) && ($detail->{parent_uuid} ne '-')) {
ERROR "[send/receive] parent_uuid is set on target: $vol_received->{PRINT}"; push @cmd_err, "parent_uuid is set on target: $vol_received->{PRINT}";
$send_receive_error = 1; $send_receive_error = 1;
} }
} }
@ -1545,7 +1534,7 @@ sub btrfs_send_receive($$;$$$)
$is_garbled = ((not $detail->{readonly}) && defined($detail->{received_uuid}) && ($detail->{received_uuid} eq '-')); $is_garbled = ((not $detail->{readonly}) && defined($detail->{received_uuid}) && ($detail->{received_uuid} eq '-'));
} }
else { else {
$err = "" if($send_receive_error); # ignore $err if send/receive failed push @cmd_err, "failed to check target subvolume: $vol_received->{PRINT}", @stderr;
$send_receive_error = 1; $send_receive_error = 1;
} }
} }
@ -1554,7 +1543,7 @@ sub btrfs_send_receive($$;$$$)
if($send_receive_error) { if($send_receive_error) {
ERROR "Failed to send/receive subvolume: $snapshot->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $vol_received->{PRINT}"; ERROR "Failed to send/receive subvolume: $snapshot->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $vol_received->{PRINT}";
ERROR "... $err" if($err); ERROR map("... $_", @cmd_err);
} }
if($is_garbled) { if($is_garbled) {
@ -1619,13 +1608,8 @@ sub btrfs_send_to_file($$$;$$)
rsh => vinfo_rsh($source, disable_compression => $stream_options->{stream_compress}), rsh => vinfo_rsh($source, disable_compression => $stream_options->{stream_compress}),
name => "btrfs send", name => "btrfs send",
stream_options => $stream_options, stream_options => $stream_options,
exitcode_loglevel => "error", # print error message if exitcode != 0 filter_stderr => [ \&_btrfs_filter_stderr, sub { $_ = undef if(/^At subvol/) } ],
catch_stderr => 1, # hack for shell-based run_cmd() fatal_stderr => sub { m/^ERROR: /; },
filter_stderr => sub {
return { error => $_, fatal => 1 } if(s/^ERROR: //); # "fatal" causes run_cmd to return undef
return { warn => $_ } if(s/^WARNING: //);
return undef;
},
}; };
if($compress) { if($compress) {
@ -1702,12 +1686,18 @@ sub btrfs_send_to_file($$$;$$)
DEBUG "Generating session key for: $vol_received->{PRINT}"; DEBUG "Generating session key for: $vol_received->{PRINT}";
my $kdf_backend_name = $encrypt->{kdf_backend}; my $kdf_backend_name = $encrypt->{kdf_backend};
$kdf_backend_name =~ s/^.*\///; $kdf_backend_name =~ s/^.*\///;
my $key_target_text = $encrypt->{kdf_keygen_each} ? "\"$vol_received->{PRINT}\"" : "all raw backups";
print STDOUT "\nGenerate session key for " . ($encrypt->{kdf_keygen_each} ? "\"$vol_received->{PRINT}\"" : "all raw backups") . ":\n"; print STDOUT "\nGenerate session key for $key_target_text:\n";
my $kdf_values = run_cmd(cmd => [ $encrypt->{kdf_backend}, $encrypt->{kdf_keysize} ], my $kdf_values = run_cmd(cmd => [ $encrypt->{kdf_backend}, $encrypt->{kdf_keysize} ],
non_destructive => 1, non_destructive => 1,
name => $kdf_backend_name name => $kdf_backend_name
); );
unless(defined($kdf_values)) {
ERROR "Failed to generate session key for $key_target_text", map("... $_", @stderr);
return undef;
}
return undef unless(defined($kdf_values)); return undef unless(defined($kdf_values));
foreach(@$kdf_values) { foreach(@$kdf_values) {
chomp; chomp;
@ -1803,8 +1793,13 @@ sub btrfs_send_to_file($$$;$$)
my $ret; my $ret;
$ret = system_write_raw_info($vol_received, \%raw_info); $ret = system_write_raw_info($vol_received, \%raw_info);
my @cmd_err;
if(defined($ret)) { if(defined($ret)) {
$ret = run_cmd(@cmd_pipe); $ret = run_cmd(@cmd_pipe);
@cmd_err = @stderr unless(defined($ret)); # save for later
}
else {
push @cmd_err, "failed to write raw .info file: $vol_received->{PATH}.info", @stderr;
} }
if(defined($ret)) { if(defined($ret)) {
@ -1813,9 +1808,10 @@ sub btrfs_send_to_file($$$;$$)
# redirection as well as "dd" always creates the target file. # redirection as well as "dd" always creates the target file.
# Note that "split" does not create empty files. # Note that "split" does not create empty files.
my $test_postfix = ($split ? ".split_aa" : ""); my $test_postfix = ($split ? ".split_aa" : "");
DEBUG "Testing target data file (non-zero size)"; my $check_file = "${target_path}/${target_filename}${test_postfix}";
DEBUG "Testing target data file (non-zero size): $check_file";
$ret = run_cmd({ $ret = run_cmd({
cmd => ['test', '-s', { unsafe => "${target_path}/${target_filename}${test_postfix}" } ], cmd => ['test', '-s', { unsafe => $check_file } ],
rsh => vinfo_rsh($target), rsh => vinfo_rsh($target),
name => "test", name => "test",
}); });
@ -1824,10 +1820,14 @@ sub btrfs_send_to_file($$$;$$)
delete $raw_info{INCOMPLETE}; delete $raw_info{INCOMPLETE};
$ret = system_write_raw_info($vol_received, \%raw_info); $ret = system_write_raw_info($vol_received, \%raw_info);
} }
else {
push @cmd_err, "failed to check target file (not present or zero length): $check_file";
}
} }
end_transaction("send-to-raw", defined($ret)); end_transaction("send-to-raw", defined($ret));
unless(defined($ret)) { unless(defined($ret)) {
ERROR "Failed to send btrfs subvolume to raw file: $source->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $vol_received->{PRINT}"; ERROR "Failed to send btrfs subvolume to raw file: $source->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $vol_received->{PRINT}";
ERROR map("... $_", @cmd_err);
return undef; return undef;
} }
return 1; return 1;
@ -1841,8 +1841,6 @@ sub system_list_mountinfo($)
my $ret = run_cmd(cmd => [ qw(cat), $file ], my $ret = run_cmd(cmd => [ qw(cat), $file ],
rsh => vinfo_rsh($vol), rsh => vinfo_rsh($vol),
non_destructive => 1, non_destructive => 1,
exitcode_loglevel => "error", # print error message if exitcode != 0
catch_stderr => 1, # hack for shell-based run_cmd()
); );
return undef unless(defined($ret)); return undef unless(defined($ret));
@ -5195,11 +5193,11 @@ MAIN:
# NOTE: ssh://{src,target} uses default config # NOTE: ssh://{src,target} uses default config
my $src_vol = vinfo($src_url, $config); my $src_vol = vinfo($src_url, $config);
unless(vinfo_init_root($src_vol)) { ERROR "Failed to fetch subvolume detail for '$src_vol->{PRINT}'" . ($err ? ": $err" : ""); exit 1; } unless(vinfo_init_root($src_vol)) { ERROR "Failed to fetch subvolume detail for '$src_vol->{PRINT}'", map("... $_", @stderr); exit 1; }
if($src_vol->{node}{is_root}) { ERROR "Subvolume is btrfs root: $src_vol->{PRINT}"; exit 1; } if($src_vol->{node}{is_root}) { ERROR "Subvolume is btrfs root: $src_vol->{PRINT}"; exit 1; }
my $target_vol = vinfo($target_url, $config); my $target_vol = vinfo($target_url, $config);
unless(vinfo_init_root($target_vol)) { ERROR "Failed to fetch subvolume detail for '$target_vol->{PRINT}'" . ($err ? ": $err" : ""); exit 1; } unless(vinfo_init_root($target_vol)) { ERROR "Failed to fetch subvolume detail for '$target_vol->{PRINT}'", map("... $_", @stderr); exit 1; }
if($target_vol->{node}{is_root}) { ERROR "Subvolume is btrfs root: $target_vol->{PRINT}"; exit 1; } if($target_vol->{node}{is_root}) { ERROR "Subvolume is btrfs root: $target_vol->{PRINT}"; exit 1; }
unless(_is_same_fs_tree($src_vol->{node}, $target_vol->{node})) { unless(_is_same_fs_tree($src_vol->{node}, $target_vol->{node})) {
@ -5301,7 +5299,7 @@ MAIN:
$realpath_cache{$mnt->{mount_point}} = $mnt->{mount_point}; # we know those are real paths, prevents calling readlink in btrfs_mountpoint $realpath_cache{$mnt->{mount_point}} = $mnt->{mount_point}; # we know those are real paths, prevents calling readlink in btrfs_mountpoint
my $vol = vinfo($mnt->{mount_point}, $config); my $vol = vinfo($mnt->{mount_point}, $config);
unless(vinfo_init_root($vol)) { unless(vinfo_init_root($vol)) {
ERROR "Failed to fetch subvolume detail for: $vol->{PRINT}" . ($err ? ": $err" : ""); ERROR "Failed to fetch subvolume detail for: $vol->{PRINT}", map("... $_", @stderr);
exit 1; exit 1;
} }
@ -5381,12 +5379,12 @@ MAIN:
my $src_root = vinfo($src_url, $config); my $src_root = vinfo($src_url, $config);
unless(vinfo_init_root($src_root)) { unless(vinfo_init_root($src_root)) {
ERROR "Failed to fetch subvolume detail for '$src_root->{PRINT}'" . ($err ? ": $err" : ""); ERROR "Failed to fetch subvolume detail for '$src_root->{PRINT}'", map("... $_", @stderr);
exit 1; exit 1;
} }
my $archive_root = vinfo($archive_url, $config); my $archive_root = vinfo($archive_url, $config);
unless($archive_raw ? vinfo_init_raw_root($archive_root) : vinfo_init_root($archive_root)) { unless($archive_raw ? vinfo_init_raw_root($archive_root) : vinfo_init_root($archive_root)) {
ERROR "Failed to fetch " . ($archive_raw ? "raw target metadata" : "subvolume detail") . " for '$archive_root->{PRINT}'" . ($err ? ": $err" : ""); ERROR "Failed to fetch " . ($archive_raw ? "raw target metadata" : "subvolume detail") . " for '$archive_root->{PRINT}'", map("... $_", @stderr);
exit 1; exit 1;
} }
@ -5421,18 +5419,18 @@ MAIN:
my $sroot = vinfo($sroot_url, $config_sroot); my $sroot = vinfo($sroot_url, $config_sroot);
vinfo_assign_config($sroot); vinfo_assign_config($sroot);
unless(vinfo_init_root($sroot)) { unless(vinfo_init_root($sroot)) {
ABORTED($sroot, "Failed to fetch subvolume detail" . ($err ? ": $err" : "")); ABORTED($sroot, "Failed to fetch subvolume detail");
WARN "Skipping archive source \"$sroot->{PRINT}\": " . ABORTED_TEXT($sroot); WARN "Skipping archive source \"$sroot->{PRINT}\": " . ABORTED_TEXT($sroot), map("... $_", @stderr);
next; next;
} }
my $droot = vinfo($droot_url, $config_droot); my $droot = vinfo($droot_url, $config_droot);
vinfo_assign_config($droot); vinfo_assign_config($droot);
unless($archive_raw ? vinfo_init_raw_root($droot) : vinfo_init_root($droot)) { unless($archive_raw ? vinfo_init_raw_root($droot) : vinfo_init_root($droot)) {
DEBUG "Failed to fetch " . ($archive_raw ? "raw target metadata" : "subvolume detail") . " for '$droot->{PRINT}'" . ($err ? ": $err" : ""); DEBUG "Failed to fetch " . ($archive_raw ? "raw target metadata" : "subvolume detail") . " for '$droot->{PRINT}'";
unless(system_mkdir($droot)) { unless(system_mkdir($droot)) {
ABORTED($droot, "Failed to create directory: $droot->{PRINT}/"); ABORTED($droot, "Failed to create directory: $droot->{PRINT}/");
WARN "Skipping archive target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot); WARN "Skipping archive target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot), map("... $_", @stderr);
next; next;
} }
$droot->{SUBDIR_CREATED} = 1; $droot->{SUBDIR_CREATED} = 1;
@ -5445,8 +5443,8 @@ MAIN:
else { else {
# after directory is created, try to init again # after directory is created, try to init again
unless($archive_raw ? vinfo_init_raw_root($droot) : vinfo_init_root($droot)) { unless($archive_raw ? vinfo_init_raw_root($droot) : vinfo_init_root($droot)) {
ABORTED($droot, "Failed to fetch subvolume detail" . ($err ? ": $err" : "")); ABORTED($droot, "Failed to fetch subvolume detail");
WARN "Skipping archive target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot); WARN "Skipping archive target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot), map("... $_", @stderr);
next; next;
} }
} }
@ -5616,8 +5614,8 @@ MAIN:
next unless(grep defined($_->{GLOB_CONTEXT}), @{$config_vol->{SUBSECTION}}); next unless(grep defined($_->{GLOB_CONTEXT}), @{$config_vol->{SUBSECTION}});
my $sroot = vinfo($config_vol->{url}, $config_vol); my $sroot = vinfo($config_vol->{url}, $config_vol);
unless(vinfo_init_root($sroot)) { unless(vinfo_init_root($sroot)) {
ABORTED($sroot, "Failed to fetch subvolume detail" . ($err ? ": $err" : "")); ABORTED($sroot, "Failed to fetch subvolume detail");
WARN "Skipping volume \"$sroot->{PRINT}\": " . ABORTED_TEXT($sroot); WARN "Skipping volume \"$sroot->{PRINT}\": " . ABORTED_TEXT($sroot), map("... $_", @stderr);
next; next;
} }
@ -5953,15 +5951,15 @@ MAIN:
foreach my $sroot (vinfo_subsection($config, 'volume')) { foreach my $sroot (vinfo_subsection($config, 'volume')) {
DEBUG "Initializing volume section: $sroot->{PRINT}"; DEBUG "Initializing volume section: $sroot->{PRINT}";
unless(vinfo_init_root($sroot)) { unless(vinfo_init_root($sroot)) {
ABORTED($sroot, "Failed to fetch subvolume detail" . ($err ? ": $err" : "")); ABORTED($sroot, "Failed to fetch subvolume detail");
WARN "Skipping volume \"$sroot->{PRINT}\": " . ABORTED_TEXT($sroot); WARN "Skipping volume \"$sroot->{PRINT}\": " . ABORTED_TEXT($sroot), map("... $_", @stderr);
next; next;
} }
foreach my $svol (vinfo_subsection($sroot, 'subvolume')) { foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
DEBUG "Initializing subvolume section: $svol->{PRINT}"; DEBUG "Initializing subvolume section: $svol->{PRINT}";
unless(vinfo_init_root($svol)) { unless(vinfo_init_root($svol)) {
ABORTED($svol, "Failed to fetch subvolume detail" . ($err ? ": $err" : "")); ABORTED($svol, "Failed to fetch subvolume detail");
WARN "Skipping subvolume \"$svol->{PRINT}\": " . ABORTED_TEXT($svol); WARN "Skipping subvolume \"$svol->{PRINT}\": " . ABORTED_TEXT($svol), map("... $_", @stderr);
next; next;
} }
if((not $svol->{node}{uuid}) || ($svol->{node}{uuid} eq '-')) { if((not $svol->{node}{uuid}) || ($svol->{node}{uuid} eq '-')) {
@ -5991,8 +5989,8 @@ MAIN:
my $snaproot = vinfo_snapshot_root($svol); my $snaproot = vinfo_snapshot_root($svol);
unless(vinfo_init_root($snaproot)) { unless(vinfo_init_root($snaproot)) {
ABORTED($svol, "Failed to fetch subvolume detail for snapshot_dir" . ($err ? ": $err" : "")); ABORTED($svol, "Failed to fetch subvolume detail for snapshot_dir");
WARN "Skipping subvolume \"$svol->{PRINT}\": " . ABORTED_TEXT($svol); WARN "Skipping subvolume \"$svol->{PRINT}\": " . ABORTED_TEXT($svol), map("... $_", @stderr);
next; next;
} }
unless(_is_same_fs_tree($snaproot->{node}, $svol->{node})) { unless(_is_same_fs_tree($snaproot->{node}, $svol->{node})) {
@ -6018,16 +6016,16 @@ MAIN:
if($target_type eq "send-receive") if($target_type eq "send-receive")
{ {
unless(vinfo_init_root($droot)) { unless(vinfo_init_root($droot)) {
ABORTED($droot, "Failed to fetch subvolume detail" . ($err ? ": $err" : "")); ABORTED($droot, "Failed to fetch subvolume detail");
WARN "Skipping target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot); WARN "Skipping target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot), map("... $_", @stderr);
next; next;
} }
} }
elsif($target_type eq "raw") elsif($target_type eq "raw")
{ {
unless(vinfo_init_raw_root($droot)) { unless(vinfo_init_raw_root($droot)) {
ABORTED($droot, "Failed to fetch raw target metadata" . ($err ? ": $err" : "")); ABORTED($droot, "Failed to fetch raw target metadata");
WARN "Skipping target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot); WARN "Skipping target \"$droot->{PRINT}\": " . ABORTED_TEXT($droot), map("... $_", @stderr);
next; next;
} }
} }
@ -6077,7 +6075,7 @@ MAIN:
my $url = $subvol_args[0] || die; my $url = $subvol_args[0] || die;
my $vol = vinfo($url, $config); my $vol = vinfo($url, $config);
unless(vinfo_init_root($vol)) { unless(vinfo_init_root($vol)) {
ERROR "Failed to fetch subvolume detail for: $url" . ($err ? ": $err" : ""); ERROR "Failed to fetch subvolume detail for: $url", map("... $_", @stderr);
exit 1; exit 1;
} }
if($vol->{node}{is_root}) { if($vol->{node}{is_root}) {