mirror of https://github.com/digint/btrbk
btrbk: use arrays as arguments for run_cmd(), making it compatible with the adaptions in the open3 branch
parent
a802674d11
commit
fd94bc25fc
202
btrbk
202
btrbk
|
@ -47,7 +47,7 @@ use Date::Calc qw(Today Delta_Days Day_of_Week);
|
||||||
use Getopt::Std;
|
use Getopt::Std;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
|
|
||||||
our $VERSION = "0.19.3";
|
our $VERSION = "0.20.0-dev";
|
||||||
our $AUTHOR = 'Axel Burri <axel@tty0.ch>';
|
our $AUTHOR = 'Axel Burri <axel@tty0.ch>';
|
||||||
our $PROJECT_HOME = '<http://www.digint.ch/btrbk/>';
|
our $PROJECT_HOME = '<http://www.digint.ch/btrbk/>';
|
||||||
|
|
||||||
|
@ -156,47 +156,57 @@ sub WARN { my $t = shift; print STDERR "WARNING: $t\n" if($loglevel >= 1); }
|
||||||
sub ERROR { my $t = shift; print STDERR "ERROR: $t\n"; }
|
sub ERROR { my $t = shift; print STDERR "ERROR: $t\n"; }
|
||||||
|
|
||||||
|
|
||||||
sub run_cmd($;@)
|
sub run_cmd(@)
|
||||||
{
|
{
|
||||||
my $cmd = shift || die;
|
my @commands = (ref($_[0]) eq "HASH") ? @_ : { @_ };
|
||||||
my %opts = @_;
|
|
||||||
my $ret = "";
|
|
||||||
$cmd =~ s/^\s+//;
|
|
||||||
$cmd =~ s/\s+$//;
|
|
||||||
$cmd .= ' 2>&1' if($opts{catch_stderr});
|
|
||||||
$err = "";
|
$err = "";
|
||||||
if($opts{non_destructive} || (not $dryrun)) {
|
|
||||||
DEBUG "### $cmd";
|
|
||||||
$ret = `$cmd`;
|
|
||||||
chomp($ret);
|
|
||||||
TRACE "Command output:\n$ret";
|
|
||||||
if($?) {
|
|
||||||
my $exitcode= $? >> 8;
|
|
||||||
my $signal = $? & 127;
|
|
||||||
DEBUG "Command execution failed (exitcode=$exitcode" . ($signal ? ", signal=$signal" : "") . "): \"$cmd\"";
|
|
||||||
|
|
||||||
if($opts{catch_stderr}) {
|
my $cmd = "";
|
||||||
if($ret =~ /ssh command rejected/) {
|
my $name = "";
|
||||||
# catch errors from ssh_filter_btrbk.sh
|
my $destructive = 0;
|
||||||
$err = "ssh command rejected (please fix ssh_filter_btrbk.sh)";
|
my $pipe = "";
|
||||||
}
|
my $catch_stderr = 0;
|
||||||
elsif($ret =~ /^ERROR: (.*)/) {
|
my $filter_stderr = undef;
|
||||||
# catch errors from btrfs command
|
foreach (@commands) {
|
||||||
$err = $1;
|
$_->{rsh} //= [];
|
||||||
}
|
$_->{cmd} = [ @{$_->{rsh}}, @{$_->{cmd}} ];
|
||||||
else {
|
$_->{cmd_text} = join(' ', map { s/\n/\\n/g; "'$_'" } @{$_->{cmd}}); # ugly escape of \n, do we need to escape others?
|
||||||
DEBUG "Unparseable error: $ret";
|
$name = $_->{name} // $_->{cmd_text};
|
||||||
$err = "unparseable error";
|
$_->{_buf} = '';
|
||||||
}
|
$cmd .= $pipe . $_->{cmd_text};
|
||||||
}
|
$pipe = ' | ';
|
||||||
return undef;
|
if($_->{catch_stderr}) {
|
||||||
|
$cmd .= ' 2>&1';
|
||||||
|
$catch_stderr = 1;
|
||||||
|
$filter_stderr = $_->{filter_stderr};
|
||||||
}
|
}
|
||||||
else {
|
$destructive = 1 unless($_->{non_destructive});
|
||||||
DEBUG "Command execution successful";
|
}
|
||||||
|
|
||||||
|
if($dryrun && $destructive) {
|
||||||
|
DEBUG "### (dryrun) $cmd";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
DEBUG "### $cmd";
|
||||||
|
|
||||||
|
my $ret = "";
|
||||||
|
$ret = `$cmd`;
|
||||||
|
chomp($ret);
|
||||||
|
TRACE "Command output:\n$ret";
|
||||||
|
if($?) {
|
||||||
|
my $exitcode= $? >> 8;
|
||||||
|
my $signal = $? & 127;
|
||||||
|
DEBUG "Command execution failed (exitcode=$exitcode" . ($signal ? ", signal=$signal" : "") . "): \"$cmd\"";
|
||||||
|
|
||||||
|
if($catch_stderr) {
|
||||||
|
$_ = $ret;
|
||||||
|
&{$filter_stderr} ($cmd) if($filter_stderr);
|
||||||
|
ERROR "[$cmd] $_" if($_);
|
||||||
}
|
}
|
||||||
|
return undef;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DEBUG "### (dryrun) $cmd";
|
DEBUG "Command execution successful";
|
||||||
}
|
}
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
@ -218,9 +228,9 @@ sub vinfo($$)
|
||||||
my ($host, $path) = ($1, $2);
|
my ($host, $path) = ($1, $2);
|
||||||
my $ssh_user = config_key($config, "ssh_user");
|
my $ssh_user = config_key($config, "ssh_user");
|
||||||
my $ssh_identity = config_key($config, "ssh_identity");
|
my $ssh_identity = config_key($config, "ssh_identity");
|
||||||
my $ssh_options = "";
|
my @ssh_options;
|
||||||
if($ssh_identity) {
|
if($ssh_identity) {
|
||||||
$ssh_options .= "-i $ssh_identity ";
|
@ssh_options = ('-i', $ssh_identity);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
WARN "No SSH identity provided (option ssh_identity is not set) for: $url";
|
WARN "No SSH identity provided (option ssh_identity is not set) for: $url";
|
||||||
|
@ -233,7 +243,7 @@ sub vinfo($$)
|
||||||
RSH_TYPE => "ssh",
|
RSH_TYPE => "ssh",
|
||||||
SSH_USER => $ssh_user,
|
SSH_USER => $ssh_user,
|
||||||
SSH_IDENTITY => $ssh_identity,
|
SSH_IDENTITY => $ssh_identity,
|
||||||
RSH => "/usr/bin/ssh $ssh_options" . $ssh_user . '@' . $host,
|
RSH => ['/usr/bin/ssh', @ssh_options, $ssh_user . '@' . $host ],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
elsif(($url =~ /^\//) && ($url =~ /^$file_match$/)) {
|
elsif(($url =~ /^\//) && ($url =~ /^$file_match$/)) {
|
||||||
|
@ -586,7 +596,9 @@ sub parse_config(@)
|
||||||
|
|
||||||
sub btrfs_filesystem_show_all_local()
|
sub btrfs_filesystem_show_all_local()
|
||||||
{
|
{
|
||||||
return run_cmd("btrfs filesystem show", non_destructive => 1);
|
return run_cmd( cmd => [ qw(btrfs filesystem show) ],
|
||||||
|
non_destructive => 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -594,8 +606,10 @@ sub btrfs_filesystem_show($)
|
||||||
{
|
{
|
||||||
my $vol = shift || die;
|
my $vol = shift || die;
|
||||||
my $path = $vol->{PATH} // die;
|
my $path = $vol->{PATH} // die;
|
||||||
my $rsh = $vol->{RSH} || "";
|
return run_cmd( cmd => [ qw(btrfs filesystem show), $path ],
|
||||||
return run_cmd("$rsh btrfs filesystem show '$path'", non_destructive => 1);
|
rsh => $vol->{RSH},
|
||||||
|
non_destructive => 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -603,8 +617,10 @@ sub btrfs_filesystem_df($)
|
||||||
{
|
{
|
||||||
my $vol = shift || die;
|
my $vol = shift || die;
|
||||||
my $path = $vol->{PATH} // die;
|
my $path = $vol->{PATH} // die;
|
||||||
my $rsh = $vol->{RSH} || "";
|
return run_cmd( cmd => [qw(btrfs filesystem df), $path],
|
||||||
return run_cmd("$rsh btrfs filesystem df '$path'", non_destructive => 1);
|
rsh => $vol->{RSH},
|
||||||
|
non_destructive => 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -612,8 +628,10 @@ sub btrfs_filesystem_usage($)
|
||||||
{
|
{
|
||||||
my $vol = shift || die;
|
my $vol = shift || die;
|
||||||
my $path = $vol->{PATH} // die;
|
my $path = $vol->{PATH} // die;
|
||||||
my $rsh = $vol->{RSH} || "";
|
return run_cmd( cmd => [ qw(btrfs filesystem usage), $path ],
|
||||||
return run_cmd("$rsh btrfs filesystem usage '$path'", non_destructive => 1);
|
rsh => $vol->{RSH},
|
||||||
|
non_destructive => 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -621,8 +639,28 @@ sub btrfs_subvolume_detail($)
|
||||||
{
|
{
|
||||||
my $vol = shift || die;
|
my $vol = shift || die;
|
||||||
my $path = $vol->{PATH} // die;
|
my $path = $vol->{PATH} // die;
|
||||||
my $rsh = $vol->{RSH} || "";
|
my $ret = run_cmd(cmd => [ qw(btrfs subvolume show), $path],
|
||||||
my $ret = run_cmd("$rsh btrfs subvolume show '$path'", non_destructive => 1, catch_stderr => 1);
|
rsh => $vol->{RSH},
|
||||||
|
non_destructive => 1,
|
||||||
|
catch_stderr => 1, # hack for shell-based run_cmd()
|
||||||
|
filter_stderr => sub {
|
||||||
|
if(/ssh command rejected/) {
|
||||||
|
# catch errors from 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 = $_;
|
||||||
|
}
|
||||||
|
# consume stderr line, as $err will be displayed as a user-friendly WARNING
|
||||||
|
$_ = undef;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return undef unless(defined($ret));
|
return undef unless(defined($ret));
|
||||||
|
|
||||||
# workaround for btrfs-progs < 3.17.3 (returns exit status 0 on errors)
|
# workaround for btrfs-progs < 3.17.3 (returns exit status 0 on errors)
|
||||||
|
@ -643,7 +681,7 @@ sub btrfs_subvolume_detail($)
|
||||||
}
|
}
|
||||||
my %detail = ( REAL_PATH => $real_path );
|
my %detail = ( REAL_PATH => $real_path );
|
||||||
|
|
||||||
if($ret eq "$real_path is btrfs root") {
|
if($ret =~ /^\Q$real_path\E is btrfs root/) {
|
||||||
DEBUG "found btrfs root: $vol->{PRINT}";
|
DEBUG "found btrfs root: $vol->{PRINT}";
|
||||||
$detail{id} = 5;
|
$detail{id} = 5;
|
||||||
$detail{is_root} = 1;
|
$detail{is_root} = 1;
|
||||||
|
@ -690,13 +728,15 @@ sub btrfs_subvolume_list($;@)
|
||||||
my $vol = shift || die;
|
my $vol = shift || die;
|
||||||
my %opts = @_;
|
my %opts = @_;
|
||||||
my $path = $vol->{PATH} // die;
|
my $path = $vol->{PATH} // die;
|
||||||
my $rsh = $vol->{RSH} || "";
|
|
||||||
my $btrfs_progs_compat = $vol->{BTRFS_PROGS_COMPAT} || $opts{btrfs_progs_compat};
|
my $btrfs_progs_compat = $vol->{BTRFS_PROGS_COMPAT} || $opts{btrfs_progs_compat};
|
||||||
my $filter_option = "-a";
|
my @filter_options = ('-a');
|
||||||
$filter_option = "-o" if($opts{subvol_only});
|
push(@filter_options, '-o') if($opts{subvol_only});
|
||||||
my $display_options = "-c -u -q";
|
my @display_options = ('-c', '-u', '-q');
|
||||||
$display_options .= " -R" unless($btrfs_progs_compat);
|
push(@display_options, '-R') unless($btrfs_progs_compat);
|
||||||
my $ret = run_cmd("$rsh btrfs subvolume list $filter_option $display_options '$path'", non_destructive => 1);
|
my $ret = run_cmd(cmd => [ qw(btrfs subvolume list), @filter_options, @display_options, $path ],
|
||||||
|
rsh => $vol->{RSH},
|
||||||
|
non_destructive => 1,
|
||||||
|
);
|
||||||
return undef unless(defined($ret));
|
return undef unless(defined($ret));
|
||||||
|
|
||||||
my @nodes;
|
my @nodes;
|
||||||
|
@ -754,9 +794,11 @@ sub btrfs_subvolume_find_new($$;$)
|
||||||
{
|
{
|
||||||
my $vol = shift || die;
|
my $vol = shift || die;
|
||||||
my $path = $vol->{PATH} // die;
|
my $path = $vol->{PATH} // die;
|
||||||
my $rsh = $vol->{RSH} || "";
|
|
||||||
my $lastgen = shift // die;
|
my $lastgen = shift // die;
|
||||||
my $ret = run_cmd("$rsh btrfs subvolume find-new '$path' $lastgen", non_destructive => 1);
|
my $ret = run_cmd(cmd => [ qw(btrfs subvolume find-new), $path, $lastgen ],
|
||||||
|
rsh => $vol->{RSH},
|
||||||
|
non_destructive => 1,
|
||||||
|
);
|
||||||
unless(defined($ret)) {
|
unless(defined($ret)) {
|
||||||
ERROR "Failed to fetch modified files for: $vol->{PRINT}";
|
ERROR "Failed to fetch modified files for: $vol->{PRINT}";
|
||||||
return undef;
|
return undef;
|
||||||
|
@ -813,13 +855,14 @@ sub btrfs_subvolume_snapshot($$)
|
||||||
my $svol = shift || die;
|
my $svol = shift || die;
|
||||||
my $target_path = shift // die;
|
my $target_path = shift // die;
|
||||||
my $src_path = $svol->{PATH} // die;
|
my $src_path = $svol->{PATH} // die;
|
||||||
my $rsh = $svol->{RSH} || "";
|
|
||||||
DEBUG "[btrfs] snapshot (ro):";
|
DEBUG "[btrfs] snapshot (ro):";
|
||||||
DEBUG "[btrfs] host : $svol->{HOST}" if($svol->{HOST});
|
DEBUG "[btrfs] host : $svol->{HOST}" if($svol->{HOST});
|
||||||
DEBUG "[btrfs] source: $src_path";
|
DEBUG "[btrfs] source: $src_path";
|
||||||
DEBUG "[btrfs] target: $target_path";
|
DEBUG "[btrfs] target: $target_path";
|
||||||
INFO ">>> " . ($svol->{HOST} ? "{$svol->{HOST}}" : "") . $target_path;
|
INFO ">>> " . ($svol->{HOST} ? "{$svol->{HOST}}" : "") . $target_path;
|
||||||
my $ret = run_cmd("$rsh btrfs subvolume snapshot -r '$src_path' '$target_path'");
|
my $ret = run_cmd(cmd => [ qw(btrfs subvolume snapshot), '-r', $src_path, $target_path ],
|
||||||
|
rsh => $svol->{RSH},
|
||||||
|
);
|
||||||
ERROR "Failed to create btrfs subvolume snapshot: $svol->{PRINT} -> $target_path" unless(defined($ret));
|
ERROR "Failed to create btrfs subvolume snapshot: $svol->{PRINT} -> $target_path" unless(defined($ret));
|
||||||
return defined($ret) ? $target_path : undef;
|
return defined($ret) ? $target_path : undef;
|
||||||
}
|
}
|
||||||
|
@ -833,17 +876,21 @@ sub btrfs_subvolume_delete($@)
|
||||||
die if($commit && ($commit ne "after") && ($commit ne "each"));
|
die if($commit && ($commit ne "after") && ($commit ne "each"));
|
||||||
$targets = [ $targets ] unless(ref($targets) eq "ARRAY");
|
$targets = [ $targets ] unless(ref($targets) eq "ARRAY");
|
||||||
return 0 unless(scalar(@$targets));
|
return 0 unless(scalar(@$targets));
|
||||||
my $rsh = $targets->[0]->{RSH} || "";
|
my $rsh = $targets->[0]->{RSH};
|
||||||
|
my $rsh_host_check = $targets->[0]->{HOST} || "";
|
||||||
foreach (@$targets) {
|
foreach (@$targets) {
|
||||||
# make sure all targets share same RSH
|
# make sure all targets share same HOST
|
||||||
my $rsh_check = $_->{RSH} || "";
|
my $host = $_->{HOST} || "";
|
||||||
die if($rsh ne $rsh_check);
|
die if($rsh_host_check ne $host);
|
||||||
}
|
}
|
||||||
DEBUG "[btrfs] delete" . ($commit ? " (commit-$commit):" : ":");
|
DEBUG "[btrfs] delete" . ($commit ? " (commit-$commit):" : ":");
|
||||||
DEBUG "[btrfs] subvolume: $_->{PRINT}" foreach(@$targets);
|
DEBUG "[btrfs] subvolume: $_->{PRINT}" foreach(@$targets);
|
||||||
my $options = "";
|
my @options;
|
||||||
$options = "--commit-$commit " if($commit);
|
@options = ("--commit-$commit") if($commit);
|
||||||
my $ret = run_cmd("$rsh btrfs subvolume delete $options" . join(' ', map( { "'$_->{PATH}'" } @$targets)));
|
my @target_paths = map( { $_->{PATH} } @$targets);
|
||||||
|
my $ret = run_cmd(cmd => [ qw(btrfs subvolume delete), @options, @target_paths ],
|
||||||
|
rsh => $rsh,
|
||||||
|
);
|
||||||
ERROR "Failed to delete btrfs subvolumes: " . join(' ', map( { $_->{PRINT} } @$targets)) unless(defined($ret));
|
ERROR "Failed to delete btrfs subvolumes: " . join(' ', map( { $_->{PRINT} } @$targets)) unless(defined($ret));
|
||||||
return defined($ret) ? scalar(@$targets) : undef;
|
return defined($ret) ? scalar(@$targets) : undef;
|
||||||
}
|
}
|
||||||
|
@ -855,9 +902,9 @@ sub btrfs_send_receive($$$)
|
||||||
my $target = shift || die;
|
my $target = shift || die;
|
||||||
my $parent = shift;
|
my $parent = shift;
|
||||||
my $snapshot_path = $snapshot->{PATH} // die;
|
my $snapshot_path = $snapshot->{PATH} // die;
|
||||||
my $snapshot_rsh = $snapshot->{RSH} || "";
|
my $snapshot_rsh = $snapshot->{RSH};
|
||||||
my $target_path = $target->{PATH} // die;
|
my $target_path = $target->{PATH} // die;
|
||||||
my $target_rsh = $target->{RSH} || "";
|
my $target_rsh = $target->{RSH};
|
||||||
my $parent_path = $parent ? $parent->{PATH} : undef;
|
my $parent_path = $parent ? $parent->{PATH} : undef;
|
||||||
|
|
||||||
my $snapshot_name = $snapshot_path;
|
my $snapshot_name = $snapshot_path;
|
||||||
|
@ -869,11 +916,24 @@ sub btrfs_send_receive($$$)
|
||||||
DEBUG "[btrfs] parent: $parent->{PRINT}" if($parent);
|
DEBUG "[btrfs] parent: $parent->{PRINT}" if($parent);
|
||||||
DEBUG "[btrfs] target: $target->{PRINT}";
|
DEBUG "[btrfs] target: $target->{PRINT}";
|
||||||
|
|
||||||
my $parent_option = $parent_path ? "-p '$parent_path'" : "";
|
my @send_options;
|
||||||
my $receive_option = "";
|
my @receive_options;
|
||||||
$receive_option = "-v" if($loglevel >= 3);
|
push(@send_options, '-p', $parent_path) if($parent_path);
|
||||||
|
push(@send_options, '-v') if($loglevel >= 3);
|
||||||
|
push(@receive_options, '-v') if($loglevel >= 3);
|
||||||
|
|
||||||
my $ret = run_cmd("$snapshot_rsh btrfs send $parent_option '$snapshot_path' | $target_rsh btrfs receive $receive_option '$target_path/'");
|
my $ret = run_cmd(
|
||||||
|
{
|
||||||
|
cmd => [ qw(btrfs send), @send_options, $snapshot_path ],
|
||||||
|
rsh => $snapshot_rsh,
|
||||||
|
name => "btrfs send",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cmd => [ qw(btrfs receive), @receive_options, $target_path . '/' ],
|
||||||
|
rsh => $target_rsh,
|
||||||
|
name => "btrfs receive",
|
||||||
|
},
|
||||||
|
);
|
||||||
unless(defined($ret)) {
|
unless(defined($ret)) {
|
||||||
ERROR "Failed to send/receive btrfs subvolume: $snapshot->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $target->{PRINT}";
|
ERROR "Failed to send/receive btrfs subvolume: $snapshot->{PRINT} " . ($parent_path ? "[$parent_path]" : "") . " -> $target->{PRINT}";
|
||||||
return undef;
|
return undef;
|
||||||
|
|
Loading…
Reference in New Issue