mirror of https://github.com/digint/btrbk
btrbk: refactor filter_stderr; more flexible error handling in run_cmd; simplify btrfs_subvolume_delete parsing
parent
8434c6881c
commit
df471c3692
147
btrbk
147
btrbk
|
@ -690,6 +690,7 @@ sub run_cmd(@)
|
||||||
|
|
||||||
my $destructive = 0;
|
my $destructive = 0;
|
||||||
my $catch_stderr = 0;
|
my $catch_stderr = 0;
|
||||||
|
my $exitcode_loglevel = "debug";
|
||||||
my $filter_stderr = undef;
|
my $filter_stderr = undef;
|
||||||
my @cmd_pipe;
|
my @cmd_pipe;
|
||||||
my @unsafe_cmd;
|
my @unsafe_cmd;
|
||||||
|
@ -706,6 +707,7 @@ sub run_cmd(@)
|
||||||
$catch_stderr = 1 if($href->{catch_stderr});
|
$catch_stderr = 1 if($href->{catch_stderr});
|
||||||
$filter_stderr = $href->{filter_stderr} if($href->{filter_stderr}); # NOTE: last filter wins!
|
$filter_stderr = $href->{filter_stderr} if($href->{filter_stderr}); # NOTE: last filter wins!
|
||||||
$destructive = 1 unless($href->{non_destructive});
|
$destructive = 1 unless($href->{non_destructive});
|
||||||
|
$exitcode_loglevel = $href->{exitcode_loglevel} if($href->{exitcode_loglevel});
|
||||||
|
|
||||||
if($href->{check_unsafe}) {
|
if($href->{check_unsafe}) {
|
||||||
_safe_cmd($href->{check_unsafe}, \@unsafe_cmd);
|
_safe_cmd($href->{check_unsafe}, \@unsafe_cmd);
|
||||||
|
@ -819,12 +821,15 @@ sub run_cmd(@)
|
||||||
no warnings qw(exec);
|
no warnings qw(exec);
|
||||||
|
|
||||||
# execute command and parse output
|
# execute command and parse output
|
||||||
|
my @exitcode_msg;
|
||||||
|
my $stderr_fatal;
|
||||||
my $ret = `$cmd`;
|
my $ret = `$cmd`;
|
||||||
if(defined($ret)) {
|
if(defined($ret)) {
|
||||||
chomp($ret);
|
chomp($ret);
|
||||||
TRACE "Command output:\n$ret" if($loglevel >= 4);
|
TRACE "Command output:\n$ret" if($loglevel >= 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# fatal errors
|
||||||
if($? == -1) {
|
if($? == -1) {
|
||||||
ERROR "Command execution failed ($!): `$cmd_print`";
|
ERROR "Command execution failed ($!): `$cmd_print`";
|
||||||
return undef;
|
return undef;
|
||||||
|
@ -834,18 +839,29 @@ sub run_cmd(@)
|
||||||
ERROR "Command execution failed (child died with signal $signal): `$cmd_print`";
|
ERROR "Command execution failed (child died with signal $signal): `$cmd_print`";
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
elsif($?) {
|
my $exitcode = $? >> 8;
|
||||||
my $exitcode= $? >> 8;
|
|
||||||
DEBUG "Command execution failed (exitcode=$exitcode): `$cmd_print`";
|
|
||||||
|
|
||||||
if($catch_stderr) {
|
# handle filter_stderr option
|
||||||
$_ = $ret;
|
if(defined($ret) && $filter_stderr) {
|
||||||
&{$filter_stderr} ($cmd) if($filter_stderr);
|
foreach(split("\n", $ret)) {
|
||||||
if($_) {
|
next if($_ eq "");
|
||||||
# no filter, or uncaught by filter
|
if(my $href = &{$filter_stderr} ($exitcode)) {
|
||||||
ERROR "Command execution failed (exitcode=$exitcode): `$cmd_print`: $_";
|
$stderr_fatal //= $href->{fatal};
|
||||||
|
push @exitcode_msg, $href;
|
||||||
|
$exitcode_loglevel = "error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($exitcode || $stderr_fatal) {
|
||||||
|
my $log_text = "Command execution failed (exitcode=$exitcode): `$cmd_print`";
|
||||||
|
if($exitcode_loglevel eq "error") { ERROR $log_text; }
|
||||||
|
elsif($exitcode_loglevel eq "warn") { WARN $log_text; }
|
||||||
|
else { DEBUG $log_text; }
|
||||||
|
foreach(@exitcode_msg) {
|
||||||
|
ERROR "... $_->{error}" if(defined($_->{error}));
|
||||||
|
WARN "... $_->{warn}" if(defined($_->{warn}));
|
||||||
|
}
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -958,21 +974,19 @@ sub btrfs_subvolume_show($;@)
|
||||||
non_destructive => 1,
|
non_destructive => 1,
|
||||||
catch_stderr => 1, # hack for shell-based run_cmd()
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
filter_stderr => sub {
|
filter_stderr => sub {
|
||||||
if(/ssh command rejected/) {
|
return undef unless($_[0]); # do nothing if exitcode=0
|
||||||
# catch errors from ssh_filter_btrbk.sh
|
if(s/^ERROR: //) { # catch errors from btrfs-progs as well as ssh_filter_btrbk.sh
|
||||||
$err = "ssh command rejected (please fix ssh_filter_btrbk.sh)";
|
|
||||||
}
|
|
||||||
elsif(/^ERROR: (.*)/) {
|
|
||||||
# catch errors from btrfs command
|
|
||||||
$err = $1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
DEBUG "Unparsed error: $_";
|
|
||||||
$err = $_;
|
$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 ||= $_;
|
||||||
}
|
}
|
||||||
# consume stderr line, as $err will be displayed as a user-friendly WARNING
|
# soft fail: $err can be displayed as a user-friendly WARNING
|
||||||
$_ = undef;
|
return undef;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return undef unless(defined($ret));
|
return undef unless(defined($ret));
|
||||||
|
@ -1300,7 +1314,6 @@ sub btrfs_subvolume_delete($@)
|
||||||
);
|
);
|
||||||
my $ret;
|
my $ret;
|
||||||
my @deleted;
|
my @deleted;
|
||||||
my @unparsed_errors;
|
|
||||||
my %err_catch;
|
my %err_catch;
|
||||||
if($target_type eq "raw") {
|
if($target_type eq "raw") {
|
||||||
my @cmd_target_paths;
|
my @cmd_target_paths;
|
||||||
|
@ -1320,57 +1333,42 @@ sub btrfs_subvolume_delete($@)
|
||||||
catch_stderr => 1, # hack for shell-based run_cmd()
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
filter_stderr => sub {
|
filter_stderr => sub {
|
||||||
# catch errors from "rm -f"
|
# catch errors from "rm -f"
|
||||||
my @error_lines = split("\n", $_);
|
|
||||||
foreach (@error_lines) {
|
|
||||||
if(/^rm: cannot remove '($file_match)':/) {
|
if(/^rm: cannot remove '($file_match)':/) {
|
||||||
my $catch = $1; # make sure $catch matches $vol->{PATH}
|
my $catch = $1; # make sure $catch matches $vol->{PATH}
|
||||||
$catch =~ s/\.info$//;
|
$catch =~ s/\.info$//;
|
||||||
$catch =~ s/\.split_[a-z][a-z]$//;
|
$catch =~ s/\.split_[a-z][a-z]$//;
|
||||||
$err_catch{$catch} //= [];
|
$err_catch{$catch} //= [];
|
||||||
push(@{$err_catch{$catch}}, $_);
|
push(@{$err_catch{$catch}}, $_);
|
||||||
|
return undef; # no error messages in run_cmd
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
push @unparsed_errors, $_;
|
# show errors in run_cmd; force "Command execution failed" error message
|
||||||
}
|
return { error => $_, fatal => 1 };
|
||||||
}
|
|
||||||
$_ = undef; # prevent "Command execution failed" error message
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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()
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
filter_stderr => sub {
|
filter_stderr => sub {
|
||||||
# catch errors from btrfs command
|
return undef unless(s/^ERROR: //); # strip ERROR prefix
|
||||||
# NOTE: this is NOT RELIABLE ANY MORE (path is not printed in btrfs-progs >= 4.16), e.g.:
|
if(/'($file_match)'/ || /: ($file_match)$/ || /($file_match):/) {
|
||||||
# "ERROR: Could not statfs: No such file or directory"
|
# NOTE: as of btrfs-progs-4.16, this does not catch anything
|
||||||
my @error_lines = split("\n", $_);
|
|
||||||
foreach (@error_lines) {
|
|
||||||
next if(/^Delete subvolume/); # NOTE: stdout is also reflected here!
|
|
||||||
if(/^ERROR: cannot access subvolume ($file_match):/ || # btrfs-progs < 4.16
|
|
||||||
/^ERROR: not a subvolume: ($file_match)/ || # btrfs-progs < 4.16
|
|
||||||
/^ERROR: cannot find real path for '($file_match)':/ ||
|
|
||||||
/^ERROR: cannot delete '($file_match)'/ || # btrfs-progs < 4.16
|
|
||||||
/^ERROR: cannot access subvolume '($file_match)'$/ || # btrfs-progs < 4.4
|
|
||||||
/^ERROR: error accessing '($file_match)'/ || # btrfs-progs < 4.4
|
|
||||||
/^ERROR: '($file_match)' is not a subvolume/ || # btrfs-progs < 4.4
|
|
||||||
/^ERROR: finding real path for '($file_match)'/ || # btrfs-progs < 4.4
|
|
||||||
/^ERROR: can't access '($file_match)'/ ) # btrfs-progs < 4.4
|
|
||||||
{
|
|
||||||
$err_catch{$1} //= [];
|
$err_catch{$1} //= [];
|
||||||
push(@{$err_catch{$1}}, $_);
|
push(@{$err_catch{$1}}, $_);
|
||||||
|
} else {
|
||||||
|
$unparsed_errors = 1;
|
||||||
}
|
}
|
||||||
else {
|
return undef; # no error messages in run_cmd
|
||||||
push @unparsed_errors, $_;
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
$_ = undef; # prevent "Command execution failed" error message
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
$ret = undef if($unparsed_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(defined($ret)) {
|
if(defined($ret)) {
|
||||||
|
@ -1389,17 +1387,12 @@ sub btrfs_subvolume_delete($@)
|
||||||
push @deleted, $check_target;
|
push @deleted, $check_target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if($catch_count != (scalar keys %err_catch)) {
|
@deleted = () if($catch_count != (scalar keys %err_catch));
|
||||||
@deleted = ();
|
|
||||||
ERROR "Failed to assign error messages, assuming nothing deleted";
|
|
||||||
ERROR "Failed to delete subvolume: $_" foreach(map( { $_->{PRINT} } @$targets));
|
|
||||||
}
|
}
|
||||||
}
|
unless(scalar(@deleted)) {
|
||||||
if(@unparsed_errors) {
|
ERROR "Failed to match error messages from delete command, assuming nothing deleted:";
|
||||||
@deleted = ();
|
ERROR "... possibly not deleted subvolume: $_" foreach(map( { $_->{PRINT} } @$targets));
|
||||||
ERROR "Failed to delete subvolume, assuming nothing deleted";
|
ERROR "... consider running 'btrbk prune -n'";
|
||||||
ERROR "[delete]: $_" foreach(@unparsed_errors);
|
|
||||||
ERROR "Failed to delete subvolume: $_" foreach(map( { $_->{PRINT} } @$targets));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1471,6 +1464,7 @@ 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
|
||||||
catch_stderr => 1, # hack for shell-based run_cmd()
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1479,7 +1473,12 @@ sub btrfs_send_receive($$;$$$)
|
||||||
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()
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
filter_stderr => sub { $err = $_; $_ = undef }
|
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;
|
||||||
|
@ -1491,17 +1490,6 @@ sub btrfs_send_receive($$;$$$)
|
||||||
my $ret = run_cmd(@cmd_pipe);
|
my $ret = run_cmd(@cmd_pipe);
|
||||||
unless(defined($ret)) {
|
unless(defined($ret)) {
|
||||||
$send_receive_error = 1;
|
$send_receive_error = 1;
|
||||||
$ret = $err; # print the errors below
|
|
||||||
}
|
|
||||||
if(defined($ret)) {
|
|
||||||
# NOTE: btrfs-progs < 4.11: if "btrfs send" fails, "btrfs receive" returns 0! so we need to parse the output for "ERROR:" lines:
|
|
||||||
my @ret_lines = split("\n", $ret);
|
|
||||||
$send_receive_error = 1 if(grep /^ERROR: /, @ret_lines);
|
|
||||||
foreach(@ret_lines) {
|
|
||||||
if(/^WARNING: / || $send_receive_error) {
|
|
||||||
WARN "[send/receive] (send=$snapshot_path, receive=$target_path) $_";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Read in target subvolume metadata (btrfs subvolume show):
|
# Read in target subvolume metadata (btrfs subvolume show):
|
||||||
|
@ -1549,6 +1537,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
|
||||||
$send_receive_error = 1;
|
$send_receive_error = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1556,7 +1545,8 @@ sub btrfs_send_receive($$;$$$)
|
||||||
end_transaction("send-receive", not $send_receive_error);
|
end_transaction("send-receive", not $send_receive_error);
|
||||||
|
|
||||||
if($send_receive_error) {
|
if($send_receive_error) {
|
||||||
ERROR "Failed to send/receive btrfs subvolume: $snapshot->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $target->{PRINT}";
|
ERROR "Failed to send/receive subvolume: $snapshot->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $vol_received->{PRINT}";
|
||||||
|
ERROR "... $err" if($err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($is_garbled) {
|
if($is_garbled) {
|
||||||
|
@ -1621,6 +1611,13 @@ 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
|
||||||
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
|
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) {
|
||||||
|
@ -1836,6 +1833,7 @@ 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()
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
);
|
);
|
||||||
return undef unless(defined($ret));
|
return undef unless(defined($ret));
|
||||||
|
@ -4091,6 +4089,7 @@ sub macro_send_receive(@)
|
||||||
if($source->{node}{uuid} =~ /^$fake_uuid_prefix/) {
|
if($source->{node}{uuid} =~ /^$fake_uuid_prefix/) {
|
||||||
DEBUG "Fetching uuid of new subvolume: $source->{PRINT}";
|
DEBUG "Fetching uuid of new subvolume: $source->{PRINT}";
|
||||||
my $detail = btrfs_subvolume_show($source);
|
my $detail = btrfs_subvolume_show($source);
|
||||||
|
return undef unless($detail);
|
||||||
die unless($detail->{uuid});
|
die unless($detail->{uuid});
|
||||||
$source->{node}{uuid} = $detail->{uuid};
|
$source->{node}{uuid} = $detail->{uuid};
|
||||||
$uuid_cache{$detail->{uuid}} = $source->{node};
|
$uuid_cache{$detail->{uuid}} = $source->{node};
|
||||||
|
|
Loading…
Reference in New Issue