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