Index: lib/Brackup/Restore.pm =================================================================== --- lib/Brackup/Restore.pm (revision 20) +++ lib/Brackup/Restore.pm (working copy) @@ -8,6 +8,9 @@ use String::Escape qw(unprintable); use Brackup::DecryptedFile; use Brackup::Decrypt; +use Brackup::Util qw(io_sha1); +use IO::File; +use IO::InnerFile; sub new { my ($class, %opts) = @_; @@ -270,6 +273,9 @@ unless $good_dig =~ /^sha1:(.+)/; $good_dig = $1; + $self->_verify_chunks($full, \@chunks, defined($self->{_meta}{'GPG-Recipient'})) + if @chunks > 1; + sysopen(my $readfh, $full, O_RDONLY) or die "Failed to reopen '$full' for verification: $!"; binmode($readfh); my $sha1 = Digest::SHA1->new; @@ -278,7 +284,14 @@ # TODO: support --onerror={continue,prompt}, etc, but for now we just die unless ($actual_dig eq $good_dig || $full =~ m!\.brackup-digest\.db\b!) { - die "Digest of restored file ($full) doesn't match"; + if(@chunks == 1) { + die "Digest of restored file ($full) doesn't match"; + } else { + # The chunks were verified, so this is not fatal. + # The full digest is probably invalid due to concurrent modifications during backup + warn "Digest of restored file ($full) doesn't match but all chunks were verified. This usually happens when a file is modified while being backed up." + if $self->{verbose}; + } } } @@ -291,5 +304,28 @@ return Brackup::Metafile->open($self->{metafile}->name); } +# for a file with multiple chunks (so chunks have raw_digest), verify that raw +# digest matches the data written on disk +sub _verify_chunks { + my ($self, $file, $chunks, $encrypted) = @_; + + my $fh = new IO::File $file, 'r' or die "Failed to open $file: $!\n"; + $fh->binmode; + + for my $ch (@$chunks) { + my ($offset, $len, $enc_len, $enc_dig, $raw_dig) = split(/;/, $ch); + + $raw_dig = $enc_dig unless $encrypted; + $raw_dig or die "No raw digest for chunk $offset-$len of restored file ($file)\n"; + + my $ifh = new IO::InnerFile($fh, $offset, $len) + or die "Failed to create inner file handle for $file: $!\n"; + my $sha1 = io_sha1($ifh); + + $raw_dig eq "sha1:$sha1" + or die "Digest of chunk $offset-$len of restored file ($file) doesn't match\n$raw_dig $sha1"; + } +} + 1;