ef736e116d8a75b366be474255ed80c8939617a2
[BackupPC.git] / lib / BackupPC / Xfer / RsyncFileIO.pm
1 #============================================================= -*-perl-*-
2 #
3 # Rsync package
4 #
5 # DESCRIPTION
6 #
7 # AUTHOR
8 #   Craig Barratt  <cbarratt@users.sourceforge.net>
9 #
10 # COPYRIGHT
11 #   Copyright (C) 2002-2003  Craig Barratt
12 #
13 #========================================================================
14 #
15 # Version 2.1.0, released 20 Jun 2004.
16 #
17 # See http://backuppc.sourceforge.net.
18 #
19 #========================================================================
20
21 package BackupPC::Xfer::RsyncFileIO;
22
23 use strict;
24 use File::Path;
25 use Encode qw/from_to/;
26 use BackupPC::Attrib qw(:all);
27 use BackupPC::View;
28 use BackupPC::Xfer::RsyncDigest qw(:all);
29 use BackupPC::PoolWrite;
30
31 use constant S_HLINK_TARGET => 0400000;    # this file is hardlink target
32 use constant S_IFMT         => 0170000;    # type of file
33 use constant S_IFDIR        => 0040000;    # directory
34 use constant S_IFCHR        => 0020000;    # character special
35 use constant S_IFBLK        => 0060000;    # block special
36 use constant S_IFREG        => 0100000;    # regular
37 use constant S_IFLNK        => 0120000;    # symbolic link
38 use constant S_IFSOCK       => 0140000;    # socket
39 use constant S_IFIFO        => 0010000;    # fifo
40
41 use vars qw( $RsyncLibOK );
42
43 BEGIN {
44     eval "use File::RsyncP::Digest";
45     if ( $@ ) {
46         #
47         # Rsync module doesn't exist.
48         #
49         $RsyncLibOK = 0;
50     } else {
51         $RsyncLibOK = 1;
52     }
53 };
54
55 sub new
56 {
57     my($class, $options) = @_;
58
59     return if ( !$RsyncLibOK );
60     $options ||= {};
61     my $fio = bless {
62         blockSize    => 700,
63         logLevel     => 0,
64         digest       => File::RsyncP::Digest->new($options->{protocol_version}),
65         checksumSeed => 0,
66         attrib       => {},
67         logHandler   => \&logHandler,
68         stats        => {
69             errorCnt          => 0,
70             TotalFileCnt      => 0,
71             TotalFileSize     => 0,
72             ExistFileCnt      => 0,
73             ExistFileSize     => 0,
74             ExistFileCompSize => 0,
75         },
76         %$options,
77     }, $class;
78
79     $fio->{shareM}   = $fio->{bpc}->fileNameEltMangle($fio->{share});
80     $fio->{outDir}   = "$fio->{xfer}{outDir}/new/";
81     $fio->{outDirSh} = "$fio->{outDir}/$fio->{shareM}/";
82     $fio->{view}     = BackupPC::View->new($fio->{bpc}, $fio->{client},
83                                          $fio->{backups});
84     $fio->{full}     = $fio->{xfer}{type} eq "full" ? 1 : 0;
85     $fio->{newFilesFH} = $fio->{xfer}{newFilesFH};
86     $fio->{partialNum} = undef if ( !$fio->{full} );
87     return $fio;
88 }
89
90 #
91 # We publish our version to File::RsyncP.  This is so File::RsyncP
92 # can provide backward compatibility to older FileIO code.
93 #
94 # Versions:
95 #
96 #   undef or 1:  protocol version 26, no hardlinks
97 #   2:           protocol version 28, supports hardlinks
98 #
99 sub version
100 {
101     return 2;
102 }
103
104 sub blockSize
105 {
106     my($fio, $value) = @_;
107
108     $fio->{blockSize} = $value if ( defined($value) );
109     return $fio->{blockSize};
110 }
111
112 sub protocol_version
113 {
114     my($fio, $value) = @_;
115
116     if ( defined($value) ) {
117         $fio->{protocol_version} = $value;
118         $fio->{digest}->protocol($fio->{protocol_version});
119     }
120     return $fio->{protocol_version};
121 }
122
123 sub preserve_hard_links
124 {
125     my($fio, $value) = @_;
126
127     $fio->{preserve_hard_links} = $value if ( defined($value) );
128     return $fio->{preserve_hard_links};
129 }
130
131 sub logHandlerSet
132 {
133     my($fio, $sub) = @_;
134     $fio->{logHandler} = $sub;
135     BackupPC::Xfer::RsyncDigest->logHandlerSet($sub);
136 }
137
138 #
139 # Setup rsync checksum computation for the given file.
140 #
141 sub csumStart
142 {
143     my($fio, $f, $needMD4, $defBlkSize, $phase) = @_;
144
145     $defBlkSize ||= $fio->{blockSize};
146     my $attr = $fio->attribGet($f, 1);
147     $fio->{file} = $f;
148     $fio->csumEnd if ( defined($fio->{csum}) );
149     return -1 if ( $attr->{type} != BPC_FTYPE_FILE );
150
151     #
152     # Rsync uses short checksums on the first phase.  If the whole-file
153     # checksum fails, then the file is repeated with full checksums.
154     # So on phase 2 we verify the checksums if they are cached.
155     #
156     if ( ($phase > 0 || rand(1) < $fio->{cacheCheckProb})
157             && $attr->{compress}
158             && $fio->{checksumSeed} == RSYNC_CSUMSEED_CACHE ) {
159         my($err, $d, $blkSize) = BackupPC::Xfer::RsyncDigest->digestStart(
160                                      $attr->{fullPath}, $attr->{size}, 0,
161                                      $defBlkSize, $fio->{checksumSeed},
162                                      0, $attr->{compress}, 0);
163         my($isCached, $isInvalid) = $d->isCached;
164         if ( $fio->{logLevel} >= 5 ) {
165             $fio->log("$attr->{fullPath} verify; cached = $isCached,"
166                     . " invalid = $isInvalid, phase = $phase");
167         }
168         if ( $isCached || $isInvalid ) {
169             my $ret = BackupPC::Xfer::RsyncDigest->digestAdd(
170                             $attr->{fullPath}, $blkSize,
171                             $fio->{checksumSeed}, 1        # verify
172                         );
173             if ( $ret != 1 ) {
174                 $fio->log("Bad cached digest for $attr->{fullPath} ($ret);"
175                         . " fixed");
176                 $fio->{stats}{errorCnt}++;
177             } else {
178                 $fio->log("$f->{name}: verified cached digest")
179                                     if ( $fio->{logLevel} >= 2 );
180             }
181         }
182         $d->digestEnd;
183     }
184     (my $err, $fio->{csum}, my $blkSize)
185          = BackupPC::Xfer::RsyncDigest->digestStart($attr->{fullPath},
186                          $attr->{size}, 0, $defBlkSize, $fio->{checksumSeed},
187                          $needMD4, $attr->{compress}, 1);
188     if ( $fio->{logLevel} >= 5 ) {
189         my($isCached, $invalid) = $fio->{csum}->isCached;
190         $fio->log("$attr->{fullPath} cache = $isCached,"
191                 . " invalid = $invalid, phase = $phase");
192     }
193     if ( $err ) {
194         $fio->log("Can't get rsync digests from $attr->{fullPath}"
195                 . " (err=$err, name=$f->{name})");
196         $fio->{stats}{errorCnt}++;
197         return -1;
198     }
199     return $blkSize;
200 }
201
202 sub csumGet
203 {
204     my($fio, $num, $csumLen, $blockSize) = @_;
205     my($fileData);
206
207     $num     ||= 100;
208     $csumLen ||= 16;
209     return if ( !defined($fio->{csum}) );
210     return $fio->{csum}->digestGet($num, $csumLen);
211 }
212
213 sub csumEnd
214 {
215     my($fio) = @_;
216
217     return if ( !defined($fio->{csum}) );
218     return $fio->{csum}->digestEnd();
219 }
220
221 sub readStart
222 {
223     my($fio, $f) = @_;
224
225     my $attr = $fio->attribGet($f, 1);
226     $fio->{file} = $f;
227     $fio->readEnd if ( defined($fio->{fh}) );
228     if ( !defined($fio->{fh} = BackupPC::FileZIO->open($attr->{fullPath},
229                                            0,
230                                            $attr->{compress})) ) {
231         $fio->log("Can't open $attr->{fullPath} (name=$f->{name})");
232         $fio->{stats}{errorCnt}++;
233         return;
234     }
235     $fio->log("$f->{name}: opened for read") if ( $fio->{logLevel} >= 4 );
236 }
237
238 sub read
239 {
240     my($fio, $num) = @_;
241     my $fileData;
242
243     $num ||= 32768;
244     return if ( !defined($fio->{fh}) );
245     if ( $fio->{fh}->read(\$fileData, $num) <= 0 ) {
246         return $fio->readEnd;
247     }
248     $fio->log(sprintf("read returns %d bytes", length($fileData)))
249                                 if ( $fio->{logLevel} >= 8 );
250     return \$fileData;
251 }
252
253 sub readEnd
254 {
255     my($fio) = @_;
256
257     return if ( !defined($fio->{fh}) );
258     $fio->{fh}->close;
259     $fio->log("closing $fio->{file}{name})") if ( $fio->{logLevel} >= 8 );
260     delete($fio->{fh});
261     return;
262 }
263
264 sub checksumSeed
265 {
266     my($fio, $checksumSeed) = @_;
267
268     $fio->{checksumSeed} = $checksumSeed;
269     $fio->log("Checksum caching enabled (checksumSeed = $checksumSeed)")
270         if ( $fio->{logLevel} >= 1 && $checksumSeed == RSYNC_CSUMSEED_CACHE );
271     $fio->log("Checksum seed is $checksumSeed")
272         if ( $fio->{logLevel} >= 2 && $checksumSeed != RSYNC_CSUMSEED_CACHE );
273 }
274
275 sub dirs
276 {
277     my($fio, $localDir, $remoteDir) = @_;
278
279     $fio->{localDir}  = $localDir;
280     $fio->{remoteDir} = $remoteDir;
281 }
282
283 sub viewCacheDir
284 {
285     my($fio, $share, $dir) = @_;
286     my $shareM;
287
288     #$fio->log("viewCacheDir($share, $dir)");
289     if ( !defined($share) ) {
290         $share  = $fio->{share};
291         $shareM = $fio->{shareM};
292     } else {
293         $shareM = $fio->{bpc}->fileNameEltMangle($share);
294     }
295     $shareM = "$shareM/$dir" if ( $dir ne "" );
296     return if ( defined($fio->{viewCache}{$shareM}) );
297     #
298     # purge old cache entries (ie: those that don't match the
299     # first part of $dir).
300     #
301     foreach my $d ( keys(%{$fio->{viewCache}}) ) {
302         delete($fio->{viewCache}{$d}) if ( $shareM !~ m{^\Q$d/} );
303     }
304     #
305     # fetch new directory attributes
306     #
307     $fio->{viewCache}{$shareM}
308                 = $fio->{view}->dirAttrib($fio->{viewNum}, $share, $dir);
309     #
310     # also cache partial backup attrib data too
311     #
312     if ( defined($fio->{partialNum}) ) {
313         foreach my $d ( keys(%{$fio->{partialCache}}) ) {
314             delete($fio->{partialCache}{$d}) if ( $shareM !~ m{^\Q$d/} );
315         }
316         $fio->{partialCache}{$shareM}
317                     = $fio->{view}->dirAttrib($fio->{partialNum}, $share, $dir);
318     }
319 }
320
321 sub attribGetWhere
322 {
323     my($fio, $f) = @_;
324     my($dir, $fname, $share, $shareM);
325
326     $fname = $f->{name};
327     $fname = "$fio->{xfer}{pathHdrSrc}/$fname"
328                        if ( defined($fio->{xfer}{pathHdrSrc}) );
329     $fname =~ s{//+}{/}g;
330     if ( $fname =~ m{(.*)/(.*)} ) {
331         $shareM = $fio->{shareM};
332         $dir = $1;
333         $fname = $2;
334     } elsif ( $fname ne "." ) {
335         $shareM = $fio->{shareM};
336         $dir = "";
337     } else {
338         $share = "";
339         $shareM = "";
340         $dir = "";
341         $fname = $fio->{share};
342     }
343     $fio->viewCacheDir($share, $dir);
344     $shareM .= "/$dir" if ( $dir ne "" );
345     if ( defined(my $attr = $fio->{viewCache}{$shareM}{$fname}) ) {
346         return ($attr, 0);
347     } elsif ( defined(my $attr = $fio->{partialCache}{$shareM}{$fname}) ) {
348         return ($attr, 1);
349     } else {
350         return;
351     }
352 }
353
354 sub attribGet
355 {
356     my($fio, $f, $doHardLink) = @_;
357
358     my($attr) = $fio->attribGetWhere($f);
359     if ( $doHardLink && $attr->{type} == BPC_FTYPE_HARDLINK ) {
360         $fio->log("$attr->{fullPath}: opening for hardlink read"
361                 . " (name = $f->{name})") if ( $fio->{logLevel} >= 4 );
362         my $fh = BackupPC::FileZIO->open($attr->{fullPath}, 0,
363                                          $attr->{compress});
364         my $target;
365         if ( defined($fh) ) {
366             $fh->read(\$target,  65536);
367             $fh->close;
368             $target =~ s/^\.?\/+//;
369         } else {
370             $fio->log("$attr->{fullPath}: can't open for hardlink read");
371             $fio->{stats}{errorCnt}++;
372             $attr->{type} = BPC_FTYPE_FILE;
373             return $attr;
374         }
375         $target = "/$target" if ( $target !~ /^\// );
376         $fio->log("$attr->{fullPath}: redirecting to $target (will trim "
377                 . "$fio->{xfer}{pathHdrSrc})") if ( $fio->{logLevel} >= 4 );
378         $target =~ s/^\Q$fio->{xfer}{pathHdrSrc}//;
379         $f->{name} = $target;
380         $attr = $fio->attribGet($f);
381         $fio->log(" ... now got $attr->{fullPath}")
382                             if ( $fio->{logLevel} >= 4 );
383     }
384     return $attr;
385 }
386
387 sub mode2type
388 {
389     my($fio, $f) = @_;
390     my $mode = $f->{mode};
391
392     if ( ($mode & S_IFMT) == S_IFREG ) {
393         if ( defined($f->{hlink}) && !$f->{hlink_self} ) {
394             return BPC_FTYPE_HARDLINK;
395         } else {
396             return BPC_FTYPE_FILE;
397         }
398     } elsif ( ($mode & S_IFMT) == S_IFDIR ) {
399         return BPC_FTYPE_DIR;
400     } elsif ( ($mode & S_IFMT) == S_IFLNK ) {
401         return BPC_FTYPE_SYMLINK;
402     } elsif ( ($mode & S_IFMT) == S_IFCHR ) {
403         return BPC_FTYPE_CHARDEV;
404     } elsif ( ($mode & S_IFMT) == S_IFBLK ) {
405         return BPC_FTYPE_BLOCKDEV;
406     } elsif ( ($mode & S_IFMT) == S_IFIFO ) {
407         return BPC_FTYPE_FIFO;
408     } elsif ( ($mode & S_IFMT) == S_IFSOCK ) {
409         return BPC_FTYPE_SOCKET;
410     } else {
411         return BPC_FTYPE_UNKNOWN;
412     }
413 }
414
415 #
416 # Set the attributes for a file.  Returns non-zero on error.
417 #
418 sub attribSet
419 {
420     my($fio, $f, $placeHolder) = @_;
421     my($dir, $file);
422
423     if ( $f->{name} =~ m{(.*)/(.*)} ) {
424         $file = $2;
425         $dir  = "$fio->{shareM}/" . $1;
426     } elsif ( $f->{name} eq "." ) {
427         $dir  = "";
428         $file = $fio->{share};
429     } else {
430         $dir  = $fio->{shareM};
431         $file = $f->{name};
432     }
433
434     if ( !defined($fio->{attribLastDir}) || $fio->{attribLastDir} ne $dir ) {
435         #
436         # Flush any directories that don't match the first part
437         # of the new directory
438         #
439         foreach my $d ( keys(%{$fio->{attrib}}) ) {
440             next if ( $d eq "" || "$dir/" =~ m{^\Q$d/} );
441             $fio->attribWrite($d);
442         }
443         $fio->{attribLastDir} = $dir;
444     }
445     if ( !exists($fio->{attrib}{$dir}) ) {
446         $fio->{attrib}{$dir} = BackupPC::Attrib->new({
447                                      compress => $fio->{xfer}{compress},
448                                 });
449         my $path = $fio->{outDir} . $dir;
450         if ( -f $fio->{attrib}{$dir}->fileName($path)
451                     && !$fio->{attrib}{$dir}->read($path) ) {
452             $fio->log(sprintf("Unable to read attribute file %s",
453                             $fio->{attrib}{$dir}->fileName($path)));
454         }
455     }
456     $fio->log("attribSet(dir=$dir, file=$file)") if ( $fio->{logLevel} >= 4 );
457
458     my $mode = $f->{mode};
459
460     $mode |= S_HLINK_TARGET if ( $f->{hlink_self} );
461     $fio->{attrib}{$dir}->set($file, {
462                             type  => $fio->mode2type($f),
463                             mode  => $mode,
464                             uid   => $f->{uid},
465                             gid   => $f->{gid},
466                             size  => $placeHolder ? -1 : $f->{size},
467                             mtime => $f->{mtime},
468                        });
469     return;
470 }
471
472 sub attribWrite
473 {
474     my($fio, $d) = @_;
475     my($poolWrite);
476
477     if ( !defined($d) ) {
478         #
479         # flush all entries (in reverse order)
480         #
481         foreach $d ( sort({$b cmp $a} keys(%{$fio->{attrib}})) ) {
482             $fio->attribWrite($d);
483         }
484         return;
485     }
486     return if ( !defined($fio->{attrib}{$d}) );
487     #
488     # Set deleted files in the attributes.  Any file in the view
489     # that doesn't have attributes is flagged as deleted for
490     # incremental dumps.  All files sent by rsync have attributes
491     # temporarily set so we can do deletion detection.  We also
492     # prune these temporary attributes.
493     #
494     if ( $d ne "" ) {
495         my $dir;
496         my $share;
497
498         $dir = $1 if ( $d =~ m{.+?/(.*)} );
499         $fio->viewCacheDir(undef, $dir);
500         ##print("attribWrite $d,$dir\n");
501         ##$Data::Dumper::Indent = 1;
502         ##$fio->log("attribWrite $d,$dir");
503         ##$fio->log("viewCacheLogKeys = ", keys(%{$fio->{viewCache}}));
504         ##$fio->log("attribKeys = ", keys(%{$fio->{attrib}}));
505         ##print "viewCache = ", Dumper($fio->{attrib});
506         ##print "attrib = ", Dumper($fio->{attrib});
507         if ( defined($fio->{viewCache}{$d}) ) {
508             foreach my $f ( keys(%{$fio->{viewCache}{$d}}) ) {
509                 my $name = $f;
510                 $name = "$1/$name" if ( $d =~ m{.*?/(.*)} );
511                 if ( defined(my $a = $fio->{attrib}{$d}->get($f)) ) {
512                     #
513                     # delete temporary attributes (skipped files)
514                     #
515                     if ( $a->{size} < 0 ) {
516                         $fio->{attrib}{$d}->set($f, undef);
517                         $fio->logFileAction("skip", {
518                                     %{$fio->{viewCache}{$d}{$f}},
519                                     name => $name,
520                                 }) if ( $fio->{logLevel} >= 2 );
521                     }
522                 } elsif ( !$fio->{full} ) {
523                     ##print("Delete file $f\n");
524                     $fio->logFileAction("delete", {
525                                 %{$fio->{viewCache}{$d}{$f}},
526                                 name => $name,
527                             }) if ( $fio->{logLevel} >= 1 );
528                     $fio->{attrib}{$d}->set($f, {
529                                     type  => BPC_FTYPE_DELETED,
530                                     mode  => 0,
531                                     uid   => 0,
532                                     gid   => 0,
533                                     size  => 0,
534                                     mtime => 0,
535                                });
536                 }
537             }
538         }
539     }
540     if ( $fio->{attrib}{$d}->fileCount ) {
541         my $data = $fio->{attrib}{$d}->writeData;
542         my $dirM = $d;
543
544         $dirM = $1 . "/" . $fio->{bpc}->fileNameMangle($2)
545                         if ( $dirM =~ m{(.*?)/(.*)} );
546         my $fileName = $fio->{attrib}{$d}->fileName("$fio->{outDir}$dirM");
547         $fio->log("attribWrite(dir=$d) -> $fileName")
548                                 if ( $fio->{logLevel} >= 4 );
549         my $poolWrite = BackupPC::PoolWrite->new($fio->{bpc}, $fileName,
550                                      length($data), $fio->{xfer}{compress});
551         $poolWrite->write(\$data);
552         $fio->processClose($poolWrite, $fio->{attrib}{$d}->fileName($dirM),
553                            length($data), 0);
554     }
555     delete($fio->{attrib}{$d});
556 }
557
558 sub processClose
559 {
560     my($fio, $poolWrite, $fileName, $origSize, $doStats) = @_;
561     my($exists, $digest, $outSize, $errs) = $poolWrite->close;
562
563     $fileName =~ s{^/+}{};
564     $fio->log(@$errs) if ( defined($errs) && @$errs );
565     if ( $doStats ) {
566         $fio->{stats}{TotalFileCnt}++;
567         $fio->{stats}{TotalFileSize} += $origSize;
568     }
569     if ( $exists ) {
570         if ( $doStats ) {
571             $fio->{stats}{ExistFileCnt}++;
572             $fio->{stats}{ExistFileSize}     += $origSize;
573             $fio->{stats}{ExistFileCompSize} += $outSize;
574         }
575     } elsif ( $outSize > 0 ) {
576         my $fh = $fio->{newFilesFH};
577         print($fh "$digest $origSize $fileName\n") if ( defined($fh) );
578     }
579     return $exists && $origSize > 0;
580 }
581
582 sub statsGet
583 {
584     my($fio) = @_;
585
586     return $fio->{stats};
587 }
588
589 #
590 # Make a given directory.  Returns non-zero on error.
591 #
592 sub makePath
593 {
594     my($fio, $f) = @_;
595     my $name = $1 if ( $f->{name} =~ /(.*)/ );
596     my $path;
597
598     if ( $name eq "." ) {
599         $path = $fio->{outDirSh};
600     } else {
601         $path = $fio->{outDirSh} . $fio->{bpc}->fileNameMangle($name);
602     }
603     $fio->logFileAction("create", $f) if ( $fio->{logLevel} >= 1 );
604     $fio->log("makePath($path, 0777)") if ( $fio->{logLevel} >= 5 );
605     $path = $1 if ( $path =~ /(.*)/ );
606     File::Path::mkpath($path, 0, 0777) if ( !-d $path );
607     return $fio->attribSet($f) if ( -d $path );
608     $fio->log("Can't create directory $path");
609     $fio->{stats}{errorCnt}++;
610     return -1;
611 }
612
613 #
614 # Make a special file.  Returns non-zero on error.
615 #
616 sub makeSpecial
617 {
618     my($fio, $f) = @_;
619     my $name = $1 if ( $f->{name} =~ /(.*)/ );
620     my $fNameM = $fio->{bpc}->fileNameMangle($name);
621     my $path = $fio->{outDirSh} . $fNameM;
622     my $attr = $fio->attribGet($f);
623     my $str = "";
624     my $type = $fio->mode2type($f);
625
626     $fio->log("makeSpecial($path, $type, $f->{mode})")
627                     if ( $fio->{logLevel} >= 5 );
628     if ( $type == BPC_FTYPE_CHARDEV || $type == BPC_FTYPE_BLOCKDEV ) {
629         my($major, $minor, $fh, $fileData);
630
631         if ( defined($f->{rdev_major}) ) {
632             $major = $f->{rdev_major};
633             $minor = $f->{rdev_minor};
634         } else {
635             $major = $f->{rdev} >> 8;
636             $minor = $f->{rdev} & 0xff;
637         }
638         $str = "$major,$minor";
639     } elsif ( ($f->{mode} & S_IFMT) == S_IFLNK ) {
640         $str = $f->{link};
641     } elsif ( ($f->{mode} & S_IFMT) == S_IFREG ) {
642         #
643         # this is a hardlink
644         #
645         if ( !defined($f->{hlink}) ) {
646             $fio->log("Error: makeSpecial($path, $type, $f->{mode}) called"
647                     . " on a regular non-hardlink file");
648             return 1;
649         }
650         $str  = $f->{hlink};
651     }
652     #
653     # Now see if the file is different, or this is a full, in which
654     # case we create the new file.
655     #
656     my($fh, $fileData);
657     if ( $fio->{full}
658             || !defined($attr)
659             || $attr->{type}  != $type
660             || $attr->{mtime} != $f->{mtime}
661             || $attr->{size}  != $f->{size}
662             || $attr->{uid}   != $f->{uid}
663             || $attr->{gid}   != $f->{gid}
664             || $attr->{mode}  != $f->{mode}
665             || !defined($fh = BackupPC::FileZIO->open($attr->{fullPath}, 0,
666                                                       $attr->{compress}))
667             || $fh->read(\$fileData, length($str) + 1) != length($str)
668             || $fileData ne $str ) {
669         $fh->close if ( defined($fh) );
670         $fh = BackupPC::PoolWrite->new($fio->{bpc}, $path,
671                                      length($str), $fio->{xfer}{compress});
672         $fh->write(\$str);
673         my $exist = $fio->processClose($fh, "$fio->{shareM}/$fNameM",
674                                        length($str), 1);
675         $fio->logFileAction($exist ? "pool" : "create", $f)
676                             if ( $fio->{logLevel} >= 1 );
677         return $fio->attribSet($f);
678     } else {
679         $fio->logFileAction("skip", $f) if ( $fio->{logLevel} >= 2 );
680     }
681     $fh->close if ( defined($fh) );
682 }
683
684 #
685 # Make a hardlink.  Returns non-zero on error.
686 # This actually gets called twice for each hardlink.
687 # Once as the file list is processed, and again at
688 # the end.  BackupPC does them as it goes (since it is
689 # just saving the hardlink info and not actually making
690 # hardlinks).
691 #
692 sub makeHardLink
693 {
694     my($fio, $f, $end) = @_;
695
696     return if ( $end );
697     return $fio->makeSpecial($f) if ( !$f->{hlink_self} );
698 }
699
700 sub unlink
701 {
702     my($fio, $path) = @_;
703     
704     $fio->log("Unexpected call BackupPC::Xfer::RsyncFileIO->unlink($path)"); 
705 }
706
707 #
708 # Default log handler
709 #
710 sub logHandler
711 {
712     my($str) = @_;
713
714     print(STDERR $str, "\n");
715 }
716
717 #
718 # Handle one or more log messages
719 #
720 sub log
721 {
722     my($fio, @logStr) = @_;
723
724     foreach my $str ( @logStr ) {
725         next if ( $str eq "" );
726         $fio->{logHandler}($str);
727     }
728 }
729
730 #
731 # Generate a log file message for a completed file
732 #
733 sub logFileAction
734 {
735     my($fio, $action, $f) = @_;
736     my $owner = "$f->{uid}/$f->{gid}";
737     my $type  = (("", "p", "c", "", "d", "", "b", "", "", "", "l", "", "s"))
738                     [($f->{mode} & S_IFMT) >> 12];
739     my $link;
740
741     if ( ($f->{mode} & S_IFMT) == S_IFLNK ) {
742         $link = " -> $f->{link}";
743     } if ( ($f->{mode} & S_IFMT) == S_IFREG
744             && defined($f->{hlink}) && !$f->{hlink_self} ) {
745         $link = " -> $f->{hlink}";
746     }
747
748     $fio->log(sprintf("  %-6s %1s%4o %9s %11.0f %s%s",
749                                 $action,
750                                 $type,
751                                 $f->{mode} & 07777,
752                                 $owner,
753                                 $f->{size},
754                                 $f->{name},
755                                 $link));
756 }
757
758 #
759 # If there is a partial and we are doing a full, we do an incremental
760 # against the partial and a full against the rest.  This subroutine
761 # is how we tell File::RsyncP which files to ignore attributes on
762 # (ie: against the partial dump we do consider the attributes, but
763 # otherwise we ignore attributes).
764 #
765 sub ignoreAttrOnFile
766 {
767     my($fio, $f) = @_;
768
769     return if ( !defined($fio->{partialNum}) );
770     my($attr, $isPartial) = $fio->attribGetWhere($f);
771     $fio->log("$f->{name}: just checking attributes from partial")
772                                 if ( $isPartial && $fio->{logLevel} >= 5 );
773     return !$isPartial;
774 }
775
776 #
777 # This is called by File::RsyncP when a file is skipped because the
778 # attributes match.
779 #
780 sub attrSkippedFile
781 {
782     my($fio, $f, $attr) = @_;
783
784     #
785     # Unless this is a partial, this is normal so ignore it.
786     #
787     return if ( !defined($fio->{partialNum}) );
788
789     $fio->log("$f->{name}: skipped in partial; adding link")
790                                     if ( $fio->{logLevel} >= 5 );
791     $fio->{rxLocalAttr} = $attr;
792     $fio->{rxFile} = $f;
793     $fio->{rxSize} = $attr->{size};
794     delete($fio->{rxInFd});
795     delete($fio->{rxOutFd});
796     delete($fio->{rxDigest});
797     delete($fio->{rxInData});
798     return $fio->fileDeltaRxDone();
799 }
800
801 #
802 # Start receive of file deltas for a particular file.
803 #
804 sub fileDeltaRxStart
805 {
806     my($fio, $f, $cnt, $size, $remainder) = @_;
807
808     $fio->{rxFile}      = $f;           # remote file attributes
809     $fio->{rxLocalAttr} = $fio->attribGet($f); # local file attributes
810     $fio->{rxBlkCnt}    = $cnt;         # how many blocks we will receive
811     $fio->{rxBlkSize}   = $size;        # block size
812     $fio->{rxRemainder} = $remainder;   # size of the last block
813     $fio->{rxMatchBlk}  = 0;            # current start of match
814     $fio->{rxMatchNext} = 0;            # current next block of match
815     $fio->{rxSize}      = 0;            # size of received file
816     my $rxSize = $cnt > 0 ? ($cnt - 1) * $size + $remainder : 0;
817     if ( $fio->{rxFile}{size} != $rxSize ) {
818         $fio->{rxMatchBlk} = undef;     # size different, so no file match
819         $fio->log("$fio->{rxFile}{name}: size doesn't match"
820                   . " ($fio->{rxFile}{size} vs $rxSize)")
821                         if ( $fio->{logLevel} >= 5 );
822     }
823     #
824     # If compression was off and now on, or on and now off, then
825     # don't do an exact match.
826     #
827     if ( defined($fio->{rxLocalAttr})
828             && !$fio->{rxLocalAttr}{compress} != !$fio->{xfer}{compress} ) {
829         $fio->{rxMatchBlk} = undef;     # compression changed, so no file match
830         $fio->log("$fio->{rxFile}{name}: compression changed, so no match"
831               . " ($fio->{rxLocalAttr}{compress} vs $fio->{xfer}{compress})")
832                     if ( $fio->{logLevel} >= 4 );
833     }
834     delete($fio->{rxInFd});
835     delete($fio->{rxOutFd});
836     delete($fio->{rxDigest});
837     delete($fio->{rxInData});
838 }
839
840 #
841 # Process the next file delta for the current file.  Returns 0 if ok,
842 # -1 if not.  Must be called with either a block number, $blk, or new data,
843 # $newData, (not both) defined.
844 #
845 sub fileDeltaRxNext
846 {
847     my($fio, $blk, $newData) = @_;
848
849     if ( defined($blk) ) {
850         if ( defined($fio->{rxMatchBlk}) && $fio->{rxMatchNext} == $blk ) {
851             #
852             # got the next block in order; just keep track.
853             #
854             $fio->{rxMatchNext}++;
855             return;
856         }
857     }
858     my $newDataLen = length($newData);
859     $fio->log("$fio->{rxFile}{name}: blk=$blk, newData=$newDataLen, rxMatchBlk=$fio->{rxMatchBlk}, rxMatchNext=$fio->{rxMatchNext}")
860                     if ( $fio->{logLevel} >= 8 );
861     if ( !defined($fio->{rxOutFd}) ) {
862         #
863         # maybe the file has no changes
864         #
865         if ( $fio->{rxMatchNext} == $fio->{rxBlkCnt}
866                 && !defined($blk) && !defined($newData) ) {
867             #$fio->log("$fio->{rxFile}{name}: file is unchanged");
868             #               if ( $fio->{logLevel} >= 8 );
869             return;
870         }
871
872         #
873         # need to open an output file where we will build the
874         # new version.
875         #
876         $fio->{rxFile}{name} =~ /(.*)/;
877         my $rxOutFileRel = "$fio->{shareM}/" . $fio->{bpc}->fileNameMangle($1);
878         my $rxOutFile    = $fio->{outDir} . $rxOutFileRel;
879         $fio->{rxOutFd}  = BackupPC::PoolWrite->new($fio->{bpc},
880                                            $rxOutFile, $fio->{rxFile}{size},
881                                            $fio->{xfer}{compress});
882         $fio->log("$fio->{rxFile}{name}: opening output file $rxOutFile")
883                         if ( $fio->{logLevel} >= 9 );
884         $fio->{rxOutFile} = $rxOutFile;
885         $fio->{rxOutFileRel} = $rxOutFileRel;
886         $fio->{rxDigest} = File::RsyncP::Digest->new($fio->{protocol_version});
887         $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
888     }
889     if ( defined($fio->{rxMatchBlk})
890                 && $fio->{rxMatchBlk} != $fio->{rxMatchNext} ) {
891         #
892         # Need to copy the sequence of blocks that matched.  If the file
893         # is compressed we need to make a copy of the uncompressed file,
894         # since the compressed file is not seekable.  Future optimizations
895         # could include only creating an uncompressed copy if the matching
896         # blocks were not monotonic, and to only do this if there are
897         # matching blocks (eg, maybe the entire file is new).
898         #
899         my $attr = $fio->{rxLocalAttr};
900         my $fh;
901         if ( !defined($fio->{rxInFd}) && !defined($fio->{rxInData}) ) {
902             if ( $attr->{compress} ) {
903                 if ( !defined($fh = BackupPC::FileZIO->open(
904                                                    $attr->{fullPath},
905                                                    0,
906                                                    $attr->{compress})) ) {
907                     $fio->log("Can't open $attr->{fullPath}");
908                     $fio->{stats}{errorCnt}++;
909                     return -1;
910                 }
911                 if ( $attr->{size} < 16 * 1024 * 1024 ) {
912                     #
913                     # Cache the entire old file if it is less than 16MB
914                     #
915                     my $data;
916                     $fio->{rxInData} = "";
917                     while ( $fh->read(\$data, 16 * 1024 * 1024) > 0 ) {
918                         $fio->{rxInData} .= $data;
919                     }
920                     $fio->log("$attr->{fullPath}: cached all $attr->{size}"
921                             . " bytes")
922                                     if ( $fio->{logLevel} >= 9 );
923                 } else {
924                     #
925                     # Create and write a temporary output file
926                     #
927                     unlink("$fio->{outDirSh}RStmp")
928                                     if  ( -f "$fio->{outDirSh}RStmp" );
929                     if ( open(F, "+>", "$fio->{outDirSh}RStmp") ) {
930                         my $data;
931                         my $byteCnt = 0;
932                         binmode(F);
933                         while ( $fh->read(\$data, 1024 * 1024) > 0 ) {
934                             if ( syswrite(F, $data) != length($data) ) {
935                                 $fio->log(sprintf("Can't write len=%d to %s",
936                                       length($data) , "$fio->{outDirSh}RStmp"));
937                                 $fh->close;
938                                 $fio->{stats}{errorCnt}++;
939                                 return -1;
940                             }
941                             $byteCnt += length($data);
942                         }
943                         $fio->{rxInFd} = *F;
944                         $fio->{rxInName} = "$fio->{outDirSh}RStmp";
945                         sysseek($fio->{rxInFd}, 0, 0);
946                         $fio->log("$attr->{fullPath}: copied $byteCnt,"
947                                 . "$attr->{size} bytes to $fio->{rxInName}")
948                                         if ( $fio->{logLevel} >= 9 );
949                     } else {
950                         $fio->log("Unable to open $fio->{outDirSh}RStmp");
951                         $fh->close;
952                         $fio->{stats}{errorCnt}++;
953                         return -1;
954                     }
955                 }
956                 $fh->close;
957             } else {
958                 if ( open(F, "<", $attr->{fullPath}) ) {
959                     binmode(F);
960                     $fio->{rxInFd} = *F;
961                     $fio->{rxInName} = $attr->{fullPath};
962                 } else {
963                     $fio->log("Unable to open $attr->{fullPath}");
964                     $fio->{stats}{errorCnt}++;
965                     return -1;
966                 }
967             }
968         }
969         my $lastBlk = $fio->{rxMatchNext} - 1;
970         $fio->log("$fio->{rxFile}{name}: writing blocks $fio->{rxMatchBlk}.."
971                   . "$lastBlk")
972                         if ( $fio->{logLevel} >= 9 );
973         my $seekPosn = $fio->{rxMatchBlk} * $fio->{rxBlkSize};
974         if ( defined($fio->{rxInFd})
975                         && !sysseek($fio->{rxInFd}, $seekPosn, 0) ) {
976             $fio->log("Unable to seek $attr->{rxInName} to $seekPosn");
977             $fio->{stats}{errorCnt}++;
978             return -1;
979         }
980         my $cnt = $fio->{rxMatchNext} - $fio->{rxMatchBlk};
981         my($thisCnt, $len, $data);
982         for ( my $i = 0 ; $i < $cnt ; $i += $thisCnt ) {
983             $thisCnt = $cnt - $i;
984             $thisCnt = 512 if ( $thisCnt > 512 );
985             if ( $fio->{rxMatchBlk} + $i + $thisCnt == $fio->{rxBlkCnt} ) {
986                 $len = ($thisCnt - 1) * $fio->{rxBlkSize} + $fio->{rxRemainder};
987             } else {
988                 $len = $thisCnt * $fio->{rxBlkSize};
989             }
990             if ( defined($fio->{rxInData}) ) {
991                 $data = substr($fio->{rxInData}, $seekPosn, $len);
992                 $seekPosn += $len;
993             } else {
994                 my $got = sysread($fio->{rxInFd}, $data, $len);
995                 if ( $got != $len ) {
996                     my $inFileSize = -s $fio->{rxInName};
997                     $fio->log("Unable to read $len bytes from $fio->{rxInName}"
998                             . " got=$got, seekPosn=$seekPosn"
999                             . " ($i,$thisCnt,$fio->{rxBlkCnt},$inFileSize"
1000                             . ",$attr->{size})");
1001                     $fio->{stats}{errorCnt}++;
1002                     return -1;
1003                 }
1004                 $seekPosn += $len;
1005             }
1006             $fio->{rxOutFd}->write(\$data);
1007             $fio->{rxDigest}->add($data);
1008             $fio->{rxSize} += length($data);
1009         }
1010         $fio->{rxMatchBlk} = undef;
1011     }
1012     if ( defined($blk) ) {
1013         #
1014         # Remember the new block number
1015         #
1016         $fio->{rxMatchBlk}  = $blk;
1017         $fio->{rxMatchNext} = $blk + 1;
1018     }
1019     if ( defined($newData) ) {
1020         #
1021         # Write the new chunk
1022         #
1023         my $len = length($newData);
1024         $fio->log("$fio->{rxFile}{name}: writing $len bytes new data")
1025                         if ( $fio->{logLevel} >= 9 );
1026         $fio->{rxOutFd}->write(\$newData);
1027         $fio->{rxDigest}->add($newData);
1028         $fio->{rxSize} += length($newData);
1029     }
1030 }
1031
1032 #
1033 # Finish up the current receive file.  Returns undef if ok, -1 if not.
1034 # Returns 1 if the md4 digest doesn't match.
1035 #
1036 sub fileDeltaRxDone
1037 {
1038     my($fio, $md4, $phase) = @_;
1039     my $name = $1 if ( $fio->{rxFile}{name} =~ /(.*)/ );
1040     my $ret;
1041
1042     close($fio->{rxInFd})  if ( defined($fio->{rxInFd}) );
1043     unlink("$fio->{outDirSh}RStmp") if  ( -f "$fio->{outDirSh}RStmp" );
1044
1045     #
1046     # Check the final md4 digest
1047     #
1048     if ( defined($md4) ) {
1049         my $newDigest;
1050         if ( !defined($fio->{rxDigest}) ) {
1051             #
1052             # File was exact match, but we still need to verify the
1053             # MD4 checksum.  Compute the md4 digest (or fetch the
1054             # cached one.)
1055             #
1056             if ( defined(my $attr = $fio->{rxLocalAttr}) ) {
1057                 #
1058                 # block size doesn't matter: we're only going to
1059                 # fetch the md4 file digest, not the block digests.
1060                 #
1061                 my($err, $csum, $blkSize)
1062                          = BackupPC::Xfer::RsyncDigest->digestStart(
1063                                  $attr->{fullPath}, $attr->{size},
1064                                  0, 2048, $fio->{checksumSeed}, 1,
1065                                  $attr->{compress}, 1);
1066                 if ( $err ) {
1067                     $fio->log("Can't open $attr->{fullPath} for MD4"
1068                             . " check (err=$err, $name)");
1069                     $fio->{stats}{errorCnt}++;
1070                 } else {
1071                     if ( $fio->{logLevel} >= 5 ) {
1072                         my($isCached, $invalid) = $csum->isCached;
1073                         $fio->log("MD4 $attr->{fullPath} cache = $isCached,"
1074                                 . " invalid = $invalid");
1075                     }
1076                     $newDigest = $csum->digestEnd;
1077                 }
1078                 $fio->{rxSize} = $attr->{size};
1079             } else {
1080                 #
1081                 # Empty file; just create an empty file digest
1082                 #
1083                 $fio->{rxDigest} = File::RsyncP::Digest->new($fio->{protocol_version});
1084                 $fio->{rxDigest}->add(pack("V", $fio->{checksumSeed}));
1085                 $newDigest = $fio->{rxDigest}->digest;
1086             }
1087             $fio->log("$name got exact match") if ( $fio->{logLevel} >= 5 );
1088         } else {
1089             $newDigest = $fio->{rxDigest}->digest;
1090         }
1091         if ( $fio->{logLevel} >= 3 ) {
1092             my $md4Str = unpack("H*", $md4);
1093             my $newStr = unpack("H*", $newDigest);
1094             $fio->log("$name got digests $md4Str vs $newStr")
1095         }
1096         if ( $md4 ne $newDigest ) {
1097             if ( $phase > 0 ) {
1098                 $fio->log("$name: fatal error: md4 doesn't match on retry;"
1099                         . " file removed");
1100             } else {
1101                 $fio->log("$name: md4 doesn't match: will retry in phase 1;"
1102                         . " file removed");
1103             }
1104             $fio->{stats}{errorCnt}++;
1105             if ( defined($fio->{rxOutFd}) ) {
1106                 $fio->{rxOutFd}->close;
1107                 unlink($fio->{rxOutFile});
1108             }
1109             delete($fio->{rxFile});
1110             delete($fio->{rxOutFile});
1111             return 1;
1112         }
1113     }
1114
1115     #
1116     # One special case is an empty file: if the file size is
1117     # zero we need to open the output file to create it.
1118     #
1119     if ( $fio->{rxSize} == 0 ) {
1120         my $rxOutFileRel = "$fio->{shareM}/"
1121                          . $fio->{bpc}->fileNameMangle($name);
1122         my $rxOutFile    = $fio->{outDir} . $rxOutFileRel;
1123         $fio->{rxOutFd}  = BackupPC::PoolWrite->new($fio->{bpc},
1124                                            $rxOutFile, $fio->{rxSize},
1125                                            $fio->{xfer}{compress});
1126     }
1127     if ( !defined($fio->{rxOutFd}) ) {
1128         #
1129         # No output file, meaning original was an exact match.
1130         #
1131         $fio->log("$name: nothing to do")
1132                         if ( $fio->{logLevel} >= 5 );
1133         my $attr = $fio->{rxLocalAttr};
1134         my $f = $fio->{rxFile};
1135         $fio->logFileAction("same", $f) if ( $fio->{logLevel} >= 1 );
1136         if ( $fio->{full}
1137                 || $attr->{type}  != $f->{type}
1138                 || $attr->{mtime} != $f->{mtime}
1139                 || $attr->{size}  != $f->{size}
1140                 || $attr->{gid}   != $f->{gid}
1141                 || $attr->{mode}  != $f->{mode} ) {
1142             #
1143             # In the full case, or if the attributes are different,
1144             # we need to make a link from the previous file and
1145             # set the attributes.
1146             #
1147             my $rxOutFile = $fio->{outDirSh}
1148                             . $fio->{bpc}->fileNameMangle($name);
1149             my($exists, $digest, $origSize, $outSize, $errs)
1150                                 = BackupPC::PoolWrite::LinkOrCopy(
1151                                       $fio->{bpc},
1152                                       $attr->{fullPath},
1153                                       $attr->{compress},
1154                                       $rxOutFile,
1155                                       $fio->{xfer}{compress});
1156             #
1157             # Cumulate the stats
1158             #
1159             $fio->{stats}{TotalFileCnt}++;
1160             $fio->{stats}{TotalFileSize} += $fio->{rxSize};
1161             $fio->{stats}{ExistFileCnt}++;
1162             $fio->{stats}{ExistFileSize} += $fio->{rxSize};
1163             $fio->{stats}{ExistFileCompSize} += -s $rxOutFile;
1164             $fio->{rxFile}{size} = $fio->{rxSize};
1165             $ret = $fio->attribSet($fio->{rxFile});
1166             $fio->log(@$errs) if ( defined($errs) && @$errs );
1167
1168             if ( !$exists && $outSize > 0 ) {
1169                 #
1170                 # the hard link failed, most likely because the target
1171                 # file has too many links.  We have copied the file
1172                 # instead, so add this to the new file list.
1173                 #
1174                 my $rxOutFileRel = "$fio->{shareM}/"
1175                                  . $fio->{bpc}->fileNameMangle($name);
1176                 $rxOutFileRel =~ s{^/+}{};
1177                 my $fh = $fio->{newFilesFH};
1178                 print($fh "$digest $origSize $rxOutFileRel\n")
1179                                                 if ( defined($fh) );
1180             }
1181         }
1182     } else {
1183         my $exist = $fio->processClose($fio->{rxOutFd},
1184                                        $fio->{rxOutFileRel},
1185                                        $fio->{rxSize}, 1);
1186         $fio->logFileAction($exist ? "pool" : "create", $fio->{rxFile})
1187                             if ( $fio->{logLevel} >= 1 );
1188         $fio->{rxFile}{size} = $fio->{rxSize};
1189         $ret = $fio->attribSet($fio->{rxFile});
1190     }
1191     delete($fio->{rxDigest});
1192     delete($fio->{rxInData});
1193     delete($fio->{rxFile});
1194     delete($fio->{rxOutFile});
1195     return $ret;
1196 }
1197
1198 #
1199 # Callback function for BackupPC::View->find.  Note the order of the
1200 # first two arguments.
1201 #
1202 sub fileListEltSend
1203 {
1204     my($a, $fio, $fList, $outputFunc) = @_;
1205     my $name = $a->{relPath};
1206     my $n = $name;
1207     my $type = $a->{type};
1208     my $extraAttribs = {};
1209
1210     $n =~ s/^\Q$fio->{xfer}{pathHdrSrc}//;
1211     $fio->log("Sending $name (remote=$n) type = $type") if ( $fio->{logLevel} >= 1 );
1212     if ( $type == BPC_FTYPE_CHARDEV
1213             || $type == BPC_FTYPE_BLOCKDEV
1214             || $type == BPC_FTYPE_SYMLINK ) {
1215         my $fh = BackupPC::FileZIO->open($a->{fullPath}, 0, $a->{compress});
1216         my($str, $rdSize);
1217         if ( defined($fh) ) {
1218             $rdSize = $fh->read(\$str, $a->{size} + 1024);
1219             if ( $type == BPC_FTYPE_SYMLINK ) {
1220                 #
1221                 # Reconstruct symbolic link
1222                 #
1223                 $extraAttribs = { link => $str };
1224                 if ( $rdSize != $a->{size} ) {
1225                     # ERROR
1226                     $fio->log("$name: can't read exactly $a->{size} bytes");
1227                     $fio->{stats}{errorCnt}++;
1228                 }
1229             } elsif ( $str =~ /(\d*),(\d*)/ ) {
1230                 #
1231                 # Reconstruct char or block special major/minor device num
1232                 #
1233                 # Note: char/block devices have $a->{size} = 0, so we
1234                 # can't do an error check on $rdSize.
1235                 #
1236                 $extraAttribs = {
1237                     rdev       => $1 * 256 + $2,
1238                     rdev_major => $1,
1239                     rdev_minor => $2,
1240                 };
1241             } else {
1242                 $fio->log("$name: unexpected special file contents $str");
1243                 $fio->{stats}{errorCnt}++;
1244             }
1245             $fh->close;
1246         } else {
1247             # ERROR
1248             $fio->log("$name: can't open");
1249             $fio->{stats}{errorCnt}++;
1250         }
1251     } elsif ( $fio->{preserve_hard_links}
1252             && ($type == BPC_FTYPE_HARDLINK || $type == BPC_FTYPE_FILE)
1253             && ($type == BPC_FTYPE_HARDLINK
1254                     || $fio->{protocol_version} < 27
1255                     || ($a->{mode} & S_HLINK_TARGET) ) ) {
1256         #
1257         # Fill in fake inode information so that the remote rsync
1258         # can correctly create hardlinks.
1259         #
1260         $name =~ s/^\.?\/+//;
1261         my($target, $inode);
1262
1263         if ( $type == BPC_FTYPE_HARDLINK ) {
1264             my $fh = BackupPC::FileZIO->open($a->{fullPath}, 0,
1265                                              $a->{compress});
1266             if ( defined($fh) ) {
1267                 $fh->read(\$target,  65536);
1268                 $fh->close;
1269                 $target =~ s/^\.?\/+//;
1270                 if ( defined($fio->{hlinkFile2Num}{$target}) ) {
1271                     $inode = $fio->{hlinkFile2Num}{$target};
1272                 } else {
1273                     $inode = $fio->{fileListCnt};
1274                     $fio->{hlinkFile2Num}{$target} = $inode;
1275                 }
1276             } else {
1277                 $fio->log("$a->{fullPath}: can't open for hardlink");
1278                 $fio->{stats}{errorCnt}++;
1279             }
1280         } elsif ( $a->{mode} & S_HLINK_TARGET ) {
1281             if ( defined($fio->{hlinkFile2Num}{$name}) ) {
1282                 $inode = $fio->{hlinkFile2Num}{$name};
1283             } else {
1284                 $inode = $fio->{fileListCnt};
1285                 $fio->{hlinkFile2Num}{$name} = $inode;
1286             }
1287         }
1288         $inode = $fio->{fileListCnt} if ( !defined($inode) );
1289         $fio->log("$name: setting inode to $inode");
1290         $extraAttribs = {
1291             %$extraAttribs,
1292             dev   => 0,
1293             inode => $inode,
1294         };
1295     }
1296     my $f = {
1297         name  => $n,
1298         mode  => $a->{mode} & ~S_HLINK_TARGET,
1299         uid   => $a->{uid},
1300         gid   => $a->{gid},
1301         mtime => $a->{mtime},
1302         size  => $a->{size},
1303         %$extraAttribs,
1304     };
1305     my $logName = $f->{name};
1306     from_to($f->{name}, "utf8", $fio->{clientCharset})
1307                             if ( $fio->{clientCharset} ne "" );
1308     $fList->encode($f);
1309
1310     $logName = "$fio->{xfer}{pathHdrDest}/$f->{name}";
1311     $logName =~ s{//+}{/}g;
1312     $f->{name} = $logName;
1313     $fio->logFileAction("restore", $f) if ( $fio->{logLevel} >= 1 );
1314
1315     &$outputFunc($fList->encodeData);
1316     #
1317     # Cumulate stats
1318     #
1319     $fio->{fileListCnt}++;
1320     if ( $type != BPC_FTYPE_DIR ) {
1321         $fio->{stats}{TotalFileCnt}++;
1322         $fio->{stats}{TotalFileSize} += $a->{size};
1323     }
1324 }
1325
1326 sub fileListSend
1327 {
1328     my($fio, $flist, $outputFunc) = @_;
1329
1330     #
1331     # Populate the file list with the files requested by the user.
1332     # Since some might be directories so we call BackupPC::View::find.
1333     #
1334     $fio->log("fileListSend: sending file list: "
1335              . join(" ", @{$fio->{fileList}})) if ( $fio->{logLevel} >= 4 );
1336     $fio->{fileListCnt} = 0;
1337     $fio->{hlinkFile2Num} = {};
1338     foreach my $name ( @{$fio->{fileList}} ) {
1339         $fio->{view}->find($fio->{xfer}{bkupSrcNum},
1340                            $fio->{xfer}{bkupSrcShare},
1341                            $name, 1,
1342                            \&fileListEltSend, $fio, $flist, $outputFunc);
1343     }
1344 }
1345
1346 sub finish
1347 {
1348     my($fio, $isChild) = @_;
1349
1350     #
1351     # If we are aborting early, remove the last file since
1352     # it was not complete
1353     #
1354     if ( $isChild && defined($fio->{rxFile}) ) {
1355         unlink("$fio->{outDirSh}RStmp") if  ( -f "$fio->{outDirSh}RStmp" );
1356         if ( defined($fio->{rxFile}) ) {
1357             unlink($fio->{rxOutFile});
1358             $fio->log("finish: removing in-process file $fio->{rxFile}{name}");
1359         }
1360     }
1361
1362     #
1363     # Flush the attributes if this is the child
1364     #
1365     $fio->attribWrite(undef) if ( $isChild );
1366 }
1367
1368 #sub is_tainted
1369 #{
1370 #    return ! eval {
1371 #        join('',@_), kill 0;
1372 #        1;
1373 #    };
1374 #}
1375
1376 1;