1 #============================================================= -*-perl-*-
8 # Craig Barratt <cbarratt@users.sourceforge.net>
11 # Copyright (C) 2002 Craig Barratt
13 #========================================================================
15 # Version 1.6.0_CVS, released 10 Dec 2002.
17 # See http://backuppc.sourceforge.net.
19 #========================================================================
21 package BackupPC::Xfer::RsyncFileIO;
25 use BackupPC::Attrib qw(:all);
26 use BackupPC::FileZIO;
27 use BackupPC::PoolWrite;
30 use constant S_IFMT => 0170000; # type of file
31 use constant S_IFDIR => 0040000; # directory
32 use constant S_IFCHR => 0020000; # character special
33 use constant S_IFBLK => 0060000; # block special
34 use constant S_IFREG => 0100000; # regular
35 use constant S_IFLNK => 0120000; # symbolic link
36 use constant S_IFSOCK => 0140000; # socket
37 use constant S_IFIFO => 0010000; # fifo
39 use vars qw( $RsyncLibOK );
42 eval "use File::RsyncP::Digest";
45 # Rsync module doesn't exist.
55 my($class, $options) = @_;
57 return if ( !$RsyncLibOK );
62 digest => File::RsyncP::Digest->new,
68 $fio->{shareM} = $fio->{bpc}->fileNameEltMangle($fio->{xfer}{shareName});
69 $fio->{outDir} = "$fio->{xfer}{outDir}/new/";
70 $fio->{outDirSh} = "$fio->{outDir}/$fio->{shareM}/";
71 $fio->{view} = BackupPC::View->new($fio->{bpc}, $fio->{host},
73 $fio->{full} = $fio->{xfer}{type} eq "full" ? 1 : 0;
74 $fio->{newFilesFH} = $fio->{xfer}{newFilesFH};
75 $fio->{lastBkupNum} = $fio->{xfer}{lastBkupNum};
81 my($fio, $value) = @_;
83 $fio->{blockSize} = $value if ( defined($value) );
84 return $fio->{blockSize};
88 # Setup rsync checksum computation for the given file.
93 my $attr = $fio->attribGet($f);
96 $fio->csumEnd if ( defined($fio->{fh}) );
97 return if ( $attr->{type} != BPC_FTYPE_FILE );
98 if ( !defined($fio->{fh} = BackupPC::FileZIO->open($attr->{fullPath},
100 $attr->{compress})) ) {
101 $fio->log("Can't open $attr->{fullPath}");
108 my($fio, $num, $csumLen, $blockSize) = @_;
114 return if ( !defined($fio->{fh}) );
115 if ( $fio->{fh}->read(\$fileData, $blockSize * $num) <= 0 ) {
116 return $fio->csumEnd;
118 #$fileData = substr($fileData, 0, $blockSize * $num - 2);
119 $fio->log(sprintf("%s: getting csum ($num,$csumLen,%d,0x%x)\n",
122 $fio->{checksumSeed}))
123 if ( $fio->{logLevel} >= 10 );
124 return $fio->{digest}->rsyncChecksum($fileData, $blockSize,
125 $csumLen, $fio->{checksumSeed});
132 return if ( !defined($fio->{fh}) );
140 my $attr = $fio->attribGet($f);
143 $fio->readEnd if ( defined($fio->{fh}) );
144 if ( !defined(my $fh = BackupPC::FileZIO->open($attr->{fullPath},
146 $attr->{compress})) ) {
147 $fio->log("Can't open $attr->{fullPath}");
158 return if ( !defined($fio->{fh}) );
159 if ( $fio->{fh}->read(\$fileData, $num) <= 0 ) {
160 return $fio->readEnd;
169 return if ( !defined($fio->{fh}) );
176 my($fio, $checksumSeed) = @_;
178 $fio->{checksumSeed} = $checksumSeed;
183 my($fio, $localDir, $remoteDir) = @_;
185 $fio->{localDir} = $localDir;
186 $fio->{remoteDir} = $remoteDir;
191 my($fio, $share, $dir) = @_;
194 #$fio->log("viewCacheDir($share, $dir)");
195 if ( !defined($share) ) {
196 $share = $fio->{xfer}{shareName};
197 $shareM = $fio->{shareM};
199 $shareM = $fio->{bpc}->fileNameEltMangle($share);
201 $shareM = "$shareM/$dir" if ( $dir ne "" );
202 return if ( defined($fio->{viewCache}{$shareM}) );
204 # purge old cache entries (ie: those that don't match the
205 # first part of $dir).
207 foreach my $d ( keys(%{$fio->{viewCache}}) ) {
208 delete($fio->{viewCache}{$d}) if ( $shareM !~ m{^\Q$d/} );
211 # fetch new directory attributes
213 $fio->{viewCache}{$shareM}
214 = $fio->{view}->dirAttrib($fio->{lastBkupNum}, $share, $dir);
220 my($dir, $fname, $share, $shareM);
222 if ( $f->{name} =~ m{(.*)/(.*)} ) {
223 $shareM = $fio->{shareM};
226 } elsif ( $f->{name} ne "." ) {
227 $shareM = $fio->{shareM};
234 $fname = $fio->{xfer}{shareName};
236 $fio->viewCacheDir($share, $dir);
237 $shareM .= "/$dir" if ( $dir ne "" );
238 return $fio->{viewCache}{$shareM}{$fname};
243 my($fio, $mode) = @_;
245 if ( ($mode & S_IFMT) == S_IFREG ) {
246 return BPC_FTYPE_FILE;
247 } elsif ( ($mode & S_IFMT) == S_IFDIR ) {
248 return BPC_FTYPE_DIR;
249 } elsif ( ($mode & S_IFMT) == S_IFLNK ) {
250 return BPC_FTYPE_SYMLINK;
251 } elsif ( ($mode & S_IFMT) == S_IFCHR ) {
252 return BPC_FTYPE_CHARDEV;
253 } elsif ( ($mode & S_IFMT) == S_IFBLK ) {
254 return BPC_FTYPE_BLOCKDEV;
255 } elsif ( ($mode & S_IFMT) == S_IFIFO ) {
256 return BPC_FTYPE_FIFO;
257 } elsif ( ($mode & S_IFMT) == S_IFSOCK ) {
258 return BPC_FTYPE_SOCKET;
260 return BPC_FTYPE_UNKNOWN;
265 # Set the attributes for a file. Returns non-zero on error.
269 my($fio, $f, $placeHolder) = @_;
272 if ( $f->{name} =~ m{(.*)/(.*)} ) {
274 $dir = "$fio->{shareM}/" . $1;
275 } elsif ( $f->{name} eq "." ) {
277 $file = $fio->{xfer}{shareName};
279 $dir = $fio->{shareM};
283 if ( !defined($fio->{attribLastDir}) || $fio->{attribLastDir} ne $dir ) {
285 # Flush any directories that don't match the first part
286 # of the new directory
288 foreach my $d ( keys(%{$fio->{attrib}}) ) {
289 next if ( $d eq "" || "$dir/" =~ m{^\Q$d/} );
290 $fio->attribWrite($d);
292 $fio->{attribLastDir} = $dir;
294 if ( !exists($fio->{attrib}{$dir}) ) {
295 $fio->{attrib}{$dir} = BackupPC::Attrib->new({
296 compress => $fio->{xfer}{compress},
298 my $path = $fio->{outDir} . $dir;
299 if ( -f $fio->{attrib}{$dir}->fileName($path)
300 && !$fio->{attrib}{$dir}->read($path) ) {
301 $fio->log(sprintf("Unable to read attribute file %s",
302 $fio->{attrib}{$dir}->fileName($path)));
305 $fio->log("attribSet(dir=$dir, file=$file)") if ( $fio->{logLevel} >= 4 );
307 $fio->{attrib}{$dir}->set($file, {
308 type => $fio->mode2type($f->{mode}),
312 size => $placeHolder ? -1 : $f->{size},
313 mtime => $f->{mtime},
323 if ( !defined($d) ) {
325 # flush all entries (in reverse order)
327 foreach $d ( sort({$b cmp $a} keys(%{$fio->{attrib}})) ) {
328 $fio->attribWrite($d);
332 return if ( !defined($fio->{attrib}{$d}) );
334 # Set deleted files in the attributes. Any file in the view
335 # that doesn't have attributes is deleted. All files sent by
336 # rsync have attributes temporarily set so we can do deletion
337 # detection. We also prune these temporary attributes.
343 $dir = $1 if ( $d =~ m{.+?/(.*)} );
344 $fio->viewCacheDir(undef, $dir);
345 ##print("attribWrite $d,$dir\n");
346 ##$Data::Dumper::Indent = 1;
347 ##$fio->log("attribWrite $d,$dir");
348 ##$fio->log("viewCacheLogKeys = ", keys(%{$fio->{viewCache}}));
349 ##$fio->log("attribKeys = ", keys(%{$fio->{attrib}}));
350 ##print "viewCache = ", Dumper($fio->{attrib});
351 ##print "attrib = ", Dumper($fio->{attrib});
352 if ( defined($fio->{viewCache}{$d}) ) {
353 foreach my $f ( keys(%{$fio->{viewCache}{$d}}) ) {
355 $name = "$1/$name" if ( $d =~ m{.*?/(.*)} );
356 if ( defined(my $a = $fio->{attrib}{$d}->get($f)) ) {
358 # delete temporary attributes (skipped files)
360 if ( $a->{size} < 0 ) {
361 $fio->{attrib}{$d}->set($f, undef);
362 $fio->logFileAction("skip", {
363 %{$fio->{viewCache}{$d}{$f}},
365 }) if ( $fio->{logLevel} >= 2 );
368 ##print("Delete file $f\n");
369 $fio->logFileAction("delete", {
370 %{$fio->{viewCache}{$d}{$f}},
372 }) if ( $fio->{logLevel} >= 1 );
373 $fio->{attrib}{$d}->set($f, {
374 type => BPC_FTYPE_DELETED,
385 if ( $fio->{attrib}{$d}->fileCount ) {
386 my $data = $fio->{attrib}{$d}->writeData;
389 $dirM = $1 . "/" . $fio->{bpc}->fileNameMangle($2)
390 if ( $dirM =~ m{(.*?)/(.*)} );
391 my $fileName = $fio->{attrib}{$d}->fileName("$fio->{outDir}$dirM");
392 $fio->log("attribWrite(dir=$d) -> $fileName")
393 if ( $fio->{logLevel} >= 4 );
394 my $poolWrite = BackupPC::PoolWrite->new($fio->{bpc}, $fileName,
395 length($data), $fio->{xfer}{compress});
396 $poolWrite->write(\$data);
397 $fio->processClose($poolWrite, $fio->{attrib}{$d}->fileName($d),
400 delete($fio->{attrib}{$d});
405 my($fio, $poolWrite, $fileName, $origSize, $doStats) = @_;
406 my($exists, $digest, $outSize, $errs) = $poolWrite->close;
408 $fileName =~ s{^/+}{};
409 $fio->log(@$errs) if ( defined($errs) && @$errs );
411 $fio->{stats}{TotalFileCnt}++;
412 $fio->{stats}{TotalFileSize} += $origSize;
416 $fio->{stats}{ExistFileCnt}++;
417 $fio->{stats}{ExistFileSize} += $origSize;
418 $fio->{stats}{ExistFileCompSize} += $outSize;
420 } elsif ( $outSize > 0 ) {
421 my $fh = $fio->{newFilesFH};
422 print($fh "$digest $origSize $fileName\n") if ( defined($fh) );
424 return $exists && $origSize > 0;
431 return $fio->{stats};
435 # Make a given directory. Returns non-zero on error.
440 my $name = $1 if ( $f->{name} =~ /(.*)/ );
443 if ( $name eq "." ) {
444 $path = $fio->{outDirSh};
446 $path = $fio->{outDirSh} . $fio->{bpc}->fileNameMangle($name);
448 $fio->logFileAction("create", $f) if ( $fio->{logLevel} >= 1 );
449 $fio->log("mkpath($path, 0777)") if ( $fio->{logLevel} >= 5 );
450 $path = $1 if ( $path =~ /(.*)/ );
451 File::Path::mkpath($path, 0, 0777) if ( !-d $path );
452 return $fio->attribSet($f) if ( -d $path );
453 $fio->log("Can't create directory $path");
458 # Make a special file. Returns non-zero on error.
463 my $name = $1 if ( $f->{name} =~ /(.*)/ );
464 my $fNameM = $fio->{bpc}->fileNameMangle($name);
465 my $path = $fio->{outDirSh} . $fNameM;
466 my $attr = $fio->attribGet($f);
468 my $type = $fio->mode2type($f->{mode});
470 $fio->log("mkspecial($path, $type, $f->{mode})")
471 if ( $fio->{logLevel} >= 5 );
472 if ( $type == BPC_FTYPE_CHARDEV || $type == BPC_FTYPE_BLOCKDEV ) {
473 my($major, $minor, $fh, $fileData);
475 $major = $f->{rdev} >> 8;
476 $minor = $f->{rdev} & 0xff;
477 $str = "$major,$minor";
478 } elsif ( ($f->{mode} & S_IFMT) == S_IFLNK ) {
482 # Now see if the file is different, or this is a full, in which
483 # case we create the new file.
488 || $attr->{type} != $fio->mode2type($f->{mode})
489 || $attr->{mtime} != $f->{mtime}
490 || $attr->{size} != $f->{size}
491 || $attr->{gid} != $f->{gid}
492 || $attr->{mode} != $f->{mode}
493 || !defined($fh = BackupPC::FileZIO->open($attr->{fullPath}, 0,
495 || $fh->read(\$fileData, length($str) + 1) != length($str)
496 || $fileData ne $str ) {
497 $fh->close if ( defined($fh) );
498 $fh = BackupPC::PoolWrite->new($fio->{bpc}, $path,
499 length($str), $fio->{xfer}{compress});
501 my $exist = $fio->processClose($fh, "$fio->{shareM}/$fNameM",
503 $fio->logFileAction($exist ? "pool" : "create", $f)
504 if ( $fio->{logLevel} >= 1 );
505 return $fio->attribSet($f);
507 $fio->logFileAction("skip", $f) if ( $fio->{logLevel} >= 2 );
509 $fh->close if ( defined($fh) );
514 my($fio, $path) = @_;
516 $fio->log("Unexpected call BackupPC::Xfer::RsyncFileIO->unlink($path)");
520 # Appends to list of log messages
527 push(@{$fio->{log}}, @msg);
531 # Generate a log file message for a completed file
535 my($fio, $action, $f) = @_;
536 my $owner = "$f->{uid}/$f->{gid}";
537 my $type = (("", "p", "c", "", "d", "", "b", "", "", "", "l", "", "s"))
538 [($f->{mode} & S_IFMT) >> 12];
540 $fio->log(sprintf(" %-6s %1s%4o %9s %11.0f %s",
550 # Returns a list of log messages
555 my $log = $fio->{log} || [];
562 # Start receive of file deltas for a particular file.
566 my($fio, $f, $cnt, $size, $remainder) = @_;
568 $fio->{rxFile} = $f; # remote file attributes
569 $fio->{rxLocalAttr} = $fio->attribGet($f); # local file attributes
570 $fio->{rxBlkCnt} = $cnt; # how many blocks we will receive
571 $fio->{rxBlkSize} = $size; # block size
572 $fio->{rxRemainder} = $remainder; # size of the last block
573 $fio->{rxMatchBlk} = 0; # current start of match
574 $fio->{rxMatchNext} = 0; # current next block of match
575 my $rxSize = ($cnt - 1) * $size + $remainder;
576 if ( $fio->{rxFile}{size} != $rxSize ) {
577 $fio->{rxMatchBlk} = undef; # size different, so no file match
578 $fio->log("$fio->{rxFile}{name}: size doesn't match"
579 . " ($fio->{rxFile}{size} vs $rxSize)")
580 if ( $fio->{logLevel} >= 5 );
582 delete($fio->{rxInFd});
583 delete($fio->{rxOutFd});
584 delete($fio->{rxDigest});
585 delete($fio->{rxInData});
589 # Process the next file delta for the current file. Returns 0 if ok,
590 # -1 if not. Must be called with either a block number, $blk, or new data,
591 # $newData, (not both) defined.
595 my($fio, $blk, $newData) = @_;
597 if ( defined($blk) ) {
598 if ( defined($fio->{rxMatchBlk}) && $fio->{rxMatchNext} == $blk ) {
600 # got the next block in order; just keep track.
602 $fio->{rxMatchNext}++;
606 my $newDataLen = length($newData);
607 $fio->log("$fio->{rxFile}{name}: blk=$blk, newData=$newDataLen, rxMatchBlk=$fio->{rxMatchBlk}, rxMatchNext=$fio->{rxMatchNext}")
608 if ( $fio->{logLevel} >= 8 );
609 if ( !defined($fio->{rxOutFd}) ) {
611 # maybe the file has no changes
613 if ( $fio->{rxMatchNext} == $fio->{rxBlkCnt}
614 && !defined($blk) && !defined($newData) ) {
615 #$fio->log("$fio->{rxFile}{name}: file is unchanged");
616 # if ( $fio->{logLevel} >= 8 );
621 # need to open an output file where we will build the
624 $fio->{rxFile}{name} =~ /(.*)/;
625 my $rxOutFileRel = "$fio->{shareM}/" . $fio->{bpc}->fileNameMangle($1);
626 my $rxOutFile = $fio->{outDir} . $rxOutFileRel;
627 $fio->{rxOutFd} = BackupPC::PoolWrite->new($fio->{bpc},
628 $rxOutFile, $fio->{rxFile}{size},
629 $fio->{xfer}{compress});
630 $fio->log("$fio->{rxFile}{name}: opening output file $rxOutFile")
631 if ( $fio->{logLevel} >= 10 );
632 $fio->{rxOutFile} = $rxOutFile;
633 $fio->{rxOutFileRel} = $rxOutFileRel;
634 $fio->{rxDigest} = File::RsyncP::Digest->new;
635 $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
637 if ( defined($fio->{rxMatchBlk})
638 && $fio->{rxMatchBlk} != $fio->{rxMatchNext} ) {
640 # Need to copy the sequence of blocks that matched. If the file
641 # is compressed we need to make a copy of the uncompressed file,
642 # since the compressed file is not seekable. Future optimizations
643 # would be to keep the uncompressed file in memory (eg, up to say
644 # 10MB), only create an uncompressed copy if the matching
645 # blocks were not monotonic, and to only do this if there are
646 # matching blocks (eg, maybe the entire file is new).
648 my $attr = $fio->{rxLocalAttr};
650 if ( !defined($fio->{rxInFd}) && !defined($fio->{rxInData}) ) {
651 if ( $attr->{compress} ) {
652 if ( !defined($fh = BackupPC::FileZIO->open(
655 $attr->{compress})) ) {
656 $fio->log("Can't open $attr->{fullPath}");
659 if ( $attr->{size} < 10 * 1024 * 1024 ) {
661 # Cache the entire old file if it is less than 10MB
664 $fio->{rxInData} = "";
665 while ( $fh->read(\$data, 10 * 1024 * 1024) > 0 ) {
666 $fio->{rxInData} .= $data;
670 # Create and write a temporary output file
672 unlink("$fio->{outDirSh}RStmp")
673 if ( -f "$fio->{outDirSh}RStmp" );
674 if ( open(F, ">+$fio->{outDirSh}RStmp") ) {
676 while ( $fh->read(\$data, 1024 * 1024) > 0 ) {
677 if ( syswrite(F, $data) != length($data) ) {
678 $fio->log(sprintf("Can't write len=%d to %s",
679 length($data) , "$fio->{outDirSh}RStmp"));
685 $fio->{rxInName} = "$fio->{outDirSh}RStmp";
686 seek($fio->{rxInFd}, 0, 0);
688 $fio->log("Unable to open $fio->{outDirSh}RStmp");
695 if ( open(F, $attr->{fullPath}) ) {
697 $fio->{rxInName} = $attr->{fullPath};
699 $fio->log("Unable to open $attr->{fullPath}");
704 my $lastBlk = $fio->{rxMatchNext} - 1;
705 $fio->log("$fio->{rxFile}{name}: writing blocks $fio->{rxMatchBlk}.."
707 if ( $fio->{logLevel} >= 10 );
708 my $seekPosn = $fio->{rxMatchBlk} * $fio->{rxBlkSize};
709 if ( defined($fio->{rxInFd}) && !seek($fio->{rxInFd}, $seekPosn, 0) ) {
710 $fio->log("Unable to seek $attr->{fullPath} to $seekPosn");
713 my $cnt = $fio->{rxMatchNext} - $fio->{rxMatchBlk};
714 my($thisCnt, $len, $data);
715 for ( my $i = 0 ; $i < $cnt ; $i += $thisCnt ) {
716 $thisCnt = $cnt - $i;
717 $thisCnt = 512 if ( $thisCnt > 512 );
718 if ( $fio->{rxMatchBlk} + $i + $thisCnt == $fio->{rxBlkCnt} ) {
719 $len = ($thisCnt - 1) * $fio->{rxBlkSize} + $fio->{rxRemainder};
721 $len = $thisCnt * $fio->{rxBlkSize};
723 if ( defined($fio->{rxInData}) ) {
724 $data = substr($fio->{rxInData}, $seekPosn, $len);
726 if ( sysread($fio->{rxInFd}, $data, $len) != $len ) {
727 $fio->log("Unable to read $len bytes from"
728 . " $fio->{rxInName} "
729 . "($i,$thisCnt,$fio->{rxBlkCnt})");
733 $fio->{rxOutFd}->write(\$data);
734 $fio->{rxDigest}->add($data);
736 $fio->{rxMatchBlk} = undef;
738 if ( defined($blk) ) {
740 # Remember the new block number
742 $fio->{rxMatchBlk} = $blk;
743 $fio->{rxMatchNext} = $blk + 1;
745 if ( defined($newData) ) {
747 # Write the new chunk
749 my $len = length($newData);
750 $fio->log("$fio->{rxFile}{name}: writing $len bytes new data")
751 if ( $fio->{logLevel} >= 10 );
752 $fio->{rxOutFd}->write(\$newData);
753 $fio->{rxDigest}->add($newData);
758 # Finish up the current receive file. Returns undef if ok, -1 if not.
759 # Returns 1 if the md4 digest doesn't match.
764 my $name = $1 if ( $fio->{rxFile}{name} =~ /(.*)/ );
766 if ( !defined($fio->{rxDigest}) ) {
768 # File was exact match, but we still need to verify the
769 # MD4 checksum. Therefore open and read the file.
771 $fio->{rxDigest} = File::RsyncP::Digest->new;
772 $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
773 my $attr = $fio->{rxLocalAttr};
774 if ( defined($attr) && defined(my $fh = BackupPC::FileZIO->open(
777 $attr->{compress})) ) {
779 while ( $fh->read(\$data, 4 * 65536) > 0 ) {
780 $fio->{rxDigest}->add($data);
786 $fio->log("$name got exact match")
787 if ( $fio->{logLevel} >= 5 );
789 close($fio->{rxInFd}) if ( defined($fio->{rxInFd}) );
790 unlink("$fio->{outDirSh}RStmp") if ( -f "$fio->{outDirSh}RStmp" );
791 my $newDigest = $fio->{rxDigest}->rsyncDigest;
792 if ( $fio->{logLevel} >= 3 ) {
793 my $md4Str = unpack("H*", $md4);
794 my $newStr = unpack("H*", $newDigest);
795 $fio->log("$name got digests $md4Str vs $newStr")
797 if ( $md4 ne $newDigest ) {
798 $fio->log("$name md4 doesn't match")
799 if ( $fio->{logLevel} >= 1 );
800 if ( defined($fio->{rxOutFd}) ) {
801 $fio->{rxOutFd}->close;
802 unlink($fio->{rxOutFile});
807 # One special case is an empty file: if the file size is
808 # zero we need to open the output file to create it.
810 if ( $fio->{rxFile}{size} == 0 ) {
811 my $rxOutFileRel = "$fio->{shareM}/"
812 . $fio->{bpc}->fileNameMangle($name);
813 my $rxOutFile = $fio->{outDir} . $rxOutFileRel;
814 $fio->{rxOutFd} = BackupPC::PoolWrite->new($fio->{bpc},
815 $rxOutFile, $fio->{rxFile}{size},
816 $fio->{xfer}{compress});
818 if ( !defined($fio->{rxOutFd}) ) {
820 # No output file, meaning original was an exact match.
822 $fio->log("$name: nothing to do")
823 if ( $fio->{logLevel} >= 5 );
824 my $attr = $fio->{rxLocalAttr};
825 my $f = $fio->{rxFile};
826 $fio->logFileAction("same", $f) if ( $fio->{logLevel} >= 1 );
828 || $attr->{type} != $f->{type}
829 || $attr->{mtime} != $f->{mtime}
830 || $attr->{size} != $f->{size}
831 || $attr->{gid} != $f->{gid}
832 || $attr->{mode} != $f->{mode} ) {
834 # In the full case, or if the attributes are different,
835 # we need to make a link from the previous file and
836 # set the attributes.
838 my $rxOutFile = $fio->{outDirSh}
839 . $fio->{bpc}->fileNameMangle($name);
840 if ( !link($attr->{fullPath}, $rxOutFile) ) {
841 $fio->log("Unable to link $attr->{fullPath} to $rxOutFile");
847 $fio->{stats}{TotalFileCnt}++;
848 $fio->{stats}{TotalFileSize} += $fio->{rxFile}{size};
849 $fio->{stats}{ExistFileCnt}++;
850 $fio->{stats}{ExistFileSize} += $fio->{rxFile}{size};
851 $fio->{stats}{ExistFileCompSize} += -s $rxOutFile;
855 if ( defined($fio->{rxOutFd}) ) {
856 my $exist = $fio->processClose($fio->{rxOutFd},
857 $fio->{rxOutFileRel},
858 $fio->{rxFile}{size}, 1);
859 $fio->logFileAction($exist ? "pool" : "create", $fio->{rxFile})
860 if ( $fio->{logLevel} >= 1 );
862 delete($fio->{rxDigest});
863 delete($fio->{rxInData});
869 my($fio, $name, $fList, $outputFunc) = @_;
872 (my $n = $name) =~ s/^\Q$fio->{localDir}/$fio->{remoteDir}/;
883 &$outputFunc($fList->encodeData);
888 my($fio, $flist, $outputFunc) = @_;
890 $fio->log("fileListSend not implemented!!");
891 $fio->{view}->find($fio->{lastBkupNum}, $fio->{xfer}{shareName},
892 $fio->{restoreFiles}, 1, \&fileListEltSend,
893 $flist, $outputFunc);
898 my($fio, $isChild) = @_;
901 # Flush the attributes if this is the child
903 $fio->attribWrite(undef)