1 #============================================================= -*-perl-*-
8 # Craig Barratt <cbarratt@users.sourceforge.net>
11 # Copyright (C) 2002-2003 Craig Barratt
13 #========================================================================
15 # Version 2.1.0_CVS, released 8 Feb 2004.
17 # See http://backuppc.sourceforge.net.
19 #========================================================================
21 package BackupPC::Xfer::RsyncFileIO;
25 use BackupPC::Attrib qw(:all);
27 use BackupPC::RsyncDigest;
28 use BackupPC::PoolWrite;
31 use constant S_IFMT => 0170000; # type of file
32 use constant S_IFDIR => 0040000; # directory
33 use constant S_IFCHR => 0020000; # character special
34 use constant S_IFBLK => 0060000; # block special
35 use constant S_IFREG => 0100000; # regular
36 use constant S_IFLNK => 0120000; # symbolic link
37 use constant S_IFSOCK => 0140000; # socket
38 use constant S_IFIFO => 0010000; # fifo
40 use vars qw( $RsyncLibOK );
43 eval "use File::RsyncP::Digest";
46 # Rsync module doesn't exist.
56 my($class, $options) = @_;
58 return if ( !$RsyncLibOK );
63 digest => File::RsyncP::Digest->new,
66 logHandler => \&logHandler,
73 ExistFileCompSize => 0,
78 $fio->{shareM} = $fio->{bpc}->fileNameEltMangle($fio->{share});
79 $fio->{outDir} = "$fio->{xfer}{outDir}/new/";
80 $fio->{outDirSh} = "$fio->{outDir}/$fio->{shareM}/";
81 $fio->{view} = BackupPC::View->new($fio->{bpc}, $fio->{client},
83 $fio->{full} = $fio->{xfer}{type} eq "full" ? 1 : 0;
84 $fio->{newFilesFH} = $fio->{xfer}{newFilesFH};
85 $fio->{partialNum} = undef if ( !$fio->{full} );
91 my($fio, $value) = @_;
93 $fio->{blockSize} = $value if ( defined($value) );
94 return $fio->{blockSize};
100 $fio->{logHandler} = $sub;
104 # Setup rsync checksum computation for the given file.
108 my($fio, $f, $needMD4, $defBlkSize) = @_;
110 my $attr = $fio->attribGet($f);
112 $fio->csumEnd if ( defined($fio->{csum}) );
113 return -1 if ( $attr->{type} != BPC_FTYPE_FILE );
114 (my $err, $fio->{csum}, my $blkSize)
115 = BackupPC::RsyncDigest->digestStart($attr->{fullPath}, $attr->{size},
116 0, $defBlkSize, $fio->{checksumSeed}, $needMD4,
117 $attr->{compress}, 1);
119 $fio->log("Can't get rsync digests from $attr->{fullPath}"
120 . " (err=$err, name=$f->{name})");
121 $fio->{stats}{errorCnt}++;
129 my($fio, $num, $csumLen, $blockSize) = @_;
134 return if ( !defined($fio->{csum}) );
135 return $fio->{csum}->digestGet($num, $csumLen);
142 return if ( !defined($fio->{csum}) );
143 return $fio->{csum}->digestEnd();
150 my $attr = $fio->attribGet($f);
152 $fio->readEnd if ( defined($fio->{fh}) );
153 if ( !defined($fio->{fh} = BackupPC::FileZIO->open($attr->{fullPath},
155 $attr->{compress})) ) {
156 $fio->log("Can't open $attr->{fullPath} (name=$f->{name})");
157 $fio->{stats}{errorCnt}++;
160 $fio->log("$f->{name}: opened for read") if ( $fio->{logLevel} >= 4 );
169 return if ( !defined($fio->{fh}) );
170 if ( $fio->{fh}->read(\$fileData, $num) <= 0 ) {
171 return $fio->readEnd;
173 $fio->log(sprintf("read returns %d bytes", length($fileData)))
174 if ( $fio->{logLevel} >= 8 );
182 return if ( !defined($fio->{fh}) );
184 $fio->log("closing $fio->{file}{name})") if ( $fio->{logLevel} >= 8 );
191 my($fio, $checksumSeed) = @_;
193 $fio->{checksumSeed} = $checksumSeed;
194 $fio->log("Checksum caching enabled (checksumSeed = $checksumSeed)")
195 if ( $fio->{logLevel} >= 1 && $checksumSeed == 32761 );
196 $fio->log("Checksum seed is $checksumSeed")
197 if ( $fio->{logLevel} >= 2 && $checksumSeed != 32761 );
202 my($fio, $localDir, $remoteDir) = @_;
204 $fio->{localDir} = $localDir;
205 $fio->{remoteDir} = $remoteDir;
210 my($fio, $share, $dir) = @_;
213 #$fio->log("viewCacheDir($share, $dir)");
214 if ( !defined($share) ) {
215 $share = $fio->{share};
216 $shareM = $fio->{shareM};
218 $shareM = $fio->{bpc}->fileNameEltMangle($share);
220 $shareM = "$shareM/$dir" if ( $dir ne "" );
221 return if ( defined($fio->{viewCache}{$shareM}) );
223 # purge old cache entries (ie: those that don't match the
224 # first part of $dir).
226 foreach my $d ( keys(%{$fio->{viewCache}}) ) {
227 delete($fio->{viewCache}{$d}) if ( $shareM !~ m{^\Q$d/} );
230 # fetch new directory attributes
232 $fio->{viewCache}{$shareM}
233 = $fio->{view}->dirAttrib($fio->{viewNum}, $share, $dir);
235 # also cache partial backup attrib data too
237 if ( defined($fio->{partialNum}) ) {
238 foreach my $d ( keys(%{$fio->{partialCache}}) ) {
239 delete($fio->{partialCache}{$d}) if ( $shareM !~ m{^\Q$d/} );
241 $fio->{partialCache}{$shareM}
242 = $fio->{view}->dirAttrib($fio->{partialNum}, $share, $dir);
249 my($dir, $fname, $share, $shareM);
252 $fname = "$fio->{xfer}{pathHdrSrc}/$fname"
253 if ( defined($fio->{xfer}{pathHdrSrc}) );
254 $fname =~ s{//+}{/}g;
255 if ( $fname =~ m{(.*)/(.*)} ) {
256 $shareM = $fio->{shareM};
259 } elsif ( $fname ne "." ) {
260 $shareM = $fio->{shareM};
266 $fname = $fio->{share};
268 $fio->viewCacheDir($share, $dir);
269 $shareM .= "/$dir" if ( $dir ne "" );
270 if ( defined(my $attr = $fio->{viewCache}{$shareM}{$fname}) ) {
272 } elsif ( defined(my $attr = $fio->{partialCache}{$shareM}{$fname}) ) {
283 my($attr) = $fio->attribGetWhere($f);
289 my($fio, $mode) = @_;
291 if ( ($mode & S_IFMT) == S_IFREG ) {
292 return BPC_FTYPE_FILE;
293 } elsif ( ($mode & S_IFMT) == S_IFDIR ) {
294 return BPC_FTYPE_DIR;
295 } elsif ( ($mode & S_IFMT) == S_IFLNK ) {
296 return BPC_FTYPE_SYMLINK;
297 } elsif ( ($mode & S_IFMT) == S_IFCHR ) {
298 return BPC_FTYPE_CHARDEV;
299 } elsif ( ($mode & S_IFMT) == S_IFBLK ) {
300 return BPC_FTYPE_BLOCKDEV;
301 } elsif ( ($mode & S_IFMT) == S_IFIFO ) {
302 return BPC_FTYPE_FIFO;
303 } elsif ( ($mode & S_IFMT) == S_IFSOCK ) {
304 return BPC_FTYPE_SOCKET;
306 return BPC_FTYPE_UNKNOWN;
311 # Set the attributes for a file. Returns non-zero on error.
315 my($fio, $f, $placeHolder) = @_;
318 if ( $f->{name} =~ m{(.*)/(.*)} ) {
320 $dir = "$fio->{shareM}/" . $1;
321 } elsif ( $f->{name} eq "." ) {
323 $file = $fio->{share};
325 $dir = $fio->{shareM};
329 if ( !defined($fio->{attribLastDir}) || $fio->{attribLastDir} ne $dir ) {
331 # Flush any directories that don't match the first part
332 # of the new directory
334 foreach my $d ( keys(%{$fio->{attrib}}) ) {
335 next if ( $d eq "" || "$dir/" =~ m{^\Q$d/} );
336 $fio->attribWrite($d);
338 $fio->{attribLastDir} = $dir;
340 if ( !exists($fio->{attrib}{$dir}) ) {
341 $fio->{attrib}{$dir} = BackupPC::Attrib->new({
342 compress => $fio->{xfer}{compress},
344 my $path = $fio->{outDir} . $dir;
345 if ( -f $fio->{attrib}{$dir}->fileName($path)
346 && !$fio->{attrib}{$dir}->read($path) ) {
347 $fio->log(sprintf("Unable to read attribute file %s",
348 $fio->{attrib}{$dir}->fileName($path)));
351 $fio->log("attribSet(dir=$dir, file=$file)") if ( $fio->{logLevel} >= 4 );
353 $fio->{attrib}{$dir}->set($file, {
354 type => $fio->mode2type($f->{mode}),
358 size => $placeHolder ? -1 : $f->{size},
359 mtime => $f->{mtime},
369 if ( !defined($d) ) {
371 # flush all entries (in reverse order)
373 foreach $d ( sort({$b cmp $a} keys(%{$fio->{attrib}})) ) {
374 $fio->attribWrite($d);
378 return if ( !defined($fio->{attrib}{$d}) );
380 # Set deleted files in the attributes. Any file in the view
381 # that doesn't have attributes is flagged as deleted for
382 # incremental dumps. All files sent by rsync have attributes
383 # temporarily set so we can do deletion detection. We also
384 # prune these temporary attributes.
390 $dir = $1 if ( $d =~ m{.+?/(.*)} );
391 $fio->viewCacheDir(undef, $dir);
392 ##print("attribWrite $d,$dir\n");
393 ##$Data::Dumper::Indent = 1;
394 ##$fio->log("attribWrite $d,$dir");
395 ##$fio->log("viewCacheLogKeys = ", keys(%{$fio->{viewCache}}));
396 ##$fio->log("attribKeys = ", keys(%{$fio->{attrib}}));
397 ##print "viewCache = ", Dumper($fio->{attrib});
398 ##print "attrib = ", Dumper($fio->{attrib});
399 if ( defined($fio->{viewCache}{$d}) ) {
400 foreach my $f ( keys(%{$fio->{viewCache}{$d}}) ) {
402 $name = "$1/$name" if ( $d =~ m{.*?/(.*)} );
403 if ( defined(my $a = $fio->{attrib}{$d}->get($f)) ) {
405 # delete temporary attributes (skipped files)
407 if ( $a->{size} < 0 ) {
408 $fio->{attrib}{$d}->set($f, undef);
409 $fio->logFileAction("skip", {
410 %{$fio->{viewCache}{$d}{$f}},
412 }) if ( $fio->{logLevel} >= 2 );
414 } elsif ( !$fio->{full} ) {
415 ##print("Delete file $f\n");
416 $fio->logFileAction("delete", {
417 %{$fio->{viewCache}{$d}{$f}},
419 }) if ( $fio->{logLevel} >= 1 );
420 $fio->{attrib}{$d}->set($f, {
421 type => BPC_FTYPE_DELETED,
432 if ( $fio->{attrib}{$d}->fileCount ) {
433 my $data = $fio->{attrib}{$d}->writeData;
436 $dirM = $1 . "/" . $fio->{bpc}->fileNameMangle($2)
437 if ( $dirM =~ m{(.*?)/(.*)} );
438 my $fileName = $fio->{attrib}{$d}->fileName("$fio->{outDir}$dirM");
439 $fio->log("attribWrite(dir=$d) -> $fileName")
440 if ( $fio->{logLevel} >= 4 );
441 my $poolWrite = BackupPC::PoolWrite->new($fio->{bpc}, $fileName,
442 length($data), $fio->{xfer}{compress});
443 $poolWrite->write(\$data);
444 $fio->processClose($poolWrite, $fio->{attrib}{$d}->fileName($dirM),
447 delete($fio->{attrib}{$d});
452 my($fio, $poolWrite, $fileName, $origSize, $doStats) = @_;
453 my($exists, $digest, $outSize, $errs) = $poolWrite->close;
455 $fileName =~ s{^/+}{};
456 $fio->log(@$errs) if ( defined($errs) && @$errs );
458 $fio->{stats}{TotalFileCnt}++;
459 $fio->{stats}{TotalFileSize} += $origSize;
463 $fio->{stats}{ExistFileCnt}++;
464 $fio->{stats}{ExistFileSize} += $origSize;
465 $fio->{stats}{ExistFileCompSize} += $outSize;
467 } elsif ( $outSize > 0 ) {
468 my $fh = $fio->{newFilesFH};
469 print($fh "$digest $origSize $fileName\n") if ( defined($fh) );
471 return $exists && $origSize > 0;
478 return $fio->{stats};
482 # Make a given directory. Returns non-zero on error.
487 my $name = $1 if ( $f->{name} =~ /(.*)/ );
490 if ( $name eq "." ) {
491 $path = $fio->{outDirSh};
493 $path = $fio->{outDirSh} . $fio->{bpc}->fileNameMangle($name);
495 $fio->logFileAction("create", $f) if ( $fio->{logLevel} >= 1 );
496 $fio->log("makePath($path, 0777)") if ( $fio->{logLevel} >= 5 );
497 $path = $1 if ( $path =~ /(.*)/ );
498 File::Path::mkpath($path, 0, 0777) if ( !-d $path );
499 return $fio->attribSet($f) if ( -d $path );
500 $fio->log("Can't create directory $path");
501 $fio->{stats}{errorCnt}++;
506 # Make a special file. Returns non-zero on error.
511 my $name = $1 if ( $f->{name} =~ /(.*)/ );
512 my $fNameM = $fio->{bpc}->fileNameMangle($name);
513 my $path = $fio->{outDirSh} . $fNameM;
514 my $attr = $fio->attribGet($f);
516 my $type = $fio->mode2type($f->{mode});
518 $fio->log("makeSpecial($path, $type, $f->{mode})")
519 if ( $fio->{logLevel} >= 5 );
520 if ( $type == BPC_FTYPE_CHARDEV || $type == BPC_FTYPE_BLOCKDEV ) {
521 my($major, $minor, $fh, $fileData);
523 $major = $f->{rdev} >> 8;
524 $minor = $f->{rdev} & 0xff;
525 $str = "$major,$minor";
526 } elsif ( ($f->{mode} & S_IFMT) == S_IFLNK ) {
530 # Now see if the file is different, or this is a full, in which
531 # case we create the new file.
536 || $attr->{type} != $fio->mode2type($f->{mode})
537 || $attr->{mtime} != $f->{mtime}
538 || $attr->{size} != $f->{size}
539 || $attr->{uid} != $f->{uid}
540 || $attr->{gid} != $f->{gid}
541 || $attr->{mode} != $f->{mode}
542 || !defined($fh = BackupPC::FileZIO->open($attr->{fullPath}, 0,
544 || $fh->read(\$fileData, length($str) + 1) != length($str)
545 || $fileData ne $str ) {
546 $fh->close if ( defined($fh) );
547 $fh = BackupPC::PoolWrite->new($fio->{bpc}, $path,
548 length($str), $fio->{xfer}{compress});
550 my $exist = $fio->processClose($fh, "$fio->{shareM}/$fNameM",
552 $fio->logFileAction($exist ? "pool" : "create", $f)
553 if ( $fio->{logLevel} >= 1 );
554 return $fio->attribSet($f);
556 $fio->logFileAction("skip", $f) if ( $fio->{logLevel} >= 2 );
558 $fh->close if ( defined($fh) );
563 my($fio, $path) = @_;
565 $fio->log("Unexpected call BackupPC::Xfer::RsyncFileIO->unlink($path)");
569 # Default log handler
575 print(STDERR $str, "\n");
579 # Handle one or more log messages
583 my($fio, @logStr) = @_;
585 foreach my $str ( @logStr ) {
586 next if ( $str eq "" );
587 $fio->{logHandler}($str);
592 # Generate a log file message for a completed file
596 my($fio, $action, $f) = @_;
597 my $owner = "$f->{uid}/$f->{gid}";
598 my $type = (("", "p", "c", "", "d", "", "b", "", "", "", "l", "", "s"))
599 [($f->{mode} & S_IFMT) >> 12];
601 $fio->log(sprintf(" %-6s %1s%4o %9s %11.0f %s",
611 # If there is a partial and we are doing a full, we do an incremental
612 # against the partial and a full against the rest. This subroutine
613 # is how we tell File::RsyncP which files to ignore attributes on
614 # (ie: against the partial dump we do consider the attributes, but
615 # otherwise we ignore attributes).
621 return if ( !defined($fio->{partialNum}) );
622 my($attr, $isPartial) = $fio->attribGetWhere($f);
623 $fio->log("$f->{name}: just checking attributes from partial")
624 if ( $isPartial && $fio->{logLevel} >= 5 );
629 # This is called by File::RsyncP when a file is skipped because the
634 my($fio, $f, $attr) = @_;
637 # Unless this is a partial, this is normal so ignore it.
639 return if ( !defined($fio->{partialNum}) );
641 $fio->log("$f->{name}: skipped in partial; adding link")
642 if ( $fio->{logLevel} >= 5 );
643 $fio->{rxLocalAttr} = $attr;
645 $fio->{rxSize} = $attr->{size};
646 delete($fio->{rxInFd});
647 delete($fio->{rxOutFd});
648 delete($fio->{rxDigest});
649 delete($fio->{rxInData});
650 return $fio->fileDeltaRxDone();
654 # Start receive of file deltas for a particular file.
658 my($fio, $f, $cnt, $size, $remainder) = @_;
660 $fio->{rxFile} = $f; # remote file attributes
661 $fio->{rxLocalAttr} = $fio->attribGet($f); # local file attributes
662 $fio->{rxBlkCnt} = $cnt; # how many blocks we will receive
663 $fio->{rxBlkSize} = $size; # block size
664 $fio->{rxRemainder} = $remainder; # size of the last block
665 $fio->{rxMatchBlk} = 0; # current start of match
666 $fio->{rxMatchNext} = 0; # current next block of match
667 $fio->{rxSize} = 0; # size of received file
668 my $rxSize = $cnt > 0 ? ($cnt - 1) * $size + $remainder : 0;
669 if ( $fio->{rxFile}{size} != $rxSize ) {
670 $fio->{rxMatchBlk} = undef; # size different, so no file match
671 $fio->log("$fio->{rxFile}{name}: size doesn't match"
672 . " ($fio->{rxFile}{size} vs $rxSize)")
673 if ( $fio->{logLevel} >= 5 );
675 delete($fio->{rxInFd});
676 delete($fio->{rxOutFd});
677 delete($fio->{rxDigest});
678 delete($fio->{rxInData});
682 # Process the next file delta for the current file. Returns 0 if ok,
683 # -1 if not. Must be called with either a block number, $blk, or new data,
684 # $newData, (not both) defined.
688 my($fio, $blk, $newData) = @_;
690 if ( defined($blk) ) {
691 if ( defined($fio->{rxMatchBlk}) && $fio->{rxMatchNext} == $blk ) {
693 # got the next block in order; just keep track.
695 $fio->{rxMatchNext}++;
699 my $newDataLen = length($newData);
700 $fio->log("$fio->{rxFile}{name}: blk=$blk, newData=$newDataLen, rxMatchBlk=$fio->{rxMatchBlk}, rxMatchNext=$fio->{rxMatchNext}")
701 if ( $fio->{logLevel} >= 8 );
702 if ( !defined($fio->{rxOutFd}) ) {
704 # maybe the file has no changes
706 if ( $fio->{rxMatchNext} == $fio->{rxBlkCnt}
707 && !defined($blk) && !defined($newData) ) {
708 #$fio->log("$fio->{rxFile}{name}: file is unchanged");
709 # if ( $fio->{logLevel} >= 8 );
714 # need to open an output file where we will build the
717 $fio->{rxFile}{name} =~ /(.*)/;
718 my $rxOutFileRel = "$fio->{shareM}/" . $fio->{bpc}->fileNameMangle($1);
719 my $rxOutFile = $fio->{outDir} . $rxOutFileRel;
720 $fio->{rxOutFd} = BackupPC::PoolWrite->new($fio->{bpc},
721 $rxOutFile, $fio->{rxFile}{size},
722 $fio->{xfer}{compress});
723 $fio->log("$fio->{rxFile}{name}: opening output file $rxOutFile")
724 if ( $fio->{logLevel} >= 9 );
725 $fio->{rxOutFile} = $rxOutFile;
726 $fio->{rxOutFileRel} = $rxOutFileRel;
727 $fio->{rxDigest} = File::RsyncP::Digest->new;
728 $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
730 if ( defined($fio->{rxMatchBlk})
731 && $fio->{rxMatchBlk} != $fio->{rxMatchNext} ) {
733 # Need to copy the sequence of blocks that matched. If the file
734 # is compressed we need to make a copy of the uncompressed file,
735 # since the compressed file is not seekable. Future optimizations
736 # could include only creating an uncompressed copy if the matching
737 # blocks were not monotonic, and to only do this if there are
738 # matching blocks (eg, maybe the entire file is new).
740 my $attr = $fio->{rxLocalAttr};
742 if ( !defined($fio->{rxInFd}) && !defined($fio->{rxInData}) ) {
743 if ( $attr->{compress} ) {
744 if ( !defined($fh = BackupPC::FileZIO->open(
747 $attr->{compress})) ) {
748 $fio->log("Can't open $attr->{fullPath}");
749 $fio->{stats}{errorCnt}++;
752 if ( $attr->{size} < 16 * 1024 * 1024 ) {
754 # Cache the entire old file if it is less than 16MB
757 $fio->{rxInData} = "";
758 while ( $fh->read(\$data, 16 * 1024 * 1024) > 0 ) {
759 $fio->{rxInData} .= $data;
761 $fio->log("$attr->{fullPath}: cached all $attr->{size}"
763 if ( $fio->{logLevel} >= 9 );
766 # Create and write a temporary output file
768 unlink("$fio->{outDirSh}RStmp")
769 if ( -f "$fio->{outDirSh}RStmp" );
770 if ( open(F, "+>", "$fio->{outDirSh}RStmp") ) {
774 while ( $fh->read(\$data, 1024 * 1024) > 0 ) {
775 if ( syswrite(F, $data) != length($data) ) {
776 $fio->log(sprintf("Can't write len=%d to %s",
777 length($data) , "$fio->{outDirSh}RStmp"));
779 $fio->{stats}{errorCnt}++;
782 $byteCnt += length($data);
785 $fio->{rxInName} = "$fio->{outDirSh}RStmp";
786 sysseek($fio->{rxInFd}, 0, 0);
787 $fio->log("$attr->{fullPath}: copied $byteCnt,"
788 . "$attr->{size} bytes to $fio->{rxInName}")
789 if ( $fio->{logLevel} >= 9 );
791 $fio->log("Unable to open $fio->{outDirSh}RStmp");
793 $fio->{stats}{errorCnt}++;
799 if ( open(F, "<", $attr->{fullPath}) ) {
802 $fio->{rxInName} = $attr->{fullPath};
804 $fio->log("Unable to open $attr->{fullPath}");
805 $fio->{stats}{errorCnt}++;
810 my $lastBlk = $fio->{rxMatchNext} - 1;
811 $fio->log("$fio->{rxFile}{name}: writing blocks $fio->{rxMatchBlk}.."
813 if ( $fio->{logLevel} >= 9 );
814 my $seekPosn = $fio->{rxMatchBlk} * $fio->{rxBlkSize};
815 if ( defined($fio->{rxInFd})
816 && !sysseek($fio->{rxInFd}, $seekPosn, 0) ) {
817 $fio->log("Unable to seek $attr->{rxInName} to $seekPosn");
818 $fio->{stats}{errorCnt}++;
821 my $cnt = $fio->{rxMatchNext} - $fio->{rxMatchBlk};
822 my($thisCnt, $len, $data);
823 for ( my $i = 0 ; $i < $cnt ; $i += $thisCnt ) {
824 $thisCnt = $cnt - $i;
825 $thisCnt = 512 if ( $thisCnt > 512 );
826 if ( $fio->{rxMatchBlk} + $i + $thisCnt == $fio->{rxBlkCnt} ) {
827 $len = ($thisCnt - 1) * $fio->{rxBlkSize} + $fio->{rxRemainder};
829 $len = $thisCnt * $fio->{rxBlkSize};
831 if ( defined($fio->{rxInData}) ) {
832 $data = substr($fio->{rxInData}, $seekPosn, $len);
835 my $got = sysread($fio->{rxInFd}, $data, $len);
836 if ( $got != $len ) {
837 my $inFileSize = -s $fio->{rxInName};
838 $fio->log("Unable to read $len bytes from $fio->{rxInName}"
839 . " got=$got, seekPosn=$seekPosn"
840 . " ($i,$thisCnt,$fio->{rxBlkCnt},$inFileSize"
841 . ",$attr->{size})");
842 $fio->{stats}{errorCnt}++;
847 $fio->{rxOutFd}->write(\$data);
848 $fio->{rxDigest}->add($data);
849 $fio->{rxSize} += length($data);
851 $fio->{rxMatchBlk} = undef;
853 if ( defined($blk) ) {
855 # Remember the new block number
857 $fio->{rxMatchBlk} = $blk;
858 $fio->{rxMatchNext} = $blk + 1;
860 if ( defined($newData) ) {
862 # Write the new chunk
864 my $len = length($newData);
865 $fio->log("$fio->{rxFile}{name}: writing $len bytes new data")
866 if ( $fio->{logLevel} >= 9 );
867 $fio->{rxOutFd}->write(\$newData);
868 $fio->{rxDigest}->add($newData);
869 $fio->{rxSize} += length($newData);
874 # Finish up the current receive file. Returns undef if ok, -1 if not.
875 # Returns 1 if the md4 digest doesn't match.
880 my $name = $1 if ( $fio->{rxFile}{name} =~ /(.*)/ );
882 close($fio->{rxInFd}) if ( defined($fio->{rxInFd}) );
883 unlink("$fio->{outDirSh}RStmp") if ( -f "$fio->{outDirSh}RStmp" );
886 # Check the final md4 digest
888 if ( defined($md4) ) {
890 if ( !defined($fio->{rxDigest}) ) {
892 # File was exact match, but we still need to verify the
893 # MD4 checksum. Compute the md4 digest (or fetch the
896 if ( defined(my $attr = $fio->{rxLocalAttr}) ) {
898 # block size doesn't matter: we're only going to
899 # fetch the md4 file digest, not the block digests.
901 my($err, $csum, $blkSize)
902 = BackupPC::RsyncDigest->digestStart(
903 $attr->{fullPath}, $attr->{size},
904 0, 2048, $fio->{checksumSeed}, 1,
907 $fio->log("Can't open $attr->{fullPath} for MD4"
908 . " check (err=$err, $name)");
909 $fio->{stats}{errorCnt}++;
911 $newDigest = $csum->digestEnd;
913 $fio->{rxSize} = $attr->{size};
916 # Empty file; just create an empty file digest
918 $fio->{rxDigest} = File::RsyncP::Digest->new;
919 $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
920 $newDigest = $fio->{rxDigest}->digest;
922 $fio->log("$name got exact match") if ( $fio->{logLevel} >= 5 );
924 $newDigest = $fio->{rxDigest}->digest;
926 if ( $fio->{logLevel} >= 3 ) {
927 my $md4Str = unpack("H*", $md4);
928 my $newStr = unpack("H*", $newDigest);
929 $fio->log("$name got digests $md4Str vs $newStr")
931 if ( $md4 ne $newDigest ) {
932 $fio->log("$name: fatal error: md4 doesn't match");
933 $fio->{stats}{errorCnt}++;
934 if ( defined($fio->{rxOutFd}) ) {
935 $fio->{rxOutFd}->close;
936 unlink($fio->{rxOutFile});
943 # One special case is an empty file: if the file size is
944 # zero we need to open the output file to create it.
946 if ( $fio->{rxSize} == 0 ) {
947 my $rxOutFileRel = "$fio->{shareM}/"
948 . $fio->{bpc}->fileNameMangle($name);
949 my $rxOutFile = $fio->{outDir} . $rxOutFileRel;
950 $fio->{rxOutFd} = BackupPC::PoolWrite->new($fio->{bpc},
951 $rxOutFile, $fio->{rxSize},
952 $fio->{xfer}{compress});
954 if ( !defined($fio->{rxOutFd}) ) {
956 # No output file, meaning original was an exact match.
958 $fio->log("$name: nothing to do")
959 if ( $fio->{logLevel} >= 5 );
960 my $attr = $fio->{rxLocalAttr};
961 my $f = $fio->{rxFile};
962 $fio->logFileAction("same", $f) if ( $fio->{logLevel} >= 1 );
964 || $attr->{type} != $f->{type}
965 || $attr->{mtime} != $f->{mtime}
966 || $attr->{size} != $f->{size}
967 || $attr->{gid} != $f->{gid}
968 || $attr->{mode} != $f->{mode} ) {
970 # In the full case, or if the attributes are different,
971 # we need to make a link from the previous file and
972 # set the attributes.
974 my $rxOutFile = $fio->{outDirSh}
975 . $fio->{bpc}->fileNameMangle($name);
976 if ( !link($attr->{fullPath}, $rxOutFile) ) {
977 $fio->log("Unable to link $attr->{fullPath} to $rxOutFile");
978 $fio->{stats}{errorCnt}++;
984 $fio->{stats}{TotalFileCnt}++;
985 $fio->{stats}{TotalFileSize} += $fio->{rxSize};
986 $fio->{stats}{ExistFileCnt}++;
987 $fio->{stats}{ExistFileSize} += $fio->{rxSize};
988 $fio->{stats}{ExistFileCompSize} += -s $rxOutFile;
989 $fio->{rxFile}{size} = $fio->{rxSize};
990 return $fio->attribSet($fio->{rxFile});
993 if ( defined($fio->{rxOutFd}) ) {
994 my $exist = $fio->processClose($fio->{rxOutFd},
995 $fio->{rxOutFileRel},
997 $fio->logFileAction($exist ? "pool" : "create", $fio->{rxFile})
998 if ( $fio->{logLevel} >= 1 );
999 $fio->{rxFile}{size} = $fio->{rxSize};
1000 return $fio->attribSet($fio->{rxFile});
1002 delete($fio->{rxDigest});
1003 delete($fio->{rxInData});
1008 # Callback function for BackupPC::View->find. Note the order of the
1009 # first two arguments.
1013 my($a, $fio, $fList, $outputFunc) = @_;
1014 my $name = $a->{relPath};
1016 my $type = $fio->mode2type($a->{mode});
1017 my $extraAttribs = {};
1019 $n =~ s/^\Q$fio->{xfer}{pathHdrSrc}//;
1020 $fio->log("Sending $name (remote=$n)") if ( $fio->{logLevel} >= 4 );
1021 if ( $type == BPC_FTYPE_CHARDEV
1022 || $type == BPC_FTYPE_BLOCKDEV
1023 || $type == BPC_FTYPE_SYMLINK ) {
1024 my $fh = BackupPC::FileZIO->open($a->{fullPath}, 0, $a->{compress});
1026 if ( defined($fh) ) {
1027 $rdSize = $fh->read(\$str, $a->{size} + 1024);
1028 if ( $type == BPC_FTYPE_SYMLINK ) {
1030 # Reconstruct symbolic link
1032 $extraAttribs = { link => $str };
1033 if ( $rdSize != $a->{size} ) {
1035 $fio->log("$name: can't read exactly $a->{size} bytes");
1036 $fio->{stats}{errorCnt}++;
1038 } elsif ( $str =~ /(\d*),(\d*)/ ) {
1040 # Reconstruct char or block special major/minor device num
1042 # Note: char/block devices have $a->{size} = 0, so we
1043 # can't do an error check on $rdSize.
1045 $extraAttribs = { rdev => $1 * 256 + $2 };
1047 $fio->log("$name: unexpected special file contents $str");
1048 $fio->{stats}{errorCnt}++;
1053 $fio->log("$name: can't open");
1054 $fio->{stats}{errorCnt}++;
1059 #dev => 0, # later, when we support hardlinks
1060 #inode => 0, # later, when we support hardlinks
1064 mtime => $a->{mtime},
1069 $f->{name} = "$fio->{xfer}{pathHdrDest}/$f->{name}";
1070 $f->{name} =~ s{//+}{/}g;
1071 $fio->logFileAction("restore", $f) if ( $fio->{logLevel} >= 1 );
1072 &$outputFunc($fList->encodeData);
1076 if ( $type != BPC_FTYPE_DIR ) {
1077 $fio->{stats}{TotalFileCnt}++;
1078 $fio->{stats}{TotalFileSize} += $a->{size};
1084 my($fio, $flist, $outputFunc) = @_;
1087 # Populate the file list with the files requested by the user.
1088 # Since some might be directories so we call BackupPC::View::find.
1090 $fio->log("fileListSend: sending file list: "
1091 . join(" ", @{$fio->{fileList}})) if ( $fio->{logLevel} >= 4 );
1092 foreach my $name ( @{$fio->{fileList}} ) {
1093 $fio->{view}->find($fio->{xfer}{bkupSrcNum},
1094 $fio->{xfer}{bkupSrcShare},
1096 \&fileListEltSend, $fio, $flist, $outputFunc);
1102 my($fio, $isChild) = @_;
1105 # Flush the attributes if this is the child
1107 $fio->attribWrite(undef);
1113 # join('',@_), kill 0;