/[BackupPC]/upstream/2.1.0/lib/BackupPC/Xfer/RsyncFileIO.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /upstream/2.1.0/lib/BackupPC/Xfer/RsyncFileIO.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show annotations)
Wed Jun 22 19:12:04 2005 UTC (18 years, 10 months ago) by dpavlin
File size: 36906 byte(s)
import of version 2.1.0

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

  ViewVC Help
Powered by ViewVC 1.1.26