diff --git a/ChangeLog b/ChangeLog index f12a289..be67740 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ btrbk-current + * Bugfix: send/receive: delete possibly left-behind garbled + subvolume on failure. Fail with unrecoverable error if stray + target subvolume is in the way (closes: #17). * Bugfix: assume unreachable target as clean on snapshot creation if snapshot_create_always is set (closes: #19). diff --git a/btrbk b/btrbk index 3ad8e8f..930819a 100755 --- a/btrbk +++ b/btrbk @@ -971,8 +971,19 @@ sub macro_send_receive($@) INFO "Receiving from snapshot: $snapshot->{PRINT}"; + # check for existing target subvolume + if(my $err_vol = vinfo_subvol($target, $snapshot->{NAME})) { + $config_target->{ABORTED} = "Target subvolume \"$err_vol->{PRINT}\" already exists"; + $config_target->{UNRECOVERABLE} = "Please delete stray subvolume: $err_vol->{PRINT}"; + ERROR $config_target->{ABORTED} . ", aborting send/receive of: $snapshot->{PRINT}"; + ERROR $config_target->{UNRECOVERABLE}; + $info{ERROR} = 1; + return undef; + } + # add info to $config->{SUBVOL_RECEIVED} - $info{received_name} = "$target->{PRINT}/$snapshot->{NAME}"; + my $vol_received = vinfo_child($target, $snapshot->{NAME}); + $info{received_subvolume} = $vol_received; $config_target->{SUBVOL_RECEIVED} //= []; push(@{$config_target->{SUBVOL_RECEIVED}}, \%info); @@ -1002,6 +1013,19 @@ sub macro_send_receive($@) } else { $info{ERROR} = 1; $config_target->{ABORTED} = "Failed to send/receive subvolume"; + + # NOTE: btrfs-progs v3.19.1 does not delete garbled received subvolume, + # we need to do this by hand. + # TODO: remove this as soon as btrfs-progs handle receive errors correctly. + DEBUG "send/received failed, deleting (possibly present and garbled) received subvolume: $vol_received->{PRINT}"; + my $ret = btrfs_subvolume_delete($vol_received, commit => "after"); + if(defined($ret)) { + WARN "Deleted partially received (garbled) subvolume: $vol_received->{PRINT}"; + } + else { + WARN "Deletion of partially received (garbled) subvolume failed, assuming clean environment: $vol_received->{PRINT}"; + } + return undef; } } @@ -1783,8 +1807,7 @@ MAIN: my @schedule; my $found_missing = 0; - # sort children of svol ascending by generation - foreach my $child (get_snapshot_children($sroot, $svol)) + foreach my $child (sort { $a->{gen} <=> $b->{gen} } get_snapshot_children($sroot, $svol)) { if(scalar get_receive_targets($droot, $child)) { DEBUG "Found matching receive target, skipping: $child->{PRINT}"; @@ -1792,6 +1815,10 @@ MAIN: else { DEBUG "No matching receive targets found, adding resume candidate: $child->{PRINT}"; + if(my $err_vol = vinfo_subvol($droot, $child->{NAME})) { + WARN "Target subvolume \"$err_vol->{PRINT}\" exists, but is not a receive target of \"$child->{PRINT}\""; + } + # check if the target would be preserved my ($date, $date_ext) = get_date_tag($child->{SUBVOL_PATH}); next unless($date && ($child->{SUBVOL_PATH} =~ /^\Q$snapdir\/$snapshot_basename\E$snapshot_postfix_match$/)); @@ -1979,6 +2006,7 @@ MAIN: unless($quiet) { my @out; + my @unrecoverable; my $err_count = 0; foreach my $config_vol (@{$config->{VOLUME}}) { @@ -2007,7 +2035,7 @@ MAIN: $create_mode = ">>>" if($_->{parent}); # substr($create_mode, 0, 1, '%') if($_->{resume}); $create_mode = "!!!" if($_->{ERROR}); - push @out, "$create_mode $_->{received_name}"; + push @out, "$create_mode $_->{received_subvolume}->{PRINT}"; } if($config_target->{SUBVOL_DELETED}) { @@ -2018,6 +2046,8 @@ MAIN: push @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, ""; } @@ -2037,12 +2067,13 @@ MAIN: print join("\n", @out); + if($preserve_backups) { + print "\nNOTE: Preserved all backups (option -p present)\n"; + } if($err_count) { print "\nNOTE: Some errors occurred, which may result in missing backups!\n"; print "Please check warning and error messages above.\n"; - } - if($preserve_backups) { - print "\nNOTE: Preserved all backups (option -p present)\n"; + print join("\n", @unrecoverable) . "\n" if(@unrecoverable); } if($dryrun) { print "\nNOTE: Dryrun was active, none of the operations above were actually executed!\n";