btrbk: added configuration option "btrfs_progs_compat", for compatibility with btrfs-tools v3.14. Note that the common snapshots are guessed by their filenames when "btrfs_progs_compat" is set

pull/30/head
Axel Burri 2015-03-24 13:13:00 +01:00
parent 8d32ae7c00
commit 28ed7d65e8
5 changed files with 89 additions and 17 deletions

View File

@ -20,3 +20,7 @@
* btrbk-0.14 * btrbk-0.14
- bugfix: correctly handle empty target subvolumes (blocker for all - bugfix: correctly handle empty target subvolumes (blocker for all
new users). Fixes issue #4 new users). Fixes issue #4
* btrbk-current
- added configuration option "btrfs_progs_compat", to be enabled if
using btrfs-progs < 3.17. Fixes issue #6

View File

@ -32,12 +32,14 @@ man-pages properly installed, follow the instructions below.
Prerequisites Prerequisites
------------- -------------
- perl interpreter (probably already installed on your system) - [btrfs-progs]: Btrfs filesystem utilities (use "btrfs_progs_compat"
- [Date::Calc] (perl module, probably already installed on your system) option for hosts running version prior to v3.17)
- [btrfs-progs] (Btrfs filesystem utilities) - Perl interpreter: probably already installed on your system
- [Date::Calc]: Perl module, probably already installed on your system
[Date::Calc]: http://search.cpan.org/perldoc?Date::Calc
[btrfs-progs]: http://www.kernel.org/pub/linux/kernel/people/kdave/btrfs-progs/ [btrfs-progs]: http://www.kernel.org/pub/linux/kernel/people/kdave/btrfs-progs/
[Date::Calc]: http://search.cpan.org/perldoc?Date::Calc
Instructions Instructions
------------ ------------

80
btrbk
View File

@ -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.14-dev"; our $VERSION = "0.15-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/>';
@ -74,6 +74,7 @@ my %config_options = (
btrfs_commit_delete => { default => undef, accept => [ "after", "each", "no" ] }, btrfs_commit_delete => { default => undef, accept => [ "after", "each", "no" ] },
ssh_identity => { default => undef, accept_file => { absolute => 1 } }, ssh_identity => { default => undef, accept_file => { absolute => 1 } },
ssh_user => { default => "root", accept_regexp => qr/^[a-z_][a-z0-9_-]*$/ }, ssh_user => { default => "root", accept_regexp => qr/^[a-z_][a-z0-9_-]*$/ },
btrfs_progs_compat => { default => undef, accept => [ "yes", "no" ] },
); );
my @config_target_types = qw(send-receive); my @config_target_types = qw(send-receive);
@ -81,6 +82,7 @@ my @config_target_types = qw(send-receive);
my %vol_info; my %vol_info;
my %uuid_info; my %uuid_info;
my %uuid_fs_map; my %uuid_fs_map;
my %vol_btrfs_progs_compat; # hacky, maps all subvolumes without received_uuid information
my $dryrun; my $dryrun;
my $loglevel = 1; my $loglevel = 1;
@ -511,10 +513,13 @@ sub btr_subvolume_list($;$@)
my $vol = shift || die; my $vol = shift || die;
my $config = shift; my $config = shift;
my %opts = @_; my %opts = @_;
my $btrfs_progs_compat = config_key($config, "btrfs_progs_compat");
my $filter_option = "-a"; my $filter_option = "-a";
$filter_option = "-o" if($opts{subvol_only}); $filter_option = "-o" if($opts{subvol_only});
my $display_options = "-c -u -q";
$display_options .= " -R" unless($btrfs_progs_compat);
my ($rsh, $real_vol) = get_rsh($vol, $config); my ($rsh, $real_vol) = get_rsh($vol, $config);
my $ret = run_cmd("$rsh /sbin/btrfs subvolume list $filter_option -c -u -q -R $real_vol", 1); my $ret = run_cmd("$rsh /sbin/btrfs subvolume list $filter_option $display_options $real_vol", 1);
unless(defined($ret)) { unless(defined($ret)) {
WARN "Failed to fetch btrfs subvolume list for: $vol"; WARN "Failed to fetch btrfs subvolume list for: $vol";
return undef; return undef;
@ -528,8 +533,24 @@ sub btr_subvolume_list($;$@)
# the subvolid= option. If -p is given, then parent <ID> is added to # the subvolid= option. If -p is given, then parent <ID> is added to
# the output between ID and top level. The parent?s ID may be used at # the output between ID and top level. The parent?s ID may be used at
# mount time via the subvolrootid= option. # mount time via the subvolrootid= option.
die("Failed to parse line: \"$_\"") unless(/^ID ([0-9]+) gen ([0-9]+) cgen ([0-9]+) top level ([0-9]+) parent_uuid ([0-9a-z-]+) received_uuid ([0-9a-z-]+) uuid ([0-9a-z-]+) path (.+)$/);
my %node = ( # NOTE: btrfs-progs prior to v1.17 do not support the -R flag
my %node;
if($btrfs_progs_compat) {
die("Failed to parse line: \"$_\"") unless(/^ID ([0-9]+) gen ([0-9]+) cgen ([0-9]+) top level ([0-9]+) parent_uuid ([0-9a-z-]+) uuid ([0-9a-z-]+) path (.+)$/);
%node = (
id => $1,
gen => $2,
cgen => $3,
top_level => $4,
parent_uuid => $5, # note: parent_uuid="-" if no parent
# received_uuid => $6,
uuid => $6,
path => $7 # btrfs path, NOT filesystem path
);
} else {
die("Failed to parse line: \"$_\"") unless(/^ID ([0-9]+) gen ([0-9]+) cgen ([0-9]+) top level ([0-9]+) parent_uuid ([0-9a-z-]+) received_uuid ([0-9a-z-]+) uuid ([0-9a-z-]+) path (.+)$/);
%node = (
id => $1, id => $1,
gen => $2, gen => $2,
cgen => $3, cgen => $3,
@ -539,6 +560,7 @@ sub btr_subvolume_list($;$@)
uuid => $7, uuid => $7,
path => $8 # btrfs path, NOT filesystem path path => $8 # btrfs path, NOT filesystem path
); );
}
# NOTE: "btrfs subvolume list <path>" prints <FS_TREE> prefix only if # NOTE: "btrfs subvolume list <path>" prints <FS_TREE> prefix only if
# the subvolume is reachable within <path>. (as of btrfs-progs-3.18.2) # the subvolume is reachable within <path>. (as of btrfs-progs-3.18.2)
@ -723,6 +745,8 @@ sub btr_fs_info($;$)
$uuid_fs_map{$_->{node}->{uuid}}->{$fs_path . '/' . $subvol_path} = 1; $uuid_fs_map{$_->{node}->{uuid}}->{$fs_path . '/' . $subvol_path} = 1;
$ret{$subvol_path} = $_; $ret{$subvol_path} = $_;
} }
$vol_btrfs_progs_compat{$fs_path} = config_key($config, "btrfs_progs_compat"); # missing received_uuid in node{}
return \%ret; return \%ret;
} }
@ -799,6 +823,7 @@ sub btrfs_send_receive($$$$;$)
my $receive_option = ""; my $receive_option = "";
$receive_option = "-v" if($changelog || ($loglevel >= 2)); $receive_option = "-v" if($changelog || ($loglevel >= 2));
$receive_option = "-v -v" if($real_parent && $changelog); $receive_option = "-v -v" if($real_parent && $changelog);
my $cmd = "$rsh_src /sbin/btrfs send $parent_option $real_src | $rsh_target /sbin/btrfs receive $receive_option $real_target/ 2>&1"; my $cmd = "$rsh_src /sbin/btrfs send $parent_option $real_src | $rsh_target /sbin/btrfs receive $receive_option $real_target/ 2>&1";
my $ret = run_cmd($cmd); my $ret = run_cmd($cmd);
unless(defined($ret)) { unless(defined($ret)) {
@ -870,10 +895,25 @@ sub get_latest_common($$$)
# sort children of svol descending by generation # sort children of svol descending by generation
foreach my $child (sort { $b->{node}->{gen} <=> $a->{node}->{gen} } get_snapshot_children($sroot, $svol)) { foreach my $child (sort { $b->{node}->{gen} <=> $a->{node}->{gen} } get_snapshot_children($sroot, $svol)) {
TRACE "get_latest_common: checking source snapshot: $child->{SUBVOL_PATH}"; TRACE "get_latest_common: checking source snapshot: $child->{SUBVOL_PATH}";
foreach (get_receive_targets_by_uuid($droot, $child->{node}->{uuid})) { if($vol_btrfs_progs_compat{$droot}) {
TRACE "get_latest_common: found receive target: $_->{FS_PATH}"; # guess matches by subvolume name (node->received_uuid is not available if BTRFS_PROGS_COMPAT is set)
DEBUG("Latest common snapshots for: $sroot/$svol: src=$child->{FS_PATH} target=$_->{FS_PATH}"); my $child_name = $child->{node}->{REL_PATH};
return ($child, $_); $child_name =~ s/^.*\///; # strip path
foreach my $backup (values %{$vol_info{$droot}}) {
my $backup_name = $backup->{node}->{REL_PATH};
$backup_name =~ s/^.*\///; # strip path
if($backup_name eq $child_name) {
DEBUG("Latest common snapshots for: $sroot/$svol: src=$child->{FS_PATH} target=$backup->{FS_PATH} (NOTE: guessed by subvolume name)");
return ($child, $backup);
}
}
}
else {
foreach (get_receive_targets_by_uuid($droot, $child->{node}->{uuid})) {
TRACE "get_latest_common: found receive target: $_->{FS_PATH}";
DEBUG("Latest common snapshots for: $sroot/$svol: src=$child->{FS_PATH} target=$_->{FS_PATH}");
return ($child, $_);
}
} }
TRACE "get_latest_common: no matching targets found for: $child->{FS_PATH}"; TRACE "get_latest_common: no matching targets found for: $child->{FS_PATH}";
} }
@ -901,8 +941,12 @@ sub _origin_tree
} }
$prefix =~ s/./ /g; $prefix =~ s/./ /g;
if($node->{received_uuid} ne '-') { if($node->{received_uuid}) {
_origin_tree("${prefix}^---", $node->{received_uuid}, $lines); if($node->{received_uuid} ne '-') {
_origin_tree("${prefix}^---", $node->{received_uuid}, $lines);
}
} else {
push(@$lines, ["$prefix^---<missing_received_uuid>", $uuid]); # printed if "btrfs_progs_compat" is set
} }
if($node->{parent_uuid} ne '-') { if($node->{parent_uuid} ne '-') {
_origin_tree("${prefix}", $node->{parent_uuid}, $lines); _origin_tree("${prefix}", $node->{parent_uuid}, $lines);
@ -1338,6 +1382,7 @@ MAIN:
# TODO: reverse tree: print all backups from $droot and their corresponding source snapshots # TODO: reverse tree: print all backups from $droot and their corresponding source snapshots
foreach my $config_vol (@{$config->{VOLUME}}) foreach my $config_vol (@{$config->{VOLUME}})
{ {
my %droot_compat;
my $sroot = $config_vol->{sroot} || die; my $sroot = $config_vol->{sroot} || die;
print "$sroot\n"; print "$sroot\n";
next unless $vol_info{$sroot}; next unless $vol_info{$sroot};
@ -1364,13 +1409,22 @@ MAIN:
my $droot = $config_target->{droot} || die; my $droot = $config_target->{droot} || die;
next unless $vol_info{$droot}; next unless $vol_info{$droot};
foreach (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } (values %{$vol_info{$droot}})) { if($vol_btrfs_progs_compat{$droot}) {
next unless($_->{node}->{received_uuid} eq $snapshot_uuid); $droot_compat{$droot} = 1;
print "| | ^== $_->{FS_PATH}\n"; }
else {
foreach (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } (values %{$vol_info{$droot}})) {
next unless($_->{node}->{received_uuid} eq $snapshot_uuid);
print "| | ^== $_->{FS_PATH}\n";
}
} }
} }
} }
} }
if(keys %droot_compat) {
print "NOTE: Received subvolumes (backups) will are not printed for targets:\n";
print " - " . join("\n - ", (sort keys %droot_compat));
}
print "\n"; print "\n";
} }
exit 0; exit 0;

View File

@ -51,6 +51,10 @@ btrfs_commit_delete after
#receive_log sidecar #receive_log sidecar
receive_log no receive_log no
# Enable compatibility mode for btrfs-progs < 3.17.
# Set this either globally or in a specific "target" section.
#btrfs_progs_compat yes
# #
# Volume section: "volume <volume-directory>" # Volume section: "volume <volume-directory>"

View File

@ -103,6 +103,14 @@ to \(lqsidecar\(rq, the file will be created in the backup directory,
named \fI<backup_subvolume>.btrbk.log\fR. Note that this log file can named \fI<backup_subvolume>.btrbk.log\fR. Note that this log file can
become very big, as every change of every file is being become very big, as every change of every file is being
logged. Consider this as a debugging feature. Defaults to \(lqno\(rq. logged. Consider this as a debugging feature. Defaults to \(lqno\(rq.
.TP
\fBbtrfs_progs_compat\fR yes|no \fI*experimental*\fR
Enable compatibility mode for btrfs-progs < 3.17 (\fIbtrfs
--version\fR). This option can be set either globally or within a
\fItarget\fR section. If enabled, the latest common snapshots are
determined by subvolume names instead of \fIreceived_uuid\fR, which
can lead to false guesses if the snapshot or target subvolumes are
manipulated by hand (moved, deleted).
.PP .PP
Lines that contain a hash character (#) in the first column are Lines that contain a hash character (#) in the first column are
treated as comments. treated as comments.