File Coverage

blib/lib/File/RsyncP.pm
Criterion Covered Total %
statement 57 738 7.7
branch 0 460 0.0
condition 0 166 0.0
subroutine 19 63 30.1
pod 9 31 29.0
total 85 1458 5.8


line stmt bran cond sub pod time code
1             #============================================================= -*-perl-*-
2             #
3             # File::RsyncP package
4             #
5             # DESCRIPTION
6             # File::RsyncP is a perl module that implements a subset of the
7             # Rsync protocol, sufficient for implementing a client that can
8             # talk to a native rsync server or rsyncd daemon.
9             #
10             # AUTHOR
11             # Craig Barratt
12             #
13             # COPYRIGHT
14             # File::RsyncP is Copyright (C) 2002-2015 Craig Barratt.
15             #
16             # Rsync is Copyright (C) 1996-2001 by Andrew Tridgell, 1996 by Paul
17             # Mackerras, 2001-2002 by Martin Pool, and 2003-2009 by Wayne Davison,
18             # and others.
19             #
20             # This program is free software; you can redistribute it and/or modify
21             # it under the terms of the GNU General Public License as published by
22             # the Free Software Foundation; either version 2 of the License, or
23             # (at your option) any later version.
24             #
25             # This program is distributed in the hope that it will be useful,
26             # but WITHOUT ANY WARRANTY; without even the implied warranty of
27             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28             # GNU General Public License for more details.
29             #
30             # You should have received a copy of the GNU General Public License
31             # along with this program; if not, write to the Free Software
32             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33             #
34             #========================================================================
35             #
36             # Version 0.72, released 11 Jan 2015.
37             #
38             # See http://perlrsync.sourceforge.net.
39             #
40             #========================================================================
41              
42             package File::RsyncP;
43              
44 1     1   557 use strict;
  1         1  
  1         29  
45 1     1   551 use Socket;
  1         3078  
  1         398  
46 1     1   477 use File::RsyncP::Digest;
  1         4  
  1         75  
47 1     1   671 use File::RsyncP::FileIO;
  1         4  
  1         43  
48 1     1   581 use File::RsyncP::FileList;
  1         3  
  1         66  
49 1     1   1065 use Getopt::Long;
  1         17942  
  1         11  
50 1     1   1051 use Data::Dumper;
  1         8980  
  1         83  
51 1     1   9 use Config;
  1         2  
  1         60  
52 1     1   756 use Encode qw/from_to/;
  1         10375  
  1         71  
53 1     1   6 use Fcntl;
  1         2  
  1         227  
54              
55 1     1   5 use vars qw($VERSION);
  1         1  
  1         49  
56             $VERSION = '0.72';
57              
58 1     1   4 use constant S_IFMT => 0170000; # type of file
  1         2  
  1         45  
59 1     1   4 use constant S_IFDIR => 0040000; # directory
  1         1  
  1         56  
60 1     1   5 use constant S_IFCHR => 0020000; # character special
  1         1  
  1         43  
61 1     1   4 use constant S_IFBLK => 0060000; # block special
  1         1  
  1         32  
62 1     1   4 use constant S_IFREG => 0100000; # regular
  1         5  
  1         31  
63 1     1   4 use constant S_IFLNK => 0120000; # symbolic link
  1         1  
  1         32  
64 1     1   4 use constant S_IFSOCK => 0140000; # socket
  1         1  
  1         51  
65 1     1   3 use constant S_IFIFO => 0010000; # fifo
  1         1  
  1         6974  
66              
67             sub new
68             {
69 0     0 0   my($class, $options) = @_;
70              
71 0   0       $options ||= {};
72 0           my $rs = bless {
73             protocol_version => 28,
74             logHandler => \&logHandler,
75             abort => 0,
76             %$options,
77             }, $class;
78              
79             #
80             # In recent versions of rsync (eg: 2.6.8) --devices is no
81             # longer identical to -D. Now -D means --devices --specials.
82             # File::RsyncP assumes --devices behaves the same as -D,
83             # and doesn't currently handle --specials.
84             #
85             # To make sure we don't lie to the remote rsync, we must
86             # send -D instead of --devices. Therefore, we manually
87             # replace --devices with -D in $rs->{rsyncArgs}.
88             #
89 0           for ( my $i = 0 ; $i < @{$rs->{rsyncArgs}} ; $i++ ) {
  0            
90 0 0         $rs->{rsyncArgs}[$i] = "-D"
91             if ( $rs->{rsyncArgs}[$i] eq "--devices" );
92             }
93              
94             #
95             # process rsync options
96             #
97 0           local(@ARGV);
98 0           $rs->{rsyncOpts} = {};
99 0           @ARGV = @{$rs->{rsyncArgs}};
  0            
100              
101 0           my $p = new Getopt::Long::Parser(
102             config => ["bundling", "pass_through"],
103             );
104              
105             #
106             # First extract all the exclude related options for processing later
107             #
108             return if ( !$p->getoptions(
109 0     0     "exclude=s", sub { optExclude($rs, @_); },
110 0     0     "exclude-from=s", sub { optExclude($rs, @_); },
111 0     0     "include=s", sub { optExclude($rs, @_); },
112 0     0     "include-from=s", sub { optExclude($rs, @_); },
113 0     0     "cvs-exclude|C", sub { optExclude($rs, @_); },
114 0 0         ) );
115              
116             #
117             # Since the exclude arguments are no longer needed (they are
118             # passed via the socket, not the command-line args), update
119             # $rs->{rsyncOpts}
120             #
121 0           @{$rs->{rsyncArgs}} = @ARGV;
  0            
122              
123             #
124             # Now process the rest of the arguments we care about
125             #
126 0 0         return if ( !$p->getoptions($rs->{rsyncOpts},
127             "block-size=i",
128             "devices|D",
129             "from0|0",
130             "group|g",
131             "hard-links|H",
132             "ignore-times|I",
133             "links|l",
134             "numeric-ids",
135             "owner|o",
136             "perms|p",
137             "protocol=i",
138             "recursive|r",
139             "relative|R",
140             "timeout",
141             "verbose|v+",
142             ) );
143 0           $rs->{blockSize} = $rs->{rsyncOpts}{"block-size"};
144 0   0       $rs->{timeout} ||= $rs->{rsyncOpts}{timeout};
145 0 0         $rs->{protocol_version} = $rs->{rsyncOpts}{protocol}
146             if ( defined($rs->{rsyncOpts}{protocol}) );
147 0           $rs->{fio_version} = 1;
148 0 0         if ( !defined($rs->{fio}) ) {
149 0           $rs->{fio} = File::RsyncP::FileIO->new({
150             blockSize => $rs->{blockSize},
151             logLevel => $rs->{logLevel},
152             protocol_version => $rs->{protocol_version},
153             preserve_hard_links => $rs->{rsyncOpts}{"hard-links"},
154             clientCharset => $rs->{clientCharset},
155             });
156 0           eval { $rs->{fio_version} = $rs->{fio}->version; };
  0            
157             } else {
158             #
159             # Tell the existing FileIO module various parameters that
160             # depend upon the parsed rsync args
161             #
162 0           eval { $rs->{fio_version} = $rs->{fio}->version; };
  0            
163 0           $rs->{fio}->blockSize($rs->{blockSize});
164 0 0         if ( $rs->{fio_version} >= 2 ) {
165 0           $rs->{fio}->protocol_version($rs->{protocol_version});
166 0           $rs->{fio}->preserve_hard_links($rs->{rsyncOpts}{"hard-links"});
167             } else {
168             #
169             # old version of FileIO: only supports version 26
170             #
171 0 0         $rs->{protocol_version} = 26 if ( $rs->{protocol_version} > 26 );
172             }
173             }
174              
175             #
176             # build signal list in case we do an abort
177             #
178 0           my $i = 0;
179 0           foreach my $name ( split(' ', $Config{sig_name}) ) {
180 0           $rs->{sigName2Num}{$name} = $i;
181 0           $i++;
182             }
183 0           return $rs;
184             }
185              
186             sub optExclude
187             {
188 0     0 0   my($rs, $argName, $argValue) = @_;
189              
190 0           push(@{$rs->{excludeArgs}}, {name => $argName, value => $argValue});
  0            
191             }
192              
193             #
194             # Strip the exclude and include arguments from the given argument list
195             #
196             sub excludeStrip
197             {
198 0     0 0   my($rs, $args) = @_;
199 0           local(@ARGV);
200 0           my $p = new Getopt::Long::Parser(
201             config => ["bundling", "pass_through"],
202             );
203              
204 0           @ARGV = @$args;
205              
206             #
207             # Extract all the exclude related options
208             #
209             $p->getoptions(
210 0     0     "exclude=s", sub { },
211 0     0     "exclude-from=s", sub { },
212 0     0     "include=s", sub { },
213 0     0     "include-from=s", sub { },
214 0     0     "cvs-exclude|C", sub { },
215 0           );
216              
217 0           return \@ARGV;
218             }
219              
220             sub serverConnect
221             {
222 0     0 1   my($rs, $host, $port) = @_;
223             #local(*FH);
224              
225 0   0       $port ||= 873;
226 0           my $proto = getprotobyname('tcp');
227 0   0       my $iaddr = inet_aton($host) || return "unknown host $host";
228 0           my $paddr = sockaddr_in($port, $iaddr);
229              
230 0 0         alarm($rs->{timeout}) if ( $rs->{timeout} );
231 0 0         socket(FH, PF_INET, SOCK_STREAM, $proto)
232             || return "inet socket: $!";
233 0 0         connect(FH, $paddr) || return "inet connect: $!";
234 0           $rs->{fh} = *FH;
235 0           $rs->writeData("\@RSYNCD: $rs->{protocol_version}\n", 1);
236 0           my $line = $rs->getLine;
237 0 0         alarm(0) if ( $rs->{timeout} );
238 0 0         if ( $line !~ /\@RSYNCD:\s*(\d+)/ ) {
239 0           return "unexpected response $line\n";
240             }
241 0           $rs->{remote_protocol} = $1;
242 0 0 0       if ( $rs->{remote_protocol} < 20 || $rs->{remote_protocol} > 40 ) {
243 0           return "Bad protocol version: $rs->{remote_protocol}\n";
244             }
245 0 0         $rs->log("Connected to $host:$port, remote version $rs->{remote_protocol}")
246             if ( $rs->{logLevel} >= 1 );
247 0 0         $rs->{protocol_version} = $rs->{remote_protocol}
248             if ( $rs->{protocol_version} > $rs->{remote_protocol} );
249 0 0         $rs->{fio}->protocol_version($rs->{protocol_version})
250             if ( $rs->{fio_version} >= 2 );
251 0 0         $rs->log("Negotiated protocol version $rs->{protocol_version}")
252             if ( $rs->{logLevel} >= 1 );
253 0           return;
254             }
255              
256             sub serverList
257             {
258 0     0 0   my($rs) = @_;
259 0           my(@service);
260              
261 0 0         return "not connected" if ( !defined($rs->{fh}) );
262 0           $rs->writeData("#list\n", 1);
263 0           while ( 1 ) {
264 0           my $line = $rs->getLine;
265 0 0         $rs->log("Got `$line'") if ( $rs->{logLevel} >= 2 );
266 0 0         last if ( $line eq "\@RSYNCD: EXIT" );
267 0           push(@service, $line);
268             }
269 0           return @service;
270             }
271              
272             sub serverService
273             {
274 0     0 1   my($rs, $service, $user, $passwd, $authRequired) = @_;
275 0           my($line);
276              
277 0 0         return "not connected" if ( !defined($rs->{fh}) );
278 0           $rs->writeData("$service\n", 1);
279 0           $line = $rs->getLine;
280 0 0         return $1 if ( $line =~ /\@ERROR: (.*)/ );
281 0 0         if ( $line =~ /\@RSYNCD: AUTHREQD (.{22})/ ) {
    0          
282 0           my $challenge = $1;
283 0           my $md4 = File::RsyncP::Digest->new($rs->{protocol_version});
284 0           $md4->add(pack("V", 0));
285 0           $md4->add($passwd);
286 0           $md4->add($challenge);
287 0           my $response = $md4->digest;
288 0 0         $rs->log("Got response: " . unpack("H*", $response))
289             if ( $rs->{logLevel} >= 2 );
290 0           my $response1 = $rs->encode_base64($response);
291 0 0         $rs->log("in mime: " . $response1) if ( $rs->{logLevel} >= 5 );
292 0           $rs->writeData("$user $response1\n", 1);
293 0 0         $rs->log("Auth: got challenge: $challenge, reply: $user $response1")
294             if ( $rs->{logLevel} >= 2 );
295 0           $line = $rs->getLine;
296             } elsif ( $authRequired ) {
297 0           return "auth required, but service $service is open/insecure";
298             }
299 0 0         return $1 if ( $line =~ /\@ERROR: (.*)/ );
300 0 0         if ( $line ne "\@RSYNCD: OK" ) {
301 0           return "unexpected response: '$line'";
302             }
303 0 0         $rs->log("Connected to module $service") if ( $rs->{logLevel} >= 1 );
304 0           return;
305             }
306              
307             sub serverStart
308             {
309 0     0 1   my($rs, $remoteSend, $remoteDir) = @_;
310              
311 0           my @args = @{$rs->{rsyncArgs}};
  0            
312 0 0         unshift(@args, "--sender") if ( $remoteSend );
313 0           unshift(@args, "--server");
314 0           push(@args, ".", $remoteDir);
315 0           $rs->{remoteSend} = $remoteSend;
316 0           $rs->writeData(join("\n", @args) . "\n\n", 1);
317 0 0         $rs->log("Sending args: " . join(" ", @args)) if ( $rs->{logLevel} >= 1 );
318             }
319              
320             sub encode_base64
321             {
322 0     0 0   my($rs, $str) = @_;
323              
324 0           my $s2 = pack('u', $str);
325 0           $s2 =~ tr|` -_|AA-Za-z0-9+/|;
326 0           return substr($s2, 1, int(1.0 - 1e-10 + length($str) * 8 / 6));
327             }
328              
329             sub remoteStart
330             {
331 0     0 1   my($rs, $remoteSend, $remoteDir) = @_;
332 0           local(*RSYNC);
333 0           my($pid, $cmd);
334              
335 0 0         socketpair(RSYNC, FH, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
336             or die "socketpair: $!";
337 0           $rs->{remoteSend} = $remoteSend;
338 0           $rs->{remoteDir} = $remoteDir;
339              
340 0 0 0       $rs->{rsyncCmd} = [split(" ", $rs->{rsyncCmd})]
341             if ( ref($rs->{rsyncCmd}) ne 'ARRAY'
342             && ref($rs->{rsyncCmd}) ne 'CODE' );
343 0 0 0       if ( $rs->{rsyncCmdType} eq "full" || ref($rs->{rsyncCmd}) ne 'ARRAY' ) {
344 0           $cmd = $rs->{rsyncCmd};
345             } else {
346 0           $cmd = $rs->{rsyncArgs};
347 0 0         unshift(@$cmd, "--sender") if ( $remoteSend );
348 0           unshift(@$cmd, "--server");
349 0 0         if ( $rs->{rsyncCmdType} eq "shell" ) {
350             #
351             # Do shell escaping of rsync arguments
352             #
353 0           for ( my $i = 0 ; $i < @$cmd ; $i++ ) {
354 0           $cmd->[$i] = $rs->shellEscape($cmd->[$i]);
355             }
356 0           $remoteDir = $rs->shellEscape($remoteDir);
357             }
358 0           $cmd = [@{$rs->{rsyncCmd}}, @$cmd];
  0            
359 0 0         if ( $remoteSend ) {
360 0           push(@$cmd, ".", $remoteDir);
361             } else {
362 0           push(@$cmd, ".");
363             }
364             }
365 0 0 0       $rs->log("Running: " . join(" ", @$cmd))
366             if ( ref($cmd) eq 'ARRAY' && $rs->{logLevel} >= 1 );
367 0 0         if ( !($pid = fork()) ) {
368             #
369             # The child execs rsync.
370             #
371 0           close(FH);
372 0           close(STDIN);
373 0           close(STDOUT);
374 0           close(STDERR);
375 0           open(STDIN, "<&RSYNC");
376 0           open(STDOUT, ">&RSYNC");
377 0           open(STDERR, ">&RSYNC");
378 0 0         if ( ref($cmd) eq 'CODE' ) {
379 0           &$cmd();
380             } else {
381 0           exec(@$cmd);
382             }
383             # not reached
384             # $rs->log("Failed to exec rsync command $cmd[0]");
385             # exit(0);
386             }
387 0           close(RSYNC);
388 0           $rs->{fh} = *FH;
389 0           $rs->{rsyncPID} = $pid;
390 0 0         $rs->{pidHandler}->($rs->{rsyncPID}, $rs->{childPID})
391             if ( defined($rs->{pidHandler}) );
392             #
393             # Write our version and get the remote version
394             #
395 0           $rs->writeData(pack("V", $rs->{protocol_version}), 1);
396 0 0         $rs->log("Rsync command pid is $pid") if ( $rs->{logLevel} >= 3 );
397 0 0         $rs->log("Fetching remote protocol") if ( $rs->{logLevel} >= 5 );
398 0 0         return -1 if ( $rs->getData(4) < 0 );
399 0           my $data = $rs->{readData};
400 0           my $version = unpack("V", $rs->{readData});
401 0           $rs->{readData} = substr($rs->{readData}, 4);
402 0           $rs->{remote_protocol} = $version;
403 0 0         $rs->log("Got remote protocol $version") if ( $rs->{logLevel} >= 1 );
404 0 0         $rs->{protocol_version} = $rs->{remote_protocol}
405             if ( $rs->{protocol_version} > $rs->{remote_protocol} );
406 0 0         $rs->{fio}->protocol_version($rs->{protocol_version})
407             if ( $rs->{fio_version} >= 2 );
408 0 0 0       if ( $version < 20 || $version > 40 ) {
409 0           $rs->log("Fatal error (bad version): $data");
410 0           return -1;
411             }
412 0 0         $rs->log("Negotiated protocol version $rs->{protocol_version}")
413             if ( $rs->{logLevel} >= 1 );
414 0           return;
415             }
416              
417             sub serverClose
418             {
419 0     0 1   my($rs) = @_;
420              
421 0 0         return if ( !defined($rs->{fh}) );
422 0           close($rs->{fh});
423 0           $rs->{fh} = undef;
424             }
425              
426             sub go
427             {
428 0     0 1   my($rs, $localDir) = @_;
429              
430 0           my $remoteDir = $rs->{remoteDir};
431 0 0         return $rs->{fatalErrorMsg} if ( $rs->getData(4) < 0 );
432 0           $rs->{checksumSeed} = unpack("V", $rs->{readData});
433 0           $rs->{readData} = substr($rs->{readData}, 4);
434 0           $rs->{fio}->checksumSeed($rs->{checksumSeed});
435 0           $rs->{fio}->dirs($localDir, $remoteDir);
436 0 0         $rs->log(sprintf("Got checksumSeed 0x%x", $rs->{checksumSeed}))
437             if ( $rs->{logLevel} >= 2 );
438              
439 0 0         if ( $rs->{remoteSend} ) {
440             #
441             # Get the file list from the remote sender
442             #
443 0 0         if ( $rs->fileListReceive() < 0 ) {
444 0           $rs->log("fileListReceive() failed");
445 0           return "fileListReceive failed";
446             }
447              
448             #
449             # Sort and match inode data if hardlinks are enabled
450             #
451 0 0         if ( $rs->{rsyncOpts}{"hard-links"} ) {
452 0           $rs->{fileList}->init_hard_links();
453             ##my $cnt = $rs->{fileList}->count;
454             ##for ( my $n = 0 ; $n < $cnt ; $n++ ) {
455             ## my $f = $rs->{fileList}->get($n);
456             ## print Dumper($f);
457             ##}
458             }
459              
460 0 0         if ( $rs->{logLevel} >= 2 ) {
461 0           my $cnt = $rs->{fileList}->count;
462 0           $rs->log("Got file list: $cnt entries");
463             }
464              
465             #
466             # At this point the uid/gid list would be received,
467             # but with numeric-ids nothing is sent. We currently
468             # only support the numeric-ids case.
469             #
470              
471             #
472             # Read and skip a word: this is the io_error flag.
473             #
474 0 0         return "can't read io_error flag" if ( $rs->getChunk(4) < 0 );
475 0           $rs->{chunkData} = substr($rs->{chunkData}, 4);
476              
477             #
478             # If this is a partial, then check which files we are
479             # going to skip
480             #
481 0 0         $rs->partialFileListPopulate() if ( $rs->{doPartial} );
482              
483             #
484             # Dup the $rs->{fh} socket file handle into two pieces: read-only
485             # and write-only. The child gets the read-only handle and
486             # we keep the write-only one. We make the write-only handle
487             # non-blocking.
488             #
489 0           my $pid;
490 0           local(*RH, *WH, *FHWr, *FHRd);
491              
492 0           socketpair(RH, WH, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
493 0           shutdown(RH, 1);
494 0           shutdown(WH, 0);
495              
496 0           open(FHWr, ">&$rs->{fh}");
497 0           open(FHRd, "<&$rs->{fh}");
498 0           close($rs->{fh});
499              
500 0 0         if ( !($pid = fork()) ) {
501             #
502             # The child receives the file deltas in two passes.
503             # If a file needs to be repeated in phase 2 we send
504             # the file into the the parent via the pipe.
505             #
506             # First the log handler for both us and fio has to forward
507             # to the parent, so redefine them.
508             #
509             $rs->{logHandler} = sub {
510 0     0     my($str) = @_;
511 0           $str =~ s/\n/\\n/g;
512 0           $str =~ s/\r/\\r/g;
513 0           print WH "log $str\n";
514 0           };
515             $rs->{fio}->logHandlerSet(sub {
516 0     0     my($str) = @_;
517 0           $str =~ s/\n/\\n/g;
518 0           $str =~ s/\r/\\r/g;
519 0           print WH "log $str\n";
520 0           });
521 0           close(RH);
522 0           close(FHWr);
523 0           $rs->{fh} = *FHRd;
524 0           setsockopt($rs->{fh}, SOL_SOCKET, SO_RCVBUF, 8 * 65536);
525 0           setsockopt(WH, SOL_SOCKET, SO_SNDBUF, 8 * 65536);
526 0           my $oldFH = select(WH); $| = 1; select($oldFH);
  0            
  0            
527 0           $rs->fileDeltaGet(*WH, 0);
528 0 0         $rs->log("Child is sending done")
529             if ( $rs->{logLevel} >= 5 );
530 0           print(WH "done\n");
531 0 0         $rs->fileDeltaGet(*WH, 1) if ( !$rs->{abort} );
532             #
533             # Get stats
534             #
535 0           $rs->statsGet(*WH);
536             #
537             # Final signoff
538             #
539 0           $rs->writeData(pack("V", 0xffffffff), 1);
540 0           $rs->{fio}->finish(1);
541 0 0         $rs->log("Child is aborting") if ( $rs->{abort} );
542 0           print(WH "exit\n");
543 0           exit(0);
544             }
545 0           close(WH);
546 0           close(FHRd);
547 0           $rs->{fh} = *FHWr;
548              
549             #
550             # Make our write handle non-blocking
551             #
552 0           my $flags = '';
553 0 0         if ( fcntl($rs->{fh}, F_GETFL, $flags) ) {
554 0           $flags |= O_NONBLOCK;
555 0 0         if ( !fcntl($rs->{fh}, F_SETFL, $flags) ) {
556 0           $rs->log("Parent fcntl(F_SETFL) failed; non-block set failed");
557             }
558             } else {
559 0           $rs->log("Parent fcntl(F_GETFL) failed; non-block failed");
560             }
561              
562 0           $rs->{childFh} = *RH;
563 0           $rs->{childPID} = $pid;
564 0 0         $rs->log("Child PID is $pid") if ( $rs->{logLevel} >= 2 );
565 0 0         $rs->{pidHandler}->($rs->{rsyncPID}, $rs->{childPID})
566             if ( defined($rs->{pidHandler}) );
567 0           setsockopt($rs->{fh}, SOL_SOCKET, SO_SNDBUF, 8 * 65536);
568 0           setsockopt($rs->{childFh}, SOL_SOCKET, SO_RCVBUF, 8 * 65536);
569             #
570             # The parent generates the file checksums and waits for
571             # the child to finish. The child tells us if any files
572             # need to be repeated for phase 2.
573             #
574             # Phase 1: csum length is 2 (or >= 2 for protocol_version >= 27)
575             #
576 0           $rs->fileCsumSend(0);
577              
578             #
579             # Phase 2: csum length is 16
580             #
581 0           $rs->fileCsumSend(1);
582              
583 0 0         if ( $rs->{abort} ) {
584             #
585             # If we are aborting, give the child a few seconds
586             # to finish up.
587             #
588 0           for ( my $i = 0 ; $i < 10 ; $i++ ) {
589 0 0 0       last if ( $rs->{childDone} >= 3 || $rs->pollChild(1) < 0 );
590             }
591 0 0         $rs->{fatalErrorMsg} = $rs->{abortReason}
592             if ( !defined($rs->{fatalErrorMsg}) );
593             }
594            
595             #
596             # Done
597             #
598 0           $rs->{fio}->finish(0);
599 0           close(RH);
600 0 0         return $rs->{fatalErrorMsg} if ( defined($rs->{fatalErrorMsg}) );
601 0           return;
602             } else {
603             #syswrite($rs->{fh}, pack("V", time));
604             #
605             # Send the file list to the remote server
606             #
607 0           $rs->fileListSend();
608 0 0         return $rs->{fatalErrorMsg} if ( $rs->{fatalError} );
609              
610             #
611             # Phase 1: csum length is 2
612             #
613 0           $rs->fileCsumReceive(0);
614 0 0         return $rs->{fatalErrorMsg} if ( $rs->{fatalError} );
615              
616             #
617             # Phase 2: csum length is 3
618             #
619 0           $rs->fileCsumReceive(1);
620 0 0         return $rs->{fatalErrorMsg} if ( $rs->{fatalError} );
621              
622             #
623             # Get final int handshake, and wait for EOF
624             #
625 0           $rs->getData(4);
626 0 0         return -1 if ( $rs->{abort} );
627 0           sysread($rs->{fh}, my $data, 1);
628              
629 0           return;
630             }
631             }
632              
633             #
634             # When a partial rsync is done (meaning selective per-file ignore-attr)
635             # we pass through the file list and remember which files we should
636             # skip. This allows the child to callback the user on each skipped
637             # file.
638             #
639             sub partialFileListPopulate
640             {
641 0     0 0   my($rs) = @_;
642 0           my $cnt = $rs->{fileList}->count;
643 0           for ( my $n = 0 ; $n < $cnt ; $n++ ) {
644 0           my $f = $rs->{fileList}->get($n);
645 0 0         next if ( !defined($f) );
646 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
647             if ( $rs->{clientCharset} ne "" );
648 0           my $attr = $rs->{fio}->attribGet($f);
649 0           my $thisIgnoreAttr = $rs->{fio}->ignoreAttrOnFile($f);
650              
651             #
652             # check if we should skip this file: same type, size, mtime etc
653             #
654 0 0 0       if ( !$thisIgnoreAttr
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
655             && $f->{size} == $attr->{size}
656             && $f->{mtime} == $attr->{mtime}
657             && (!$rs->{rsyncOpts}{perms} || $f->{mode} == $attr->{mode})
658             && (!$rs->{rsyncOpts}{group} || $f->{gid} == $attr->{gid})
659             && (!$rs->{rsyncOpts}{owner} || $f->{uid} == $attr->{uid})
660             && (!$rs->{rsyncOpts}{"hard-links"}
661             || $f->{hlink_self} == $attr->{hlink_self}) ) {
662 0           $rs->{fileList}->flagSet($n, 1);
663             }
664             }
665             }
666              
667             sub fileListReceive
668             {
669 0     0 0   my($rs) = @_;
670 0           my($flags, $l1, $l2, $namel1, $name, $length, $mode, $mtime,
671             $uid, $gid, $rdev);
672 0           my($data, $flData);
673              
674 0           $rs->{fileList} = File::RsyncP::FileList->new({
675             preserve_uid => $rs->{rsyncOpts}{owner},
676             preserve_gid => $rs->{rsyncOpts}{group},
677             preserve_links => $rs->{rsyncOpts}{links},
678             preserve_devices => $rs->{rsyncOpts}{devices},
679             preserve_hard_links => $rs->{rsyncOpts}{"hard-links"},
680             always_checksum => $rs->{rsyncOpts}{checksum},
681             protocol_version => $rs->{protocol_version},
682             });
683              
684             #
685             # Process the exclude/include arguments and send the
686             # exclude/include file list
687             #
688 0           foreach my $arg ( @{$rs->{excludeArgs}} ) {
  0            
689 0 0         if ( $arg->{name} eq "exclude" ) {
    0          
    0          
    0          
    0          
690 0           $rs->{fileList}->exclude_add($arg->{value}, 0);
691             } elsif ( $arg->{name} eq "include" ) {
692 0           $rs->{fileList}->exclude_add($arg->{value}, 2);
693             } elsif ( $arg->{name} eq "exclude-from" ) {
694 0           $rs->{fileList}->exclude_add_file($arg->{value}, 1);
695             } elsif ( $arg->{name} eq "include-from" ) {
696 0           $rs->{fileList}->exclude_add_file($arg->{value}, 3);
697             } elsif ( $arg->{name} eq "cvs-exclude" ) {
698 0           $rs->{fileList}->exclude_cvs_add();
699             } else {
700 0           $rs->log("Error: Don't recognize exclude argument $arg->{name}"
701             . " ($arg->{value})");
702             }
703             }
704 0           $rs->{fileList}->exclude_list_send();
705 0           $rs->writeData($rs->{fileList}->encodeData(), 1);
706 0 0         if ( $rs->{logLevel} >= 1 ) {
707 0           foreach my $exc ( @{$rs->{fileList}->exclude_list_get()} ) {
  0            
708 0 0         from_to($exc->{pattern}, $rs->{clientCharset}, "utf8")
709             if ( $rs->{clientCharset} ne "" );
710 0 0         if ( $exc->{flags} & (1 << 4) ) {
711 0           $rs->log("Sent include: $exc->{pattern}");
712             } else {
713 0           $rs->log("Sent exclude: $exc->{pattern}");
714             }
715             }
716             }
717              
718             #
719             # Now receive the file list
720             #
721 0           my $curr = 0;
722 0           while ( !$rs->{fileList}->decodeDone ) {
723 0 0 0       return -1 if ( $rs->{chunkData} eq "" && $rs->getChunk(1) < 0 );
724 0           my $cnt = $rs->{fileList}->decode($rs->{chunkData});
725 0 0         return -1 if ( $rs->{fileList}->fatalError );
726 0 0         if ( $rs->{logLevel} >= 4 ) {
727 0           my $end = $rs->{fileList}->count;
728 0           while ( $curr < $end ) {
729 0           my $f = $rs->{fileList}->get($curr);
730 0 0         next if ( !defined($f) );
731 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
732             if ( $rs->{clientCharset} ne "" );
733 0           $rs->log("Got file ($curr of $end): $f->{name}");
734 0           $curr++;
735             }
736             }
737 0 0         if ( $cnt > 0 ) {
738 0           $rs->{chunkData} = substr($rs->{chunkData}, $cnt);
739 0 0 0       return -1 if ( !$rs->{fileList}->decodeDone
740             && $rs->getChunk(length($rs->{chunkData}) + 1) < 0 );
741             }
742             }
743              
744             #
745             # Sort and clean the file list
746             #
747 0           $rs->{fileList}->clean;
748             }
749              
750             #
751             # Called by the child process to create directories, special files,
752             # and optionally to set attributes on normal files.
753             #
754             sub fileSpecialCreate
755             {
756 0     0 0   my($rs, $start, $end) = @_;
757              
758 0 0         $end = $rs->{fileList}->count if ( !defined($end) );
759 0           for ( my $n = $start ; $n < $end ; $n++ ) {
760 0           my $f = $rs->{fileList}->get($n);
761 0 0         next if ( !defined($f) );
762 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
763             if ( $rs->{clientCharset} ne "" );
764 0           my $attr = $rs->{fio}->attribGet($f);
765              
766 0 0 0       if ( $rs->{doPartial} && $rs->{fileList}->flagGet($n) ) {
767 0           $rs->{fio}->attrSkippedFile($f, $attr);
768 0           next;
769             }
770              
771 0           $rs->{fio}->attribSet($f, 1);
772              
773 0 0 0       if ( ($f->{mode} & S_IFMT) != S_IFREG ) {
    0          
774             #
775             # A special file
776             #
777 0 0         if ( ($f->{mode} & S_IFMT) == S_IFDIR ) {
778 0 0         if ( $rs->{fio}->makePath($f) ) {
779             # error
780 0           $rs->log("Error: makePath($f->{name}) failed");
781             }
782             } else {
783 0 0         if ( $rs->{fio}->makeSpecial($f) ) {
784             # error
785 0           $rs->log("Error: makeSpecial($f->{name}) failed");
786             }
787             }
788             } elsif ( defined($f->{hlink}) && !$f->{hlink_self} ) {
789 0 0         if ( $rs->{fio}->makeHardLink($f, 0) ) {
790 0           $rs->log("Error: makeHardlink($f->{name} -> $f->{hlink}) failed");
791             }
792             }
793             }
794             }
795              
796             sub fileCsumSend
797             {
798 0     0 0   my($rs, $phase) = @_;
799 0 0         my $csumLen = $phase == 0 ? 2 : 16;
800 0           my $ignoreAttr = $rs->{rsyncOpts}{"ignore-times"};
801              
802 0           $rs->{phase} = $phase;
803 0           my $cnt = $rs->{fileList}->count;
804 0 0         $rs->{doList} = [0..($cnt-1)] if ( $phase == 0 );
805 0           $rs->{redoList} = [];
806 0 0         if ( $rs->{logLevel} >= 2 ) {
807 0           my $cnt = @{$rs->{doList}};
  0            
808 0           $rs->log("Sending csums, cnt = $cnt, phase = $phase");
809             }
810 0   0       while ( @{$rs->{doList}} || $phase == 1 && $rs->{childDone} < 3 ) {
  0   0        
811 0 0         if ( @{$rs->{doList}} ) {
  0            
812 0           my $n = shift(@{$rs->{doList}});
  0            
813 0           my $f = $rs->{fileList}->get($n);
814 0 0         next if ( !defined($f) );
815 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
816             if ( $rs->{clientCharset} ne "" );
817              
818 0 0 0       if ( $rs->{doPartial} && $rs->{fileList}->flagGet($n) ) {
819 0 0 0       $rs->log("Skipping $f->{name} (same attr on partial)")
820             if ( $rs->{logLevel} >= 3
821             && ($f->{mode} & S_IFMT) == S_IFREG );
822 0           next;
823             }
824              
825             #
826             # check if we should skip this file: same type, size, mtime etc
827             #
828 0           my $attr = $rs->{fio}->attribGet($f);
829              
830 0 0 0       if ( !$ignoreAttr
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
831             && $phase == 0
832             && $f->{size} == $attr->{size}
833             && $f->{mtime} == $attr->{mtime}
834             && (!$rs->{rsyncOpts}{perms} || $f->{mode} == $attr->{mode})
835             && (!$rs->{rsyncOpts}{group} || $f->{gid} == $attr->{gid})
836             && (!$rs->{rsyncOpts}{owner} || $f->{uid} == $attr->{uid})
837             && (!$rs->{rsyncOpts}{"hard-links"}
838             || $f->{hlink_self} == $attr->{hlink_self}) ) {
839 0 0 0       $rs->log("Skipping $f->{name} (same attr)")
840             if ( $rs->{logLevel} >= 3
841             && ($f->{mode} & S_IFMT) == S_IFREG );
842 0           next;
843             }
844              
845 0           my $blkSize;
846 0 0 0       if ( ($f->{mode} & S_IFMT) != S_IFREG ) {
    0 0        
    0 0        
    0          
847             #
848             # Remote file is special: no checksum needed.
849             #
850 0           next;
851             } elsif ( $rs->{rsyncOpts}{"hard-links"}
852             && defined($f->{hlink})
853             && !$f->{hlink_self} ) {
854             #
855             # Skip any hardlinks; the child will create them later
856             #
857 0           next;
858             } elsif ( !defined($attr->{mode})
859             || ($attr->{mode} & S_IFMT) != S_IFREG ) {
860             #
861             # Local file isn't a regular file but remote is.
862             # So delete the local file and send an empty
863             # checksum.
864             #
865 0 0         $rs->{fio}->unlink($f->{name}) if ( defined($attr->{mode}) );
866 0 0         $rs->log("Sending empty csums for $f->{name}")
867             if ( $rs->{logLevel} >= 5 );
868 0           $rs->write_sum_head($n, 0, $rs->{blockSize}, $csumLen, 0);
869             } elsif ( ($blkSize = $rs->{fio}->csumStart($f, 0, $rs->{blockSize},
870             $phase)) < 0 ) {
871             #
872             # Can't open the file, so send an empty checksum
873             #
874 0 0         $rs->log("Sending empty csums for $f->{name}")
875             if ( $rs->{logLevel} >= 5 );
876 0           $rs->write_sum_head($n, 0, $rs->{blockSize}, $csumLen, 0);
877             } else {
878             #
879             # The local file is a regular file, so generate and
880             # send the checksums.
881             #
882              
883             #
884             # Compute adaptive block size, from $rs->{blockSize}
885             # to 16384 based on file size.
886             #
887 0 0         if ( $blkSize <= 0 ) {
888 0           $blkSize = int($attr->{size} / 10000);
889 0 0         $blkSize = $rs->{blockSize}
890             if ( $blkSize < $rs->{blockSize} );
891 0 0         $blkSize = 16384 if ( $blkSize > 16384 );
892             }
893 0           my $blkCnt = int(($attr->{size} + $blkSize - 1)
894             / $blkSize);
895 0 0         $rs->log("Sending csums for $f->{name} (size=$attr->{size})")
896             if ( $rs->{logLevel} >= 5 );
897 0 0         $rs->write_sum_head($n, $blkCnt, $blkSize, $csumLen,
898             $blkCnt > 0
899             ? $attr->{size} - ($blkCnt - 1) * $blkSize
900             : $attr->{size});
901 0           my $nWrite = ($csumLen + 4) * $blkCnt;
902 0   0       while ( $blkCnt > 0 && $nWrite > 0 ) {
903 0 0         my $thisCnt = $blkCnt > 256 ? 256 : $blkCnt;
904 0           my $csum = $rs->{fio}->csumGet($thisCnt, $csumLen,
905             $blkSize);
906 0           $rs->writeData($csum);
907 0           $nWrite -= length($csum);
908 0           $blkCnt -= $thisCnt;
909 0 0         return if ( $rs->{abort} );
910             }
911             #
912             # In case the reported file size was wrong, we need to
913             # send enough checksum data. It's not clear that sending
914             # zeros is right, but this shouldn't happen in any case.
915             #
916 0 0 0       if ( $nWrite > 0 && !$rs->{abort} ) {
917 0           $rs->writeData(pack("c", 0) x $nWrite);
918             }
919 0           $rs->{fio}->csumEnd;
920             }
921             }
922 0 0 0       if ( !@{$rs->{doList}} && $phase == 1 && $rs->{childDone} == 1 ) {
  0   0        
923             #
924             # end of phase 1
925             #
926 0           $rs->writeData(pack("V", 0xffffffff), 1);
927 0           $rs->{childDone} = 2;
928             }
929             #
930             # Now poll the pipe from the child to see if there are any
931             # files we need to redo on the second phase
932             #
933             # If there are no more files but we haven't seen "exit"
934             # from the child then block forever.
935             #
936 0 0         return if ( $rs->{abort} );
937 0 0 0       $rs->pollChild(($phase == 1 && !@{$rs->{doList}}) ? undef : 0);
938             }
939 0 0         if ( $phase == 0 ) {
940             #
941             # end of phase 0
942             #
943 0           $rs->writeData(pack("V", 0xffffffff), 1);
944 0           $rs->{doList} = $rs->{redoList};
945             }
946             }
947              
948             #
949             # See if there are any messges from the local child over the pipe.
950             # These could be logging messages or requests to repeat files.
951             #
952             sub pollChild
953             {
954 0     0 0   my($rs, $timeout) = @_;
955 0           my($FDread);
956              
957 0 0         return -1 if ( !defined($rs->{childFh}) );
958 0 0         $rs->log("pollChild($timeout)") if ( $rs->{logLevel} >= 12 );
959              
960 0           vec($FDread, fileno($rs->{childFh}), 1) = 1;
961 0           my $ein = $FDread;
962             #$rs->log("pollChild: select(timeout=$timeout)");
963 0           select(my $rout = $FDread, undef, $ein, $timeout);
964 0 0         return if ( !vec($rout, fileno($rs->{childFh}), 1) );
965             #$rs->log("pollChild: reading from child");
966 0           my $nbytes = sysread($rs->{childFh}, my $mesg, 65536);
967             #$rs->log("pollChild: done reading from child");
968 0 0         $rs->{childMesg} .= $mesg if ( $nbytes > 0 );
969 0 0         if ( $nbytes <= 0 ) {
970 0           close($rs->{childFh});
971 0           delete($rs->{childFh});
972 0 0         $rs->log("Parent read EOF from child: fatal error!")
973             if ( $rs->{logLevel} >= 1 );
974 0           $rs->{abort} = 1;
975 0           $rs->{fatalError} = 1;
976 0           $rs->{fatalErrorMsg} = "Child exited prematurely";
977 0           return -1;
978             }
979             #
980             # Process any complete lines of output from the child.
981             #
982             # Because some regexps are very slow in 5.8.0, this old code:
983             #
984             # while ( $rs->{childMesg} =~ /(.*?)[\n\r]+(.*)/s ) {
985             # $mesg = $1;
986             # $rs->{childMesg} = $2;
987             #
988             # was replaced with the split() below.
989             #
990 0           while ( $rs->{childMesg} =~ /[\n\r]/ ) {
991 0           ($mesg, $rs->{childMesg}) = split(/[\n\r]+/, $rs->{childMesg}, 2);
992 0 0         $rs->log("Parent read: $mesg")
993             if ( $rs->{logLevel} >= 20 );
994 0 0         if ( $mesg =~ /^done$/ ) {
    0          
    0          
    0          
    0          
995 0 0         $rs->log("Got done from child")
996             if ( $rs->{logLevel} >= 4 );
997 0           $rs->{childDone} = 1;
998             } elsif ( $mesg =~ /^stats (\d+) (\d+) (\d+) (\d+) (.*)/ ) {
999 0           $rs->{stats}{totalRead} = $1;
1000 0           $rs->{stats}{totalWritten} = $2;
1001 0           $rs->{stats}{totalSize} = $3;
1002 0           $rs->{stats}{remoteErrCnt} += $4;
1003 0           my %childStats = eval($5);
1004 0 0         $rs->log("Got stats: $1 $2 $3 $4 $5")
1005             if ( $rs->{logLevel} >= 4 );
1006 0           $rs->{stats}{childStats} = \%childStats;
1007 0           $rs->{stats}{parentStats} = $rs->{fio}->statsGet;
1008             } elsif ( $mesg =~ /^exit/ ) {
1009 0 0         $rs->log("Got exit from child") if ( $rs->{logLevel} >= 4 );
1010 0           $rs->{childDone} = 3;
1011             } elsif ( $mesg =~ /^redo (\d+)/ ) {
1012 0 0         if ( $rs->{phase} == 1 ) {
1013 0           push(@{$rs->{doList}}, $1);
  0            
1014             } else {
1015 0           push(@{$rs->{redoList}}, $1);
  0            
1016             }
1017 0 0         $rs->log("Got redo $1") if ( $rs->{logLevel} >= 4 );
1018             } elsif ( $mesg =~ /^log (.*)/ ) {
1019 0           $rs->log($1);
1020             } else {
1021 0           $rs->log("Don't understand '$mesg' from child");
1022             }
1023             }
1024             }
1025              
1026             sub fileCsumReceive
1027             {
1028 0     0 0   my($rs, $phase) = @_;
1029 0           my($fileNum, $blkCnt, $blkSize, $remainder);
1030 0 0         my $csumLen = $phase == 0 ? 2 : 16;
1031             #
1032             # delete list -> disabled by argv
1033             #
1034             # $rs->writeData(pack("V", 1));
1035             #
1036 0           while ( 1 ) {
1037 0 0         return -1 if ( $rs->getChunk(4) < 0 );
1038 0           $fileNum = unpack("V", $rs->{chunkData});
1039 0           $rs->{chunkData} = substr($rs->{chunkData}, 4);
1040 0 0         if ( $fileNum == 0xffffffff ) {
1041 0 0         $rs->log("Finished csumReceive")
1042             if ( $rs->{logLevel} >= 2 );
1043 0           last;
1044             }
1045 0           my $f = $rs->{fileList}->get($fileNum);
1046 0 0         next if ( !defined($f) );
1047 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
1048             if ( $rs->{clientCharset} ne "" );
1049 0 0         if ( $rs->{protocol_version} >= 27 ) {
1050 0 0         return -1 if ( $rs->getChunk(16) < 0 );
1051 0           my $thisCsumLen;
1052 0           ($blkCnt, $blkSize, $thisCsumLen, $remainder)
1053             = unpack("V4", $rs->{chunkData});
1054 0           $rs->{chunkData} = substr($rs->{chunkData}, 16);
1055             } else {
1056 0 0         return -1 if ( $rs->getChunk(12) < 0 );
1057 0           ($blkCnt, $blkSize, $remainder) = unpack("V3", $rs->{chunkData});
1058 0           $rs->{chunkData} = substr($rs->{chunkData}, 12);
1059             }
1060 0 0         $rs->log("Got #$fileNum ($f->{name}), blkCnt=$blkCnt,"
1061             . " blkSize=$blkSize, rem=$remainder")
1062             if ( $rs->{logLevel} >= 5 );
1063             #
1064             # For now we just check if the file is identical or not.
1065             # We don't do clever differential restores; we effectively
1066             # do --whole-file for sending to the remote machine.
1067             #
1068             # All this code needs to be replaced with proper file delta
1069             # generation...
1070             #
1071 0 0         next if ( ($f->{mode} & S_IFMT) != S_IFREG );
1072 0           $rs->{fio}->csumStart($f, 1, $blkSize, $phase);
1073 0           my $attr = $rs->{fio}->attribGet($f);
1074 0 0         my $fileSame = $attr->{size} == ($blkCnt > 0
1075             ? ($blkCnt - 1) * $blkSize + $remainder
1076             : 0);
1077 0           my $cnt = $blkCnt;
1078 0           while ( $cnt > 0 ) {
1079 0 0         my $thisCnt = $cnt > 256 ? 256 : $cnt;
1080 0           my $len = $thisCnt * ($csumLen + 4);
1081 0 0         my $csum = $rs->{fio}->csumGet($thisCnt, $csumLen, $blkSize)
1082             if ( $fileSame );
1083 0           $rs->getChunk($len);
1084 0           my $csumRem = unpack("a$len", $rs->{chunkData});
1085 0           $rs->{chunkData} = substr($rs->{chunkData}, $len);
1086 0 0         $fileSame = 0 if ( $csum ne $csumRem );
1087 0 0         $rs->log(sprintf(" got same=%d, local=%s, remote=%s",
1088             $fileSame, unpack("H*", $csum), unpack("H*", $csumRem)))
1089             if ( $rs->{logLevel} >= 8 );
1090 0           $cnt -= $thisCnt;
1091             }
1092              
1093 0           my $md4 = $rs->{fio}->csumEnd;
1094             #
1095             # Send the file number, numBlocks, blkSize and remainder
1096             # (based on the old file size)
1097             #
1098             ##$blkCnt = int(($attr->{size} + $blkSize - 1) / $blkSize);
1099             ##$remainder = $attr->{size} - ($blkCnt - 1) * $blkSize;
1100 0           $rs->write_sum_head($fileNum, $blkCnt, $blkSize, $csumLen, $remainder);
1101              
1102 0 0         if ( $fileSame ) {
1103 0 0         $rs->log("$f->{name}: unchanged") if ( $rs->{logLevel} >= 3 );
1104             #
1105             # The file is the same, so just send a bunch of block numbers
1106             #
1107 0           for ( my $blk = 1 ; $blk <= $blkCnt ; $blk++ ) {
1108 0           $rs->writeData(pack("V", -$blk));
1109             }
1110             } else {
1111             #
1112             # File doesn't match: send the file
1113             #
1114 0           $rs->{fio}->readStart($f);
1115 0           while ( 1 ) {
1116 0           my $dataR = $rs->{fio}->read(4 * 65536);
1117 0 0 0       last if ( !defined($dataR) || length($$dataR) == 0 );
1118 0           $rs->writeData(pack("V a*", length($$dataR), $$dataR));
1119             }
1120 0           $rs->{fio}->readEnd($f);
1121             }
1122              
1123             #
1124             # Send a final 0 and the MD4 file digest
1125             #
1126 0           $rs->writeData(pack("V a16", 0, $md4));
1127             }
1128              
1129             #
1130             # Indicate end of this phase
1131             #
1132 0           $rs->writeData(pack("V", 0xffffffff), 1);
1133             }
1134              
1135             sub fileDeltaGet
1136             {
1137 0     0 0   my($rs, $fh, $phase) = @_;
1138 0           my($fileNum, $blkCnt, $blkSize, $remainder, $len, $d, $token);
1139 0           my $fileStart = 0;
1140              
1141 0           while ( 1 ) {
1142 0 0         return -1 if ( $rs->getChunk(4) < 0 );
1143 0           $fileNum = unpack("V", $rs->{chunkData});
1144 0           $rs->{chunkData} = substr($rs->{chunkData}, 4);
1145 0 0         last if ( $fileNum == 0xffffffff );
1146              
1147             #
1148             # Make any intermediate dirs or special files
1149             #
1150 0 0         $rs->fileSpecialCreate($fileStart, $fileNum) if ( $phase == 0 );
1151 0           $fileStart = $fileNum + 1;
1152              
1153 0           my $f = $rs->{fileList}->get($fileNum);
1154 0 0         next if ( !defined($f) );
1155 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
1156             if ( $rs->{clientCharset} ne "" );
1157 0 0         if ( $rs->{protocol_version} >= 27 ) {
1158 0 0         return -1 if ( $rs->getChunk(16) < 0 );
1159 0           my $thisCsumLen;
1160 0           ($blkCnt, $blkSize, $thisCsumLen, $remainder)
1161             = unpack("V4", $rs->{chunkData});
1162 0           $rs->{chunkData} = substr($rs->{chunkData}, 16);
1163             } else {
1164 0 0         return -1 if ( $rs->getChunk(12) < 0 );
1165 0           ($blkCnt, $blkSize, $remainder) = unpack("V3", $rs->{chunkData});
1166 0           $rs->{chunkData} = substr($rs->{chunkData}, 12);
1167             }
1168 0 0         $rs->log("Starting file $fileNum ($f->{name}),"
1169             . " blkCnt=$blkCnt, blkSize=$blkSize, remainder=$remainder")
1170             if ( $rs->{logLevel} >= 5 );
1171 0           $rs->{fio}->fileDeltaRxStart($f, $blkCnt, $blkSize, $remainder);
1172            
1173 0           while ( 1 ) {
1174 0 0         return -1 if ( $rs->getChunk(4) < 0 );
1175 0           $len = unpack("V", $rs->{chunkData});
1176 0           $rs->{chunkData} = substr($rs->{chunkData}, 4);
1177 0 0         if ( $len == 0 ) {
    0          
1178 0 0         return -1 if ( $rs->getChunk(16) < 0 );
1179 0           my $md4digest = unpack("a16", $rs->{chunkData});
1180 0           $rs->{chunkData} = substr($rs->{chunkData}, 16);
1181 0   0       my $ret = $rs->{fio}->fileDeltaRxNext(undef, undef)
1182             || $rs->{fio}->fileDeltaRxDone($md4digest, $phase);
1183 0 0         if ( $ret == 1 ) {
1184 0 0         if ( $phase == 1 ) {
1185 0           $rs->log("MD4 does't agree: fatal error on #$fileNum ($f->{name})");
1186 0           last;
1187             }
1188 0 0         $rs->log("Must redo $fileNum ($f->{name})\n")
1189             if ( $rs->{logLevel} >= 2 );
1190 0           print($fh "redo $fileNum\n");
1191             }
1192 0           last;
1193             } elsif ( $len > 0x80000000 ) {
1194 0           $len = 0xffffffff - $len;
1195 0           my $ret = $rs->{fio}->fileDeltaRxNext($len, undef);
1196             } else {
1197 0 0         return -1 if ( $rs->getChunk($len) < 0 );
1198 0           $d = unpack("a$len", $rs->{chunkData});
1199 0           $rs->{chunkData} = substr($rs->{chunkData}, $len);
1200 0           my $ret = $rs->{fio}->fileDeltaRxNext(undef, $d);
1201             }
1202             }
1203              
1204             #
1205             # If this is 2nd phase, then set the attributes just for this file
1206             #
1207 0 0         $rs->{fio}->attribSet($f, 1) if ( $phase == 1 );
1208             }
1209             #
1210             # Make any remaining dirs or special files
1211             #
1212 0 0         $rs->fileSpecialCreate($fileStart, undef) if ( $phase == 0 );
1213              
1214 0 0         $rs->log("Finished deltaGet phase $phase") if ( $rs->{logLevel} >= 2 );
1215              
1216             #
1217             # Finish up hardlinks at the very end
1218             #
1219 0 0 0       if ( $phase == 1 && $rs->{rsyncOpts}{"hard-links"} ) {
1220 0           my $cnt = $rs->{fileList}->count;
1221 0           for ( my $n = 0 ; $n < $cnt ; $n++ ) {
1222 0           my $f = $rs->{fileList}->get($n);
1223 0 0         next if ( !defined($f) );
1224 0 0 0       next if ( !defined($f->{hlink}) || $f->{hlink_self} );
1225 0 0         if ( $rs->{clientCharset} ne "" ) {
1226 0           from_to($f->{name}, $rs->{clientCharset}, "utf8");
1227 0           from_to($f->{hlink}, $rs->{clientCharset}, "utf8");
1228             }
1229 0 0         if ( $rs->{fio}->makeHardLink($f, 1) ) {
1230 0           $rs->log("Error: makeHardlink($f->{name} -> $f->{hlink}) failed");
1231             }
1232             }
1233             }
1234             }
1235              
1236             sub fileListSend
1237             {
1238 0     0 0   my($rs) = @_;
1239              
1240 0           $rs->{fileList} = File::RsyncP::FileList->new({
1241             preserve_uid => $rs->{rsyncOpts}{owner},
1242             preserve_gid => $rs->{rsyncOpts}{group},
1243             preserve_links => $rs->{rsyncOpts}{links},
1244             preserve_devices => $rs->{rsyncOpts}{devices},
1245             preserve_hard_links => $rs->{rsyncOpts}{"hard-links"},
1246             always_checksum => $rs->{rsyncOpts}{checksum},
1247             protocol_version => $rs->{protocol_version},
1248             });
1249              
1250 0 0         if ( $rs->{rsyncOpts}{"hard-links"} ) {
1251 0           $rs->{fileList}->init_hard_links();
1252             }
1253              
1254 0     0     $rs->{fio}->fileListSend($rs->{fileList}, sub { $rs->writeData($_[0]); });
  0            
1255              
1256             #
1257             # Send trailing null byte to indicate end of file list
1258             #
1259 0           $rs->writeData(pack("C", 0));
1260              
1261             #
1262             # Send io_error flag
1263             #
1264 0           $rs->writeData(pack("V", 0), 1);
1265              
1266             #
1267             # At this point io buffering should be switched off
1268             #
1269             # Sort and clean the file list
1270             #
1271 0           $rs->{fileList}->clean;
1272              
1273             #
1274             # Print out the sorted file list
1275             #
1276 0 0         if ( $rs->{logLevel} >= 4 ) {
1277 0           my $cnt = $rs->{fileList}->count;
1278 0           $rs->log("Sorted file list has $cnt entries");
1279 0           for ( my $n = 0 ; $n < $cnt ; $n++ ) {
1280 0           my $f = $rs->{fileList}->get($n);
1281 0 0         next if ( !defined($f) );
1282 0 0         from_to($f->{name}, $rs->{clientCharset}, "utf8")
1283             if ( $rs->{clientCharset} ne "" );
1284 0           $rs->log("PostSortFile $n: $f->{name}");
1285             }
1286             }
1287             }
1288              
1289             sub write_sum_head
1290             {
1291 0     0 0   my($rs, $fileNum, $blkCnt, $blkSize, $csumLen, $remainder) = @_;
1292              
1293 0 0         if ( $rs->{protocol_version} >= 27 ) {
1294             #
1295             # For protocols >= 27 we also send the csum length
1296             # for this file.
1297             #
1298 0           $rs->writeData(pack("V5",
1299             $fileNum,
1300             $blkCnt,
1301             $blkSize,
1302             $csumLen,
1303             $remainder), 0);
1304             } else {
1305 0           $rs->writeData(pack("V4",
1306             $fileNum,
1307             $blkCnt,
1308             $blkSize,
1309             $remainder), 0);
1310             }
1311             }
1312              
1313             sub abort
1314             {
1315 0     0 1   my($rs, $reason, $timeout) = @_;
1316              
1317 0           $rs->{abort} = 1;
1318 0 0         $rs->{timeout} = $timeout if ( defined($timeout) );
1319 0   0       $rs->{abortReason} = $reason || "aborted by user request";
1320 0 0         kill($rs->{sigName2Num}{ALRM}, $rs->{childPID})
1321             if ( defined($rs->{childPID}) );
1322 0 0         alarm($rs->{timeout}) if ( $rs->{timeout} );
1323             }
1324              
1325             sub statsGet
1326             {
1327 0     0 0   my($rs, $fh) = @_;
1328              
1329 0           my($totalWritten, $totalRead, $totalSize) = (0, 0, 0);
1330              
1331 0 0         if ( $rs->getChunk(12) >= 0 ) {
1332 0           ($totalWritten, $totalRead, $totalSize)
1333             = unpack("V3", $rs->{chunkData});
1334             }
1335            
1336 0 0         if ( defined($fh) ) {
1337 0           my $fioStats = $rs->{fio}->statsGet;
1338 0           my $dump = Data::Dumper->new([$fioStats], [qw(*fioStats)]);
1339 0           $dump->Terse(1);
1340 0           $dump->Indent(0);
1341 0           my $remoteErrCnt = 0 + $rs->{stats}{remoteErrCnt};
1342 0           print($fh "stats $totalWritten $totalRead $totalSize $remoteErrCnt ",
1343             $dump->Dump, "\n");
1344             } else {
1345 0           $rs->{stats}{totalRead} = $totalRead;
1346 0           $rs->{stats}{totalWritten} = $totalWritten;
1347 0           $rs->{stats}{totalSize} = $totalSize;
1348 0           $rs->{stats}{fioStats} = $rs->{fio}->statsGet;
1349             }
1350             }
1351              
1352             sub getData
1353             {
1354 0     0 0   my($rs, $len) = @_;
1355 0           my($data);
1356              
1357 0 0         return -1 if ( $rs->{abort} );
1358 0 0         alarm($rs->{timeout}) if ( $rs->{timeout} );
1359 0           while ( length($rs->{readData}) < $len ) {
1360 0 0         return -1 if ( $rs->{abort} );
1361 0           my $ein;
1362 0           vec($ein, fileno($rs->{fh}), 1) = 1;
1363 0           select(my $rout = $ein, undef, $ein, undef);
1364 0 0         return -1 if ( $rs->{abort} );
1365 0           sysread($rs->{fh}, $data, 65536);
1366 0 0         if ( length($data) == 0 ) {
1367 0 0         $rs->log("Read EOF: $!") if ( $rs->{logLevel} >= 1 );
1368 0 0         return -1 if ( $rs->{abort} );
1369 0           sysread($rs->{fh}, $data, 65536);
1370 0 0         $rs->log(sprintf("Tried again: got %d bytes", length($data)))
1371             if ( $rs->{logLevel} >= 1 );
1372 0           $rs->{abort} = 1;
1373 0           $rs->{fatalError} = 1;
1374 0           $rs->{fatalErrorMsg} = "Unable to read $len bytes";
1375 0           return -1;
1376             }
1377 0 0         if ( $rs->{logLevel} >= 10 ) {
1378 0           $rs->log("Receiving: " . unpack("H*", $data));
1379             }
1380 0           $rs->{readData} .= $data;
1381             }
1382             }
1383              
1384             sub getChunk
1385             {
1386 0     0 0   my($rs, $len) = @_;
1387              
1388 0   0       $len ||= 1;
1389 0           while ( length($rs->{chunkData}) < $len ) {
1390 0 0         return -1 if ( $rs->getData(4) < 0 );
1391 0           my $d = unpack("V", $rs->{readData});
1392 0           $rs->{readData} = substr($rs->{readData}, 4);
1393 0           my $code = ($d >> 24) - 7;
1394 0           my $len = $d & 0xffffff;
1395 0 0         return -1 if ( $rs->getData($len) < 0 );
1396 0           $d = substr($rs->{readData}, 0, $len);
1397 0           $rs->{readData} = substr($rs->{readData}, $len);
1398 0 0         if ( $code == 0 ) {
1399 0           $rs->{chunkData} .= $d;
1400             } else {
1401 0           $d =~ s/[\n\r]+$//;
1402 0 0         from_to($d, $rs->{clientCharset}, "utf8")
1403             if ( $rs->{clientCharset} ne "" );
1404 0           $rs->log("Remote[$code]: $d");
1405 0 0 0       if ( $code == 1
1406             || $d =~ /^file has vanished: /
1407             ) {
1408 0           $rs->{stats}{remoteErrCnt}++
1409             }
1410             }
1411             }
1412             }
1413              
1414             sub getLine
1415             {
1416 0     0 0   my($rs) = @_;
1417              
1418 0           while ( 1 ) {
1419 0 0         if ( $rs->{readData} =~ /(.*?)[\n\r]+(.*)/s ) {
1420 0           $rs->{readData} = $2;
1421 0           return $1;
1422             }
1423 0 0         return if ( $rs->getData(length($rs->{readData}) + 1) < 0 );
1424             }
1425             }
1426              
1427             sub writeData
1428             {
1429 0     0 0   my($rs, $data, $flush) = @_;
1430              
1431 0           $rs->{writeBuf} .= $data;
1432 0 0 0       $rs->writeFlush() if ( $flush || length($rs->{writeBuf}) > 32768 );
1433             }
1434              
1435             sub statsFinal
1436             {
1437 0     0 1   my($rs) = @_;
1438              
1439 0 0         $rs->{stats}{parentStats} = $rs->{fio}->statsGet
1440             if ( !defined($rs->{stats}{parentStats}) );
1441 0           return $rs->{stats};
1442             }
1443              
1444             sub writeFlush
1445             {
1446 0     0 0   my($rs) = @_;
1447              
1448 0           my($FDread, $FDwrite);
1449              
1450 0 0         return if ( $rs->{abort} );
1451 0 0         alarm($rs->{timeout}) if ( $rs->{timeout} );
1452 0           while ( $rs->{writeBuf} ne "" ) {
1453             #(my $chunk, $rs->{writeBuf}) = unpack("a4092 a*", $rs->{writeBuf});
1454             #$chunk = pack("V", (7 << 24) | length($chunk)) . $chunk;
1455 0 0         vec($FDread, fileno($rs->{childFh}), 1) = 1
1456             if ( defined($rs->{childFh}) );
1457 0           vec($FDwrite, fileno($rs->{fh}), 1) = 1;
1458 0           my $ein = $FDread;
1459 0           vec($ein, fileno($rs->{fh}), 1) = 1;
1460 0           select(my $rout = $FDread, my $rwrite = $FDwrite, $ein, undef);
1461 0 0 0       if ( defined($rs->{childFh})
1462             && vec($rout, fileno($rs->{childFh}), 1) ) {
1463 0           $rs->pollChild(0);
1464             }
1465 0 0         return if ( $rs->{abort} );
1466 0 0         if ( vec($rwrite, fileno($rs->{fh}), 1) ) {
1467 0           my $n = syswrite($rs->{fh}, $rs->{writeBuf});
1468 0 0         if ( $n <= 0 ) {
1469 0           return $rs->log(sprintf("Can't write %d bytes to socket",
1470             length($rs->{writeBuf})));
1471             }
1472 0 0         if ( $rs->{logLevel} >= 10 ) {
1473 0           my $chunk = substr($rs->{writeBuf}, 0, $n);
1474 0           $rs->log("Sending: " . unpack("H*", $chunk));
1475             }
1476 0           $rs->{writeBuf} = substr($rs->{writeBuf}, $n);
1477             }
1478             }
1479             }
1480              
1481             #
1482             # Default log handler
1483             #
1484             sub logHandler
1485             {
1486 0     0 1   my($str) = @_;
1487              
1488 0           print(STDERR $str, "\n");
1489             }
1490              
1491             sub log
1492             {
1493 0     0 0   my($rs, @logStr) = @_;
1494              
1495 0           foreach my $str ( @logStr ) {
1496 0 0         next if ( $str eq "" );
1497 0           $rs->{logHandler}->($str);
1498             }
1499             }
1500              
1501             #
1502             # Escape shell meta-characters with backslashes.
1503             # This should be applied to each argument seperately, not an
1504             # entire shell command.
1505             #
1506             sub shellEscape
1507             {
1508 0     0 0   my($self, $cmd) = @_;
1509              
1510 0           $cmd =~ s/([][;&()<>{}|^\n\r\t *\$\\'"`?])/\\$1/g;
1511 0           return $cmd;
1512             }
1513              
1514             1;
1515              
1516             __END__