diff --git a/ChangeLog b/ChangeLog index 97d0ebd..4b0703c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ btrbk-current * Set PATH variable instead of using absolute "/sbin/btrfs" for compatibility with all linux distros out there, which all install 'btrfs' in different locations (closes: #20). + * Added command line option -r (resume only). * Catch and display errors from "btrfs subvolume show". * Include systemd service and timer unit for daily backups. diff --git a/btrbk b/btrbk index 5da8105..ee06126 100755 --- a/btrbk +++ b/btrbk @@ -47,7 +47,7 @@ use Date::Calc qw(Today Delta_Days Day_of_Week); use Getopt::Std; use Data::Dumper; -our $VERSION = "0.17.2-dev"; +our $VERSION = "0.18.0-dev-resume_only"; our $AUTHOR = 'Axel Burri '; our $PROJECT_HOME = ''; @@ -118,6 +118,7 @@ sub HELP_MESSAGE print STDERR " --version display version information\n"; print STDERR " -c FILE specify configuration file\n"; print STDERR " -p preserve all backups (do not delete any old targets)\n"; + print STDERR " -r resume only (no new snapshots, resume all missing backups)\n"; print STDERR " -v be verbose (set loglevel=info)\n"; print STDERR " -q be quiet (do not print summary at end of \"run\" command)\n"; print STDERR " -l LEVEL set loglevel (warn, info, debug, trace)\n"; @@ -1277,7 +1278,7 @@ MAIN: my @today = Today(); my %opts; - unless(getopts('hc:vql:p', \%opts)) { + unless(getopts('hc:prvql:', \%opts)) { VERSION_MESSAGE(); HELP_MESSAGE(0); exit 1; @@ -1297,6 +1298,7 @@ MAIN: @config_src = ( $opts{c} ) if($opts{c}); my $quiet = $opts{q}; my $preserve_backups = $opts{p}; + my $resume_only = $opts{r}; # check command line options if($opts{h} || (not $command)) { @@ -1729,65 +1731,71 @@ MAIN: if($action_run) { - # - # create snapshots - # - my $timestamp = sprintf("%04d%02d%02d", @today); - foreach my $config_vol (@{$config->{VOLUME}}) + if($resume_only) { + INFO "Skipping snapshot creation (option \"-r\" present)"; + } + else { - next if($config_vol->{ABORTED}); - my $sroot = $config_vol->{sroot} || die; - foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) + # + # create snapshots + # + my $timestamp = sprintf("%04d%02d%02d", @today); + foreach my $config_vol (@{$config->{VOLUME}}) { - next if($config_subvol->{ABORTED}); - my $svol = $config_subvol->{svol} || die; - my $snapdir = config_key($config_subvol, "snapshot_dir") || ""; - my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die; + 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 $snapdir = config_key($config_subvol, "snapshot_dir") || ""; + my $snapshot_basename = config_key($config_subvol, "snapshot_name") // die; - # check if we need to create a snapshot - my $create_snapshot = config_key($config_subvol, "snapshot_create_always"); - foreach my $config_target (@{$config_subvol->{TARGET}}) { - next if($config_target->{ABORTED}); - $create_snapshot = 1 if($config_target->{target_type} eq "send-receive"); - } - unless($create_snapshot) { - $config_subvol->{ABORTED} = "No targets defined for subvolume: $svol->{PRINT}"; - WARN "Skipping subvolume section: $config_subvol->{ABORTED}"; - next; - } - - # find unique snapshot name - my @unconfirmed_target_name; - my @lookup = keys %{vinfo_subvol_list($sroot)}; - @lookup = grep s/^\Q$snapdir\E\/// , @lookup; - foreach my $config_target (@{$config_subvol->{TARGET}}) { - if($config_target->{ABORTED}) { - push(@unconfirmed_target_name, vinfo($config_target->{url}, $config_target)); + # check if we need to create a snapshot + my $create_snapshot = config_key($config_subvol, "snapshot_create_always"); + foreach my $config_target (@{$config_subvol->{TARGET}}) { + next if($config_target->{ABORTED}); + $create_snapshot = 1 if($config_target->{target_type} eq "send-receive"); + } + unless($create_snapshot) { + $config_subvol->{ABORTED} = "No targets defined for subvolume: $svol->{PRINT}"; + WARN "Skipping subvolume section: $config_subvol->{ABORTED}"; next; } - my $droot = $config_target->{droot} || die; - push(@lookup, keys %{vinfo_subvol_list($droot)}); - } - @lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup; - TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup); - @lookup = map { /_([0-9]+)$/ ? $1 : 0 } @lookup; - @lookup = sort { $b <=> $a } @lookup; - my $postfix_counter = $lookup[0] // -1; - $postfix_counter++; - my $snapshot_name = $snapshot_basename . '.' . $timestamp . ($postfix_counter ? "_$postfix_counter" : ""); - if(@unconfirmed_target_name) { - INFO "Failed to check all targets, assuming non-present subvolume \"$snapshot_name\" in: " . join(", ", map { "\"$_->{PRINT}\"" } @unconfirmed_target_name); - } + # find unique snapshot name + my @unconfirmed_target_name; + my @lookup = keys %{vinfo_subvol_list($sroot)}; + @lookup = grep s/^\Q$snapdir\E\/// , @lookup; + foreach my $config_target (@{$config_subvol->{TARGET}}) { + if($config_target->{ABORTED}) { + push(@unconfirmed_target_name, vinfo($config_target->{url}, $config_target)); + next; + } + my $droot = $config_target->{droot} || die; + push(@lookup, keys %{vinfo_subvol_list($droot)}); + } + @lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup; + TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup); + @lookup = map { /_([0-9]+)$/ ? $1 : 0 } @lookup; + @lookup = sort { $b <=> $a } @lookup; + my $postfix_counter = $lookup[0] // -1; + $postfix_counter++; + my $snapshot_name = $snapshot_basename . '.' . $timestamp . ($postfix_counter ? "_$postfix_counter" : ""); - # finally create the snapshot - INFO "Creating subvolume snapshot for: $svol->{PRINT}"; - if(btrfs_subvolume_snapshot($svol, "$sroot->{PATH}/$snapdir/$snapshot_name")) { - $config_subvol->{SNAPSHOT} = vinfo_child($sroot, "$snapdir/$snapshot_name"); - } - else { - $config_subvol->{ABORTED} = "Failed to create snapshot: $svol->{PRINT} -> $sroot->{PRINT}/$snapdir/$snapshot_name"; - WARN "Skipping subvolume section: $config_subvol->{ABORTED}"; + if(@unconfirmed_target_name) { + INFO "Failed to check all targets, assuming non-present subvolume \"$snapshot_name\" in: " . join(", ", map { "\"$_->{PRINT}\"" } @unconfirmed_target_name); + } + + # finally create the snapshot + INFO "Creating subvolume snapshot for: $svol->{PRINT}"; + if(btrfs_subvolume_snapshot($svol, "$sroot->{PATH}/$snapdir/$snapshot_name")) { + $config_subvol->{SNAPSHOT} = vinfo_child($sroot, "$snapdir/$snapshot_name"); + } + else { + $config_subvol->{ABORTED} = "Failed to create snapshot: $svol->{PRINT} -> $sroot->{PRINT}/$snapdir/$snapshot_name"; + WARN "Skipping subvolume section: $config_subvol->{ABORTED}"; + } } } } @@ -1818,7 +1826,9 @@ MAIN: WARN "Ignoring deprecated option \"receive_log\" for target: $droot->{PRINT}" } + # # resume missing backups (resume_missing) + # if(config_key($config_target, "resume_missing")) { INFO "Checking for missing backups of subvolume \"$svol->{PRINT}\" in: $droot->{PRINT}/"; @@ -1896,18 +1906,21 @@ MAIN: } } - # skip creation if resume_missing failed - next if($config_target->{ABORTED}); - die unless($config_subvol->{SNAPSHOT}); + unless($resume_only) + { + # skip creation if resume_missing failed + next if($config_target->{ABORTED}); + die unless($config_subvol->{SNAPSHOT}); - # finally receive the previously created snapshot - INFO "Creating subvolume backup (send-receive) for: $svol->{PRINT}"; - my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot); - macro_send_receive($config_target, - snapshot => $config_subvol->{SNAPSHOT}, - target => $droot, - parent => $latest_common_src, # this is if no common found - ); + # finally receive the previously created snapshot + INFO "Creating subvolume backup (send-receive) for: $svol->{PRINT}"; + my ($latest_common_src, $latest_common_target) = get_latest_common($sroot, $svol, $droot); + macro_send_receive($config_target, + snapshot => $config_subvol->{SNAPSHOT}, + target => $droot, + parent => $latest_common_src, # this is if no common found + ); + } } else { ERROR "Unknown target type \"$target_type\", skipping: $svol->{PRINT}"; @@ -1921,8 +1934,8 @@ MAIN: # # remove backups following a preserve daily/weekly/monthly scheme # - if($preserve_backups) { - INFO "Preserving all backups (option \"-p\" present)"; + if($preserve_backups || $resume_only) { + INFO "Preserving all backups (option \"-p\" or \"-r\" present)"; } else { @@ -2033,19 +2046,19 @@ MAIN: my $sroot = $config_vol->{sroot} || vinfo($config_vol->{url}, $config_vol); foreach my $config_subvol (@{$config_vol->{SUBVOLUME}}) { + my @subvol_out; my $svol = $config_subvol->{svol} || vinfo_child($sroot, $config_subvol->{rel_path}); - push @out, "$svol->{PRINT}"; if($config_vol->{ABORTED}) { - push @out, "!!! $sroot->{PRINT}: ABORTED: $config_vol->{ABORTED}"; + push @subvol_out, "!!! $sroot->{PRINT}: ABORTED: $config_vol->{ABORTED}"; $err_count++ unless($config_vol->{ABORTED_NOERR}); } if($config_subvol->{ABORTED}) { - push @out, "!!! Subvolume \"$svol->{PRINT}\" aborted: $config_subvol->{ABORTED}"; + push @subvol_out, "!!! Subvolume \"$svol->{PRINT}\" aborted: $config_subvol->{ABORTED}"; $err_count++ unless($config_subvol->{ABORTED_NOERR}); } - push @out, "+++ $config_subvol->{SNAPSHOT}->{PRINT}" if($config_subvol->{SNAPSHOT}); + push @subvol_out, "+++ $config_subvol->{SNAPSHOT}->{PRINT}" if($config_subvol->{SNAPSHOT}); if($config_subvol->{SUBVOL_DELETED}) { - push @out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_subvol->{SUBVOL_DELETED}}); + push @subvol_out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_subvol->{SUBVOL_DELETED}}); } foreach my $config_target (@{$config_subvol->{TARGET}}) { @@ -2055,21 +2068,26 @@ MAIN: $create_mode = ">>>" if($_->{parent}); # substr($create_mode, 0, 1, '%') if($_->{resume}); $create_mode = "!!!" if($_->{ERROR}); - push @out, "$create_mode $_->{received_subvolume}->{PRINT}"; + push @subvol_out, "$create_mode $_->{received_subvolume}->{PRINT}"; } if($config_target->{SUBVOL_DELETED}) { - push @out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_target->{SUBVOL_DELETED}}); + push @subvol_out, "--- $_->{PRINT}" foreach(sort { $b->{PATH} cmp $a->{PATH} } @{$config_target->{SUBVOL_DELETED}}); } if($config_target->{ABORTED}) { - push @out, "!!! Target \"$droot->{PRINT}\" aborted: $config_target->{ABORTED}"; + push @subvol_out, "!!! Target \"$droot->{PRINT}\" aborted: $config_target->{ABORTED}"; $err_count++ unless($config_target->{ABORTED_NOERR}); } push(@unrecoverable, $config_target->{UNRECOVERABLE}) if($config_target->{UNRECOVERABLE}); } - push @out, ""; + if(@subvol_out) { + push @out, "$svol->{PRINT}", @subvol_out, ""; + } + else { + push @out, "$svol->{PRINT}", "", ""; + } } } @@ -2087,8 +2105,11 @@ MAIN: print join("\n", @out); - if($preserve_backups) { - print "\nNOTE: Preserved all backups (option -p present)\n"; + if($resume_only) { + print "\nNOTE: No snapshots created (option -r present)\n"; + } + if($preserve_backups || $resume_only) { + print "\nNOTE: Preserved all backups (option -p or -r present)\n"; } if($err_count) { print "\nNOTE: Some errors occurred, which may result in missing backups!\n";