Updates in preparation for 3.0.0beta2:
[BackupPC.git] / lib / BackupPC / Xfer / RsyncFileIO.pm
index 820f898..a8b69b9 100644 (file)
@@ -12,7 +12,7 @@
 #
 #========================================================================
 #
-# Version 3.0.0alpha, released 23 Jan 2006.
+# Version 3.0.0beta2, released 11 Nov 2006.
 #
 # See http://backuppc.sourceforge.net.
 #
@@ -61,7 +61,7 @@ sub new
     my $fio = bless {
         blockSize    => 700,
         logLevel     => 0,
-        digest       => File::RsyncP::Digest->new($options->{protocol_version}),
+        digest       => File::RsyncP::Digest->new(),
         checksumSeed => 0,
        attrib       => {},
        logHandler   => \&logHandler,
@@ -76,6 +76,7 @@ sub new
        %$options,
     }, $class;
 
+    $fio->{digest}->protocol($fio->{protocol_version});
     $fio->{shareM}   = $fio->{bpc}->fileNameEltMangle($fio->{share});
     $fio->{outDir}   = "$fio->{xfer}{outDir}/new/";
     $fio->{outDirSh} = "$fio->{outDir}/$fio->{shareM}/";
@@ -320,14 +321,14 @@ sub viewCacheDir
 
 sub attribGetWhere
 {
-    my($fio, $f) = @_;
-    my($dir, $fname, $share, $shareM);
+    my($fio, $f, $noCache) = @_;
+    my($dir, $fname, $share, $shareM, $partial, $attr);
 
     $fname = $f->{name};
     $fname = "$fio->{xfer}{pathHdrSrc}/$fname"
                       if ( defined($fio->{xfer}{pathHdrSrc}) );
     $fname =~ s{//+}{/}g;
-    if ( $fname =~ m{(.*)/(.*)} ) {
+    if ( $fname =~ m{(.*)/(.*)}s ) {
        $shareM = $fio->{shareM};
        $dir = $1;
        $fname = $2;
@@ -340,15 +341,27 @@ sub attribGetWhere
        $dir = "";
        $fname = $fio->{share};
     }
-    $fio->viewCacheDir($share, $dir);
     $shareM .= "/$dir" if ( $dir ne "" );
-    if ( defined(my $attr = $fio->{viewCache}{$shareM}{$fname}) ) {
-        return ($attr, 0);
-    } elsif ( defined(my $attr = $fio->{partialCache}{$shareM}{$fname}) ) {
-        return ($attr, 1);
+
+    if ( $noCache ) {
+        $share  = $fio->{share} if ( !defined($share) );
+        my $dirAttr = $fio->{view}->dirAttrib($fio->{viewNum}, $share, $dir);
+        $attr = $dirAttr->{$fname};
     } else {
-        return;
+        $fio->viewCacheDir($share, $dir);
+        if ( defined($attr = $fio->{viewCache}{$shareM}{$fname}) ) {
+            $partial = 0;
+        } elsif ( defined($attr = $fio->{partialCache}{$shareM}{$fname}) ) {
+            $partial = 1;
+        } else {
+            return;
+        }
+        if ( $attr->{mode} & S_HLINK_TARGET ) {
+            $attr->{hlink_self} = 1;
+            $attr->{mode} &= ~S_HLINK_TARGET;
+        }
     }
+    return ($attr, $partial);
 }
 
 sub attribGet
@@ -376,8 +389,12 @@ sub attribGet
         $fio->log("$attr->{fullPath}: redirecting to $target (will trim "
                 . "$fio->{xfer}{pathHdrSrc})") if ( $fio->{logLevel} >= 4 );
         $target =~ s/^\Q$fio->{xfer}{pathHdrSrc}//;
+        $target =~ s{^/+}{};
+        #
+        # Note: overwrites name to point to real file
+        #
         $f->{name} = $target;
-        $attr = $fio->attribGet($f);
+        ($attr) = $fio->attribGetWhere($f, 1);
         $fio->log(" ... now got $attr->{fullPath}")
                             if ( $fio->{logLevel} >= 4 );
     }
@@ -420,7 +437,7 @@ sub attribSet
     my($fio, $f, $placeHolder) = @_;
     my($dir, $file);
 
-    if ( $f->{name} =~ m{(.*)/(.*)} ) {
+    if ( $f->{name} =~ m{(.*)/(.*)}s ) {
        $file = $2;
        $dir  = "$fio->{shareM}/" . $1;
     } elsif ( $f->{name} eq "." ) {
@@ -474,6 +491,11 @@ sub attribWrite
     my($fio, $d) = @_;
     my($poolWrite);
 
+    #
+    # Don't write attributes on 2nd phase - they're already
+    # taken care of during the first phase.
+    #
+    return if ( $fio->{phase} > 0 );
     if ( !defined($d) ) {
         #
         # flush all entries (in reverse order)
@@ -495,7 +517,7 @@ sub attribWrite
        my $dir;
        my $share;
 
-       $dir = $1 if ( $d =~ m{.+?/(.*)} );
+       $dir = $1 if ( $d =~ m{.+?/(.*)}s );
        $fio->viewCacheDir(undef, $dir);
        ##print("attribWrite $d,$dir\n");
        ##$Data::Dumper::Indent = 1;
@@ -507,7 +529,7 @@ sub attribWrite
        if ( defined($fio->{viewCache}{$d}) ) {
            foreach my $f ( keys(%{$fio->{viewCache}{$d}}) ) {
                my $name = $f;
-               $name = "$1/$name" if ( $d =~ m{.*?/(.*)} );
+               $name = "$1/$name" if ( $d =~ m{.*?/(.*)}s );
                if ( defined(my $a = $fio->{attrib}{$d}->get($f)) ) {
                    #
                    # delete temporary attributes (skipped files)
@@ -517,7 +539,8 @@ sub attribWrite
                        $fio->logFileAction("skip", {
                                    %{$fio->{viewCache}{$d}{$f}},
                                    name => $name,
-                               }) if ( $fio->{logLevel} >= 2 );
+                               }) if ( $fio->{logLevel} >= 2
+                                      && $a->{type} == BPC_FTYPE_FILE );
                    }
                } elsif ( !$fio->{full} ) {
                    ##print("Delete file $f\n");
@@ -542,7 +565,7 @@ sub attribWrite
        my $dirM = $d;
 
        $dirM = $1 . "/" . $fio->{bpc}->fileNameMangle($2)
-                       if ( $dirM =~ m{(.*?)/(.*)} );
+                       if ( $dirM =~ m{(.*?)/(.*)}s );
         my $fileName = $fio->{attrib}{$d}->fileName("$fio->{outDir}$dirM");
        $fio->log("attribWrite(dir=$d) -> $fileName")
                                if ( $fio->{logLevel} >= 4 );
@@ -592,7 +615,7 @@ sub statsGet
 sub makePath
 {
     my($fio, $f) = @_;
-    my $name = $1 if ( $f->{name} =~ /(.*)/ );
+    my $name = $1 if ( $f->{name} =~ /(.*)/s );
     my $path;
 
     if ( $name eq "." ) {
@@ -602,7 +625,7 @@ sub makePath
     }
     $fio->logFileAction("create", $f) if ( $fio->{logLevel} >= 1 );
     $fio->log("makePath($path, 0777)") if ( $fio->{logLevel} >= 5 );
-    $path = $1 if ( $path =~ /(.*)/ );
+    $path = $1 if ( $path =~ /(.*)/s );
     File::Path::mkpath($path, 0, 0777) if ( !-d $path );
     return $fio->attribSet($f) if ( -d $path );
     $fio->log("Can't create directory $path");
@@ -616,7 +639,7 @@ sub makePath
 sub makeSpecial
 {
     my($fio, $f) = @_;
-    my $name = $1 if ( $f->{name} =~ /(.*)/ );
+    my $name = $1 if ( $f->{name} =~ /(.*)/s );
     my $fNameM = $fio->{bpc}->fileNameMangle($name);
     my $path = $fio->{outDirSh} . $fNameM;
     my $attr = $fio->attribGet($f);
@@ -656,12 +679,13 @@ sub makeSpecial
     my($fh, $fileData);
     if ( $fio->{full}
             || !defined($attr)
-            || $attr->{type}  != $type
-            || $attr->{mtime} != $f->{mtime}
-            || $attr->{size}  != $f->{size}
-            || $attr->{uid}   != $f->{uid}
-            || $attr->{gid}   != $f->{gid}
-            || $attr->{mode}  != $f->{mode}
+            || $attr->{type}       != $type
+            || $attr->{mtime}      != $f->{mtime}
+            || $attr->{size}       != $f->{size}
+            || $attr->{uid}        != $f->{uid}
+            || $attr->{gid}        != $f->{gid}
+            || $attr->{mode}       != $f->{mode}
+            || $attr->{hlink_self} != $f->{hlink_self}
             || !defined($fh = BackupPC::FileZIO->open($attr->{fullPath}, 0,
                                                       $attr->{compress}))
             || $fh->read(\$fileData, length($str) + 1) != length($str)
@@ -831,6 +855,21 @@ sub fileDeltaRxStart
               . " ($fio->{rxLocalAttr}{compress} vs $fio->{xfer}{compress})")
                     if ( $fio->{logLevel} >= 4 );
     }
+    #
+    # If the local file is a hardlink then no match
+    #
+    if ( defined($fio->{rxLocalAttr})
+           && $fio->{rxLocalAttr}{type} == BPC_FTYPE_HARDLINK ) {
+        $fio->{rxMatchBlk} = undef;
+        $fio->log("$fio->{rxFile}{name}: no match on hardlinks")
+                                    if ( $fio->{logLevel} >= 4 );
+        my $fCopy;
+        # need to copy since hardlink attribGet overwrites the name
+        %{$fCopy} = %$f;
+        $fio->{rxHLinkAttr} = $fio->attribGet($fCopy, 1); # hardlink attributes
+    } else {
+        delete($fio->{rxHLinkAttr});
+    }
     delete($fio->{rxInFd});
     delete($fio->{rxOutFd});
     delete($fio->{rxDigest});
@@ -873,7 +912,7 @@ sub fileDeltaRxNext
         # need to open an output file where we will build the
         # new version.
         #
-        $fio->{rxFile}{name} =~ /(.*)/;
+        $fio->{rxFile}{name} =~ /(.*)/s;
        my $rxOutFileRel = "$fio->{shareM}/" . $fio->{bpc}->fileNameMangle($1);
         my $rxOutFile    = $fio->{outDir} . $rxOutFileRel;
         $fio->{rxOutFd}  = BackupPC::PoolWrite->new($fio->{bpc},
@@ -883,7 +922,8 @@ sub fileDeltaRxNext
                         if ( $fio->{logLevel} >= 9 );
         $fio->{rxOutFile} = $rxOutFile;
         $fio->{rxOutFileRel} = $rxOutFileRel;
-        $fio->{rxDigest} = File::RsyncP::Digest->new($fio->{protocol_version});
+        $fio->{rxDigest} = File::RsyncP::Digest->new();
+        $fio->{rxDigest}->protocol($fio->{protocol_version});
         $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
     }
     if ( defined($fio->{rxMatchBlk})
@@ -899,12 +939,15 @@ sub fileDeltaRxNext
         my $attr = $fio->{rxLocalAttr};
        my $fh;
         if ( !defined($fio->{rxInFd}) && !defined($fio->{rxInData}) ) {
+            my $inPath = $attr->{fullPath};
+            $inPath = $fio->{rxHLinkAttr}{fullPath}
+                            if ( defined($fio->{rxHLinkAttr}) );
             if ( $attr->{compress} ) {
                 if ( !defined($fh = BackupPC::FileZIO->open(
-                                                   $attr->{fullPath},
+                                                   $inPath,
                                                    0,
                                                    $attr->{compress})) ) {
-                    $fio->log("Can't open $attr->{fullPath}");
+                    $fio->log("Can't open $inPath");
                    $fio->{stats}{errorCnt}++;
                     return -1;
                 }
@@ -955,12 +998,12 @@ sub fileDeltaRxNext
                 }
                 $fh->close;
             } else {
-                if ( open(F, "<", $attr->{fullPath}) ) {
+                if ( open(F, "<", $inPath) ) {
                    binmode(F);
                     $fio->{rxInFd} = *F;
                     $fio->{rxInName} = $attr->{fullPath};
                 } else {
-                    $fio->log("Unable to open $attr->{fullPath}");
+                    $fio->log("Unable to open $inPath");
                    $fio->{stats}{errorCnt}++;
                     return -1;
                 }
@@ -973,7 +1016,7 @@ sub fileDeltaRxNext
         my $seekPosn = $fio->{rxMatchBlk} * $fio->{rxBlkSize};
         if ( defined($fio->{rxInFd})
                        && !sysseek($fio->{rxInFd}, $seekPosn, 0) ) {
-            $fio->log("Unable to seek $attr->{rxInName} to $seekPosn");
+            $fio->log("Unable to seek $fio->{rxInName} to $seekPosn");
            $fio->{stats}{errorCnt}++;
             return -1;
         }
@@ -1036,11 +1079,12 @@ sub fileDeltaRxNext
 sub fileDeltaRxDone
 {
     my($fio, $md4, $phase) = @_;
-    my $name = $1 if ( $fio->{rxFile}{name} =~ /(.*)/ );
+    my $name = $1 if ( $fio->{rxFile}{name} =~ /(.*)/s );
     my $ret;
 
     close($fio->{rxInFd})  if ( defined($fio->{rxInFd}) );
     unlink("$fio->{outDirSh}RStmp") if  ( -f "$fio->{outDirSh}RStmp" );
+    $fio->{phase} = $phase;
 
     #
     # Check the final md4 digest
@@ -1080,7 +1124,8 @@ sub fileDeltaRxDone
                #
                # Empty file; just create an empty file digest
                #
-               $fio->{rxDigest} = File::RsyncP::Digest->new($fio->{protocol_version});
+               $fio->{rxDigest} = File::RsyncP::Digest->new();
+                $fio->{rxDigest}->protocol($fio->{protocol_version});
                $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
                $newDigest = $fio->{rxDigest}->digest;
            }
@@ -1134,11 +1179,13 @@ sub fileDeltaRxDone
         my $f = $fio->{rxFile};
        $fio->logFileAction("same", $f) if ( $fio->{logLevel} >= 1 );
         if ( $fio->{full}
-                || $attr->{type}  != $f->{type}
-                || $attr->{mtime} != $f->{mtime}
-                || $attr->{size}  != $f->{size}
-                || $attr->{gid}   != $f->{gid}
-                || $attr->{mode}  != $f->{mode} ) {
+                || $attr->{type}       != $f->{type}
+                || $attr->{mtime}      != $f->{mtime}
+                || $attr->{size}       != $f->{size}
+                || $attr->{uid}        != $f->{uid}
+                || $attr->{gid}        != $f->{gid}
+                || $attr->{mode}       != $f->{mode}
+                || $attr->{hlink_self} != $f->{hlink_self} ) {
             #
             # In the full case, or if the attributes are different,
             # we need to make a link from the previous file and
@@ -1207,6 +1254,10 @@ sub fileListEltSend
     my $type = $a->{type};
     my $extraAttribs = {};
 
+    if ( $a->{mode} & S_HLINK_TARGET ) {
+        $a->{hlink_self} = 1;
+        $a->{mode} &= ~S_HLINK_TARGET;
+    }
     $n =~ s/^\Q$fio->{xfer}{pathHdrSrc}//;
     $fio->log("Sending $name (remote=$n) type = $type") if ( $fio->{logLevel} >= 1 );
     if ( $type == BPC_FTYPE_CHARDEV
@@ -1252,7 +1303,7 @@ sub fileListEltSend
             && ($type == BPC_FTYPE_HARDLINK || $type == BPC_FTYPE_FILE)
             && ($type == BPC_FTYPE_HARDLINK
                     || $fio->{protocol_version} < 27
-                    || ($a->{mode} & S_HLINK_TARGET) ) ) {
+                    || $a->{hlink_self}) ) {
         #
         # Fill in fake inode information so that the remote rsync
         # can correctly create hardlinks.
@@ -1277,7 +1328,7 @@ sub fileListEltSend
                 $fio->log("$a->{fullPath}: can't open for hardlink");
                 $fio->{stats}{errorCnt}++;
             }
-        } elsif ( $a->{mode} & S_HLINK_TARGET ) {
+        } elsif ( $a->{hlink_self} ) {
             if ( defined($fio->{hlinkFile2Num}{$name}) ) {
                 $inode = $fio->{hlinkFile2Num}{$name};
             } else {
@@ -1307,7 +1358,7 @@ sub fileListEltSend
                             if ( $fio->{clientCharset} ne "" );
     $fList->encode($f);
 
-    $logName = "$fio->{xfer}{pathHdrDest}/$f->{name}";
+    $logName = "$fio->{xfer}{pathHdrDest}/$logName";
     $logName =~ s{//+}{/}g;
     $f->{name} = $logName;
     $fio->logFileAction("restore", $f) if ( $fio->{logLevel} >= 1 );