* checkin with 3.2.0beta0 release header
[BackupPC.git] / lib / BackupPC / Xfer / RsyncDigest.pm
index 07d5a10..9e12feb 100644 (file)
@@ -11,7 +11,7 @@
 #   Craig Barratt  <cbarratt@users.sourceforge.net>
 #
 # COPYRIGHT
-#   Copyright (C) 2001-2003  Craig Barratt
+#   Copyright (C) 2001-2007  Craig Barratt
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
@@ -29,7 +29,7 @@
 #
 #========================================================================
 #
-# Version 2.1.0beta2, released 9 May 2004.
+# Version 3.2.0beta0, released 5 April 2009.
 #
 # See http://backuppc.sourceforge.net.
 #
@@ -42,6 +42,7 @@ use BackupPC::FileZIO;
 
 use vars qw( $RsyncLibOK );
 use Carp;
+use Fcntl;
 require Exporter;
 use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
 
@@ -100,11 +101,11 @@ sub fileDigestIsCached
     my($class, $file) = @_;
     my $data;
 
-    open(my $fh, "<", $file) || return -1;
+    sysopen(my $fh, $file, O_RDONLY) || return -1;
     binmode($fh);
     return -2 if ( sysread($fh, $data, 1) != 1 );
     close($fh);
-    return $data eq chr(0xd6) ? 1 : 0;
+    return $data eq chr(0xd7) ? 1 : 0;
 }
 
 #
@@ -113,13 +114,15 @@ sub fileDigestIsCached
 # Empty files don't get cached checksums.
 #
 # If verify is set then existing cached checksums are checked.
+# If verify == 2 then only a verify is done; no fixes are applied.
 # 
 # Returns 0 on success.  Returns 1 on good verify and 2 on bad verify.
 # Returns a variety of negative values on error.
 #
 sub digestAdd
 {
-    my($class, $file, $blockSize, $checksumSeed, $verify) = @_;
+    my($class, $file, $blockSize, $checksumSeed, $verify,
+                $protocol_version) = @_;
     my $retValue = 0;
 
     #
@@ -138,19 +141,23 @@ sub digestAdd
     return -101 if ( !$RsyncLibOK );
 
     my $digest = File::RsyncP::Digest->new;
+    $digest->protocol($protocol_version)
+                        if ( defined($protocol_version) );
     $digest->add(pack("V", $checksumSeed)) if ( $checksumSeed );
 
     return -102 if ( !defined(my $fh = BackupPC::FileZIO->open($file, 0, 1)) );
 
+    my $fileSize;
     while ( 1 ) {
         $fh->read(\$data, $nBlks * $blockSize);
+        $fileSize += length($data);
         last if ( $data eq "" );
         $blockDigest .= $digest->blockDigest($data, $blockSize, 16,
                                              $checksumSeed);
         $digest->add($data);
     }
     $fileDigest = $digest->digest2;
-    my $eofPosn = tell($fh->{fh});
+    my $eofPosn = sysseek($fh->{fh}, 0, 1);
     $fh->close;
     my $rsyncData = $blockDigest . $fileDigest;
     my $metaData  = pack("VVVV", $blockSize,
@@ -164,10 +171,10 @@ sub digestAdd
 #                                            length($metaData),
 #                                            $file,
 #                                            $eofPosn);
-    open(my $fh2, "+<", $file) || return -103;
+    sysopen(my $fh2, $file, O_RDWR) || return -103;
     binmode($fh2);
     return -104 if ( sysread($fh2, $data, 1) != 1 );
-    if ( $data ne chr(0x78) && $data ne chr(0xd6) ) {
+    if ( $data ne chr(0x78) && $data ne chr(0xd6) && $data ne chr(0xd7) ) {
         &$Log(sprintf("digestAdd: $file has unexpected first char 0x%x",
                              ord($data)));
         return -105;
@@ -179,7 +186,7 @@ sub digestAdd
         #
         # Verify the cached checksums
         #
-        return -107 if ( $data ne chr(0xd6) );
+        return -107 if ( $data ne chr(0xd7) );
         return -108 if ( sysread($fh2, $data3, length($data2) + 1) < 0 );
         if ( $data2 eq $data3 ) {
             return 1;
@@ -187,9 +194,13 @@ sub digestAdd
         #
         # Checksums don't agree - fall through so we rewrite the data
         #
-        &$Log("digestAdd: $file verify failed; redoing checksums");
+        &$Log(sprintf("digestAdd: %s verify failed; redoing checksums; len = %d,%d; eofPosn = %d, fileSize = %d",
+                $file, length($data2), length($data3), $eofPosn, $fileSize));
+        #&$Log(sprintf("dataNew  = %s", unpack("H*", $data2)));
+        #&$Log(sprintf("dataFile = %s", unpack("H*", $data3)));
         return -109 if ( sysseek($fh2, $eofPosn, 0) != $eofPosn );
         $retValue = 2;
+        return $retValue if ( $verify == 2 );
     }
     return -110 if ( syswrite($fh2, $data2) != length($data2) );
     if ( $verify ) {
@@ -199,19 +210,20 @@ sub digestAdd
         # match our expected length.
         #
         return -111 if ( !defined(sysseek($fh2, 0, 2)) );
-        if ( tell($fh2) != $eofPosn + length($data2) ) {
+        if ( sysseek($fh2, 0, 1) != $eofPosn + length($data2) ) {
             if ( !truncate($fh2, $eofPosn + length($data2)) ) {
                 &$Log(sprintf("digestAdd: $file truncate from %d to %d failed",
-                                tell($fh2), $eofPosn + length($data2)));
+                                sysseek($fh2, 0, 1), $eofPosn + length($data2)));
                 return -112;
             } else {
-                &$Log(sprintf("digestAdd: $file truncated from %d to %d",
-                                tell($fh2), $eofPosn + length($data2)));
+                &$Log(sprintf("digestAdd: %s truncated from %d to %d",
+                                $file,
+                                sysseek($fh2, 0, 1), $eofPosn + length($data2)));
             }
         }
     }
     return -113 if ( !defined(sysseek($fh2, 0, 0)) );
-    return -114 if ( syswrite($fh2, chr(0xd6)) != 1 );
+    return -114 if ( syswrite($fh2, chr(0xd7)) != 1 );
     close($fh2);
     return $retValue;
 }
@@ -235,7 +247,7 @@ sub digestAdd
 sub digestStart
 {
     my($class, $fileName, $fileSize, $blockSize, $defBlkSize,
-       $checksumSeed, $needMD4, $compress, $doCache) = @_;
+       $checksumSeed, $needMD4, $compress, $doCache, $protocol_version) = @_;
 
     return -1 if ( !$RsyncLibOK );
 
@@ -245,15 +257,19 @@ sub digestStart
         name     => $fileName,
         needMD4  => $needMD4,
         digest   => File::RsyncP::Digest->new,
+        protocol_version => $protocol_version,
     }, $class;
 
+    $dg->{digest}->protocol($dg->{protocol_version})
+                        if ( defined($dg->{protocol_version}) );
+
     if ( $fileSize > 0 && $compress && $doCache >= 0 ) {
         open(my $fh, "<", $fileName) || return -2;
         binmode($fh);
-        return -3 if ( read($fh, $data, 1) != 1 );
+        return -3 if ( sysread($fh, $data, 4096) < 1 );
         my $ret;
 
-        if ( $data eq chr(0x78) && $doCache > 0
+        if ( (vec($data, 0, 8) == 0x78 || vec($data, 0, 8) == 0xd6) && $doCache > 0
                      && $checksumSeed == RSYNC_CSUMSEED_CACHE ) {
             #
             # RSYNC_CSUMSEED_CACHE (32761) is the magic number that
@@ -271,7 +287,7 @@ sub digestStart
                             $blockSize
                                 || BackupPC::Xfer::RsyncDigest->blockSize(
                                                     $fileSize, $defBlkSize),
-                                $checksumSeed);
+                                $checksumSeed, 0, $dg->{protocol_version});
             if ( $ret < 0 ) {
                 &$Log("digestAdd($fileName) failed ($ret)");
             }
@@ -282,31 +298,44 @@ sub digestStart
             binmode($fh);
             return -5 if ( read($fh, $data, 1) != 1 );
         }
-        if ( $ret >= 0 && $data eq chr(0xd6) ) {
+        if ( $ret >= 0 && vec($data, 0, 8) == 0xd7 ) {
             #
             # Looks like this file has cached checksums
             # Read the last 48 bytes: that's 2 file MD4s (32 bytes)
             # plus 4 words of meta data
             #
-            return -6 if ( !defined(seek($fh, -48, 2)) ); 
-            return -7 if ( read($fh, $data, 48) != 48 );
+            my $cacheInfo;
+            if ( length($data) >= 4096 ) {
+                return -6 if ( !defined(sysseek($fh, -4096, 2)) ); 
+                return -7 if ( sysread($fh, $data, 4096) != 4096 );
+            }
+            $cacheInfo = substr($data, -48);
             ($dg->{md4DigestOld},
              $dg->{md4Digest},
              $dg->{blockSize},
              $dg->{checksumSeed},
              $dg->{nBlocks},
-             $dg->{magic}) = unpack("a16 a16 V V V V", $data);
+             $dg->{magic}) = unpack("a16 a16 V V V V", $cacheInfo);
             if ( $dg->{magic} == 0x5fe3c289
                     && $dg->{checksumSeed} == $checksumSeed
                     && ($blockSize == 0 || $dg->{blockSize} == $blockSize) ) {
                 $dg->{fh}     = $fh;
                 $dg->{cached} = 1;
-                #
-                # position the file at the start of the rsync block checksums
-                # (4 (adler) + 16 (md4) bytes each)
-                #
-                return -8
-                    if ( !defined(seek($fh, -$dg->{nBlocks}*20 - 48, 2)) );
+                if ( length($data) >= $dg->{nBlocks} * 20 + 48 ) {
+                    #
+                    # We have all the data already - just remember it
+                    #
+                    $dg->{digestData} = substr($data,
+                                               length($data) - $dg->{nBlocks} * 20 - 48,
+                                               $dg->{nBlocks} * 20);
+                } else {
+                    #
+                    # position the file at the start of the rsync block checksums
+                    # (4 (adler) + 16 (md4) bytes each)
+                    #
+                    return -8
+                        if ( !defined(sysseek($fh, -$dg->{nBlocks} * 20 - 48, 2)) );
+                }
             } else {
                 #
                 # cached checksums are not valid, so we close the
@@ -332,6 +361,8 @@ sub digestStart
         return -9 if ( !defined($dg->{fh}) );
         if ( $needMD4) {
             $dg->{csumDigest} = File::RsyncP::Digest->new;
+            $dg->{csumDigest}->protocol($dg->{protocol_version})
+                                if ( defined($dg->{protocol_version}) );
             $dg->{csumDigest}->add(pack("V", $dg->{checksumSeed}));
         }
     }
@@ -347,7 +378,12 @@ sub digestGet
     if ( $dg->{cached} ) {
         my $thisNum = $num;
         $thisNum = $dg->{nBlocks} if ( $thisNum > $dg->{nBlocks} );
-        read($dg->{fh}, $fileData, 20 * $thisNum);
+        if ( defined($dg->{digestData}) ) {
+            $fileData = substr($dg->{digestData}, 0, 20 * $thisNum);
+            $dg->{digestData} = substr($dg->{digestData}, 20 * $thisNum);
+        } else {
+            sysread($dg->{fh}, $fileData, 20 * $thisNum);
+        }
         $dg->{nBlocks} -= $thisNum;
         if ( $thisNum < $num && !$noPad) {
             #
@@ -376,7 +412,13 @@ sub digestEnd
 
     if ( $dg->{cached} ) {
         close($dg->{fh});
-        return $dg->{md4DigestOld} if ( $dg->{needMD4} );
+        if ( $dg->{needMD4} ) {
+            if ( $dg->{protocol_version} <= 26 ) {
+                return $dg->{md4DigestOld};
+            } else {
+                return $dg->{md4Digest};
+            }
+        }
     } else {
         #
         # make sure we read the entire file for the file MD4 digest
@@ -421,7 +463,7 @@ sub logHandler
 #
 sub logHandlerSet
 {
-    my($sub) = @_;
+    my($dg, $sub) = @_;
 
     $Log = $sub;
 }