a6d61a1c9ff51af17b94a25e0b08e16c87f06bee
[BackupPC.git] / lib / BackupPC / Xfer / Rsync.pm
1 #============================================================= -*-perl-*-
2 #
3 # BackupPC::Xfer::Rsync package
4 #
5 # DESCRIPTION
6 #
7 #   This library defines a BackupPC::Xfer::Rsync class for managing
8 #   the rsync-based transport of backup data from the client.
9 #
10 # AUTHOR
11 #   Craig Barratt  <cbarratt@users.sourceforge.net>
12 #
13 # COPYRIGHT
14 #   Copyright (C) 2002  Craig Barratt
15 #
16 #   This program is free software; you can redistribute it and/or modify
17 #   it under the terms of the GNU General Public License as published by
18 #   the Free Software Foundation; either version 2 of the License, or
19 #   (at your option) any later version.
20 #
21 #   This program is distributed in the hope that it will be useful,
22 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
23 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 #   GNU General Public License for more details.
25 #
26 #   You should have received a copy of the GNU General Public License
27 #   along with this program; if not, write to the Free Software
28 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29 #
30 #========================================================================
31 #
32 # Version 1.6.0_CVS, released 10 Dec 2002.
33 #
34 # See http://backuppc.sourceforge.net.
35 #
36 #========================================================================
37
38 package BackupPC::Xfer::Rsync;
39
40 use strict;
41 use BackupPC::View;
42 use BackupPC::Xfer::RsyncFileIO;
43
44 use vars qw( $RsyncLibOK );
45
46 BEGIN {
47     eval "use File::RsyncP;";
48     if ( $@ ) {
49         #
50         # Rsync module doesn't exist.
51         #
52         $RsyncLibOK = 0;
53     } else {
54         $RsyncLibOK = 1;
55     }
56 };
57
58 sub new
59 {
60     my($class, $bpc, $args) = @_;
61
62     return if ( !$RsyncLibOK );
63     $args ||= {};
64     my $t = bless {
65         bpc       => $bpc,
66         conf      => { $bpc->Conf },
67         host      => "",
68         hostIP    => "",
69         shareName => "",
70         badFiles  => [],
71         %$args,
72     }, $class;
73
74     return $t;
75 }
76
77 sub args
78 {
79     my($t, $args) = @_;
80
81     foreach my $arg ( keys(%$args) ) {
82         $t->{$arg} = $args->{$arg};
83     }
84 }
85
86 sub useTar
87 {
88     return 0;
89 }
90
91 sub start
92 {
93     my($t) = @_;
94     my $bpc = $t->{bpc};
95     my $conf = $t->{conf};
96     my(@fileList, @rsyncClientCmd, $logMsg, $incrDate);
97
98     if ( $t->{type} eq "restore" ) {
99         # TODO
100         #push(@rsyncClientCmd, split(/ +/, $c o n f->{RsyncClientRestoreCmd}));
101         $logMsg = "restore not supported for $t->{shareName}";
102         #
103         # restores are considered to work unless we see they fail
104         # (opposite to backups...)
105         #
106         $t->{xferOK} = 1;
107     } else {
108         #
109         # Turn $conf->{BackupFilesOnly} and $conf->{BackupFilesExclude}
110         # into a hash of arrays of files.  NOT IMPLEMENTED YET.
111         #
112         $conf->{RsyncShareName} = [ $conf->{RsyncShareName} ]
113                         unless ref($conf->{RsyncShareName}) eq "ARRAY";
114         foreach my $param qw(BackupFilesOnly BackupFilesExclude) {
115             next if ( !defined($conf->{$param}) );
116             if ( ref($conf->{$param}) eq "ARRAY" ) {
117                 $conf->{$param} = {
118                         $conf->{RsyncShareName}[0] => $conf->{$param}
119                 };
120             } elsif ( ref($conf->{$param}) eq "HASH" ) {
121                 # do nothing
122             } else {
123                 $conf->{$param} = {
124                         $conf->{RsyncShareName}[0] => [ $conf->{$param} ]
125                 };
126             }
127         }
128         if ( defined($conf->{BackupFilesExclude}{$t->{shareName}}) ) {
129             foreach my $file ( @{$conf->{BackupFilesExclude}{$t->{shareName}}} )
130             {
131                 push(@fileList, "--exclude=$file");
132             }
133         }
134         if ( defined($conf->{BackupFilesOnly}{$t->{shareName}}) ) {
135             foreach my $file ( @{$conf->{BackupFilesOnly}{$t->{shareName}}} ) {
136                 push(@fileList, $file);
137             }
138         } else {
139             push(@fileList, ".");
140         }
141         push(@rsyncClientCmd, split(/ +/, $conf->{RsyncClientCmd}));
142         if ( $t->{type} eq "full" ) {
143             $logMsg = "full backup started for directory $t->{shareName}";
144         } else {
145             $incrDate = $bpc->timeStampISO($t->{lastFull} - 3600, 1);
146             $logMsg = "incr backup started back to $incrDate for directory"
147                     . " $t->{shareName}";
148         }
149         $t->{xferOK} = 0;
150     }
151     #
152     # Merge variables into @rsyncClientCmd
153     #
154     my $vars = {
155         host      => $t->{host},
156         hostIP    => $t->{hostIP},
157         shareName => $t->{shareName},
158         rsyncPath => $conf->{RsyncClientPath},
159         sshPath   => $conf->{SshPath},
160     };
161     my @cmd = @rsyncClientCmd;
162     @rsyncClientCmd = ();
163     foreach my $arg ( @cmd ) {
164         next if ( $arg =~ /^\s*$/ );
165         if ( $arg =~ /^\$fileList(\+?)/ ) {
166             my $esc = $1 eq "+";
167             foreach $arg ( @fileList ) {
168                 $arg = $bpc->shellEscape($arg) if ( $esc );
169                 push(@rsyncClientCmd, $arg);
170             }
171         } elsif ( $arg =~ /^\$argList(\+?)/ ) {
172             my $esc = $1 eq "+";
173             foreach $arg ( (@{$conf->{RsyncArgs}},
174                             @{$conf->{RsyncClientArgs}}) ) {
175                 $arg = $bpc->shellEscape($arg) if ( $esc );
176                 push(@rsyncClientCmd, $arg);
177             }
178         } else {
179             $arg =~ s{\$(\w+)(\+?)}{
180                 defined($vars->{$1})
181                     ? ($2 eq "+" ? $bpc->shellEscape($vars->{$1}) : $vars->{$1})
182                     : "\$$1"
183             }eg;
184             push(@rsyncClientCmd, $arg);
185         }
186     }
187
188     #
189     # A full dump is implemented with --ignore-times: this causes all
190     # files to be checksummed, even if the attributes are the same.
191     # That way all the file contents are checked, but you get all
192     # the efficiencies of rsync: only files deltas need to be
193     # transferred, even though it is a full dump.
194     #
195     my $rsyncArgs = $conf->{RsyncArgs};
196     $rsyncArgs = [@$rsyncArgs, "--ignore-times"] if ( $t->{type} eq "full" );
197
198     #
199     # Create the Rsync object, and tell it to use our own File::RsyncP::FileIO
200     # module, which handles all the special BackupPC file storage
201     # (compression, mangling, hardlinks, special files, attributes etc).
202     #
203     $t->{rs} = File::RsyncP->new({
204         logLevel   => $conf->{RsyncLogLevel},
205         rsyncCmd   => \@rsyncClientCmd,
206         rsyncArgs  => $rsyncArgs,
207         logHandler => sub {
208                           my($str) = @_;
209                           $str .= "\n";
210                           $t->{XferLOG}->write(\$str);
211                       },
212         fio        => BackupPC::Xfer::RsyncFileIO->new({
213                             xfer       => $t,
214                             bpc        => $t->{bpc},
215                             conf       => $t->{conf},
216                             host       => $t->{host},
217                             backups    => $t->{backups},
218                             logLevel   => $conf->{RsyncLogLevel},
219                       }),
220     });
221
222     # TODO: alarm($conf->{SmbClientTimeout});
223     delete($t->{_errStr});
224
225     return $logMsg;
226 }
227
228 sub run
229 {
230     my($t) = @_;
231     my $rs = $t->{rs};
232     my $conf = $t->{conf};
233
234     if ( $t->{XferMethod} eq "rsync" ) {
235         #
236         # Run rsync command
237         #
238         $rs->remoteStart(1, $t->{shareName});
239     } else {
240         #
241         # Connect to the rsync server
242         #
243         if ( defined(my $err = $rs->serverConnect($t->{hostIP},
244                                              $conf->{RsyncdClientPort})) ) {
245             $t->{hostError} = $err;
246             return;
247         }
248         if ( defined(my $err = $rs->serverService($t->{shareName},
249                                                  "craig", "xyz123", 0)) ) {
250             $t->{hostError} = $err;
251             return;
252         }
253         $rs->serverStart(1, ".");
254     }
255     my $error = $rs->go($t->{shareName});
256     $rs->serverClose();
257
258     #
259     # TODO: generate sensible stats
260     # 
261     # $rs->{stats}{totalWritten}
262     # $rs->{stats}{totalSize}
263     #
264     # qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt
265     #           xferOK hostAbort hostError lastOutputLine)
266     #
267     my $stats = $rs->statsFinal;
268     if ( !defined($error) && defined($stats) ) {
269         $t->{xferOK}  = 1;
270     } else {
271         $t->{xferOK}  = 0;
272     }
273     $t->{byteCnt} = $stats->{childStats}{TotalFileSize}
274                   + $stats->{parentStats}{TotalFileSize};
275     $t->{fileCnt} = $stats->{childStats}{TotalFileCnt}
276                   + $stats->{parentStats}{TotalFileCnt};
277     #
278     # TODO: get error count, and call fio to get stats...
279     #
280     $t->{hostError} = $error if ( defined($error) );
281
282     return (
283         0,
284         $stats->{childStats}{ExistFileCnt}
285             + $stats->{parentStats}{ExistFileCnt},
286         $stats->{childStats}{ExistFileSize}
287             + $stats->{parentStats}{ExistFileSize},
288         $stats->{childStats}{ExistFileCompSize}
289             + $stats->{parentStats}{ExistFileCompSize},
290         $stats->{childStats}{TotalFileCnt}
291             + $stats->{parentStats}{TotalFileCnt},
292         $stats->{childStats}{TotalFileSize}
293             + $stats->{parentStats}{TotalFileSize},
294     );
295 }
296
297 #        alarm($conf->{SmbClientTimeout});
298
299 sub setSelectMask
300 {
301     my($t, $FDreadRef) = @_;
302 }
303
304 sub errStr
305 {
306     my($t) = @_;
307
308     return $t->{_errStr};
309 }
310
311 sub xferPid
312 {
313     my($t) = @_;
314
315     return -1;
316 }
317
318 sub logMsg
319 {
320     my($t, $msg) = @_;
321
322     push(@{$t->{_logMsg}}, $msg);
323 }
324
325 sub logMsgGet
326 {
327     my($t) = @_;
328
329     return shift(@{$t->{_logMsg}});
330 }
331
332 #
333 # Returns a hash ref giving various status information about
334 # the transfer.
335 #
336 sub getStats
337 {
338     my($t) = @_;
339
340     return { map { $_ => $t->{$_} }
341             qw(byteCnt fileCnt xferErrCnt xferBadShareCnt xferBadFileCnt
342                xferOK hostAbort hostError lastOutputLine)
343     };
344 }
345
346 sub getBadFiles
347 {
348     my($t) = @_;
349
350     return @{$t->{badFiles}};
351 }
352
353 1;