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