File Coverage

/root/.cpan/build/File-RsyncP-0.76-0/blib/lib/File/RsyncP.pm
Criterion Covered Total %
statement 57 756 7.5
branch 0 472 0.0
condition 0 172 0.0
subroutine 19 64 29.6
pod 9 32 28.1
total 85 1496 5.6


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