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