1 |
#!/bin/perl |
2 |
#============================================================= -*-perl-*- |
3 |
# |
4 |
# BackupPC_restore: Restore files to a client. |
5 |
# |
6 |
# DESCRIPTION |
7 |
# |
8 |
# Usage: BackupPC_restore <hostIP> <client> <reqFileName> |
9 |
# |
10 |
# AUTHOR |
11 |
# Craig Barratt <cbarratt@users.sourceforge.net> |
12 |
# |
13 |
# COPYRIGHT |
14 |
# Copyright (C) 2001-2003 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 2.1.0, released 20 Jun 2004. |
33 |
# |
34 |
# See http://backuppc.sourceforge.net. |
35 |
# |
36 |
#======================================================================== |
37 |
|
38 |
use strict; |
39 |
no utf8; |
40 |
use lib "__INSTALLDIR__/lib"; |
41 |
use BackupPC::Lib; |
42 |
use BackupPC::FileZIO; |
43 |
use BackupPC::Xfer::Smb; |
44 |
use BackupPC::Xfer::Tar; |
45 |
use BackupPC::Xfer::Rsync; |
46 |
use Socket; |
47 |
|
48 |
use File::Path; |
49 |
use Getopt::Std; |
50 |
|
51 |
use vars qw( %RestoreReq ); |
52 |
|
53 |
########################################################################### |
54 |
# Initialize |
55 |
########################################################################### |
56 |
|
57 |
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); |
58 |
my $TopDir = $bpc->TopDir(); |
59 |
my $BinDir = $bpc->BinDir(); |
60 |
my %Conf = $bpc->Conf(); |
61 |
my $NeedPostCmd; |
62 |
|
63 |
my($hostIP, $host, $client, $reqFileName, %stat); |
64 |
|
65 |
$bpc->ChildInit(); |
66 |
|
67 |
if ( @ARGV != 3 ) { |
68 |
print("usage: $0 <hostIP> <client> <reqFileName>\n"); |
69 |
exit(1); |
70 |
} |
71 |
$hostIP = $1 if ( $ARGV[0] =~ /(.+)/ ); |
72 |
$client = $1 if ( $ARGV[1] =~ /(.+)/ ); |
73 |
if ( $ARGV[2] !~ /^([\w.]+)$/ ) { |
74 |
print("$0: bad reqFileName (arg #3): $ARGV[2]\n"); |
75 |
exit(1); |
76 |
} |
77 |
$reqFileName = $1; |
78 |
|
79 |
my $startTime = time(); |
80 |
|
81 |
my $Hosts = $bpc->HostInfoRead($client); |
82 |
|
83 |
my $Dir = "$TopDir/pc/$client"; |
84 |
my @xferPid = (); |
85 |
my $tarPid = -1; |
86 |
|
87 |
# |
88 |
# Catch various signals |
89 |
# |
90 |
$SIG{INT} = \&catch_signal; |
91 |
$SIG{ALRM} = \&catch_signal; |
92 |
$SIG{TERM} = \&catch_signal; |
93 |
$SIG{PIPE} = \&catch_signal; |
94 |
$SIG{STOP} = \&catch_signal; |
95 |
$SIG{TSTP} = \&catch_signal; |
96 |
$SIG{TTIN} = \&catch_signal; |
97 |
my $Pid = $$; |
98 |
|
99 |
mkpath($Dir, 0, 0777) if ( !-d $Dir ); |
100 |
if ( !-f "$Dir/LOCK" ) { |
101 |
open(LOCK, ">", "$Dir/LOCK") && close(LOCK); |
102 |
} |
103 |
open(LOG, ">>", "$Dir/LOG"); |
104 |
select(LOG); $| = 1; select(STDOUT); |
105 |
|
106 |
|
107 |
# |
108 |
# Read the request file |
109 |
# |
110 |
if ( !(my $ret = do "$Dir/$reqFileName") ) { |
111 |
my $err; |
112 |
if ( $@ ) { |
113 |
$err = "couldn't parse $Dir/$reqFileName: $@"; |
114 |
} elsif ( !defined($ret) ) { |
115 |
$err = "couldn't do $Dir/$reqFileName: $!"; |
116 |
} else { |
117 |
$err = "couldn't run $Dir/$reqFileName"; |
118 |
} |
119 |
$stat{hostError} = $err; |
120 |
exit(RestoreCleanup($client)); |
121 |
} |
122 |
|
123 |
# |
124 |
# Re-read config file, so we can include the PC-specific config |
125 |
# |
126 |
if ( defined(my $error = $bpc->ConfigRead($client)) ) { |
127 |
$stat{hostError} = "Can't read PC's config file: $error"; |
128 |
exit(RestoreCleanup($client)); |
129 |
} |
130 |
%Conf = $bpc->Conf(); |
131 |
|
132 |
# |
133 |
# Make sure we eventually timeout if there is no activity from |
134 |
# the data transport program. |
135 |
# |
136 |
alarm($Conf{ClientTimeout}); |
137 |
|
138 |
# |
139 |
# See if the host name is aliased |
140 |
# |
141 |
if ( $Conf{ClientNameAlias} ne "" ) { |
142 |
$host = $Conf{ClientNameAlias}; |
143 |
} else { |
144 |
$host = $client; |
145 |
} |
146 |
|
147 |
# |
148 |
# Find its IP address |
149 |
# |
150 |
if ( $hostIP !~ /\d+\.\d+\.\d+\.\d+/ ) { |
151 |
if ( !defined(gethostbyname($host)) ) { |
152 |
# |
153 |
# Ok, NS doesn't know about it. Maybe it is a NetBios name |
154 |
# instead. |
155 |
# |
156 |
if ( !defined($hostIP = $bpc->NetBiosHostIPFind($host)) ) { |
157 |
$stat{hostError} = "Can't find host $host"; |
158 |
exit(RestoreCleanup($client)); |
159 |
} |
160 |
} else { |
161 |
$hostIP = $host; |
162 |
} |
163 |
} |
164 |
|
165 |
# |
166 |
# Check if $host is alive |
167 |
# |
168 |
my $delay = $bpc->CheckHostAlive($hostIP); |
169 |
if ( $delay < 0 ) { |
170 |
$stat{hostError} = "no ping response from $host ($hostIP)"; |
171 |
exit(RestoreCleanup($client)); |
172 |
} elsif ( $delay > $Conf{PingMaxMsec} ) { |
173 |
$stat{hostError} = sprintf("ping too slow: %.4gmsec (max is %gmsec)\n", |
174 |
$delay, $Conf{PingMaxMsec}); |
175 |
exit(RestoreCleanup($client)); |
176 |
} |
177 |
|
178 |
# |
179 |
# Make sure it is really the machine we expect |
180 |
# |
181 |
if ( (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { |
182 |
$stat{hostError} = $errMsg; |
183 |
exit(RestoreCleanup($client)); |
184 |
} |
185 |
|
186 |
# |
187 |
# Setup file extension for compression and open RestoreLOG output file |
188 |
# |
189 |
$Conf{CompressLevel} = 0 if ( !BackupPC::FileZIO->compOk ); |
190 |
my $fileExt = $Conf{CompressLevel} > 0 ? ".z" : ""; |
191 |
my $RestoreLOG = BackupPC::FileZIO->open("$Dir/RestoreLOG$fileExt", 1, |
192 |
$Conf{CompressLevel}); |
193 |
my $tarCreateFileCnt = 0; |
194 |
my $tarCreateByteCnt = 0; |
195 |
my $tarCreateErrCnt = 1; # assume not ok until we learn otherwise |
196 |
my $tarCreateErr; |
197 |
my($logMsg, $xfer); |
198 |
|
199 |
$stat{xferOK} = $stat{hostAbort} = undef; |
200 |
$stat{hostError} = $stat{lastOutputLine} = undef; |
201 |
local(*RH, *WH); |
202 |
|
203 |
# |
204 |
# Run an optional pre-restore command |
205 |
# |
206 |
UserCommandRun("RestorePreUserCmd"); |
207 |
$NeedPostCmd = 1; |
208 |
|
209 |
if ( $Conf{XferMethod} eq "tar" ) { |
210 |
# |
211 |
# Use tar (eg: tar/ssh) as the transport program. |
212 |
# |
213 |
$xfer = BackupPC::Xfer::Tar->new($bpc); |
214 |
} elsif ( $Conf{XferMethod} eq "rsync" || $Conf{XferMethod} eq "rsyncd" ) { |
215 |
# |
216 |
# Use rsync as the transport program. |
217 |
# |
218 |
if ( !defined($xfer = BackupPC::Xfer::Rsync->new($bpc)) ) { |
219 |
my $errStr = BackupPC::Xfer::Rsync->errStr; |
220 |
UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); |
221 |
$stat{hostError} = $errStr; |
222 |
exit(RestoreCleanup($client)); |
223 |
} |
224 |
} else { |
225 |
# |
226 |
# Default is to use smbclient (smb) as the transport program. |
227 |
# |
228 |
$xfer = BackupPC::Xfer::Smb->new($bpc); |
229 |
} |
230 |
my $useTar = $xfer->useTar; |
231 |
|
232 |
if ( $useTar ) { |
233 |
# |
234 |
# Create a socketpair to connect BackupPC_tarCreate to the transport |
235 |
# program (smbclient, tar, etc). |
236 |
# WH is the write handle for writing, provided to BackupPC_tarCreate |
237 |
# and RH is the other end of the pipe for reading provided to the |
238 |
# transport program. |
239 |
# |
240 |
if ( socketpair(RH, WH, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) { |
241 |
shutdown(RH, 1); # no writing to this socket |
242 |
shutdown(WH, 0); # no reading from this socket |
243 |
setsockopt(RH, SOL_SOCKET, SO_RCVBUF, 8 * 65536); |
244 |
setsockopt(WH, SOL_SOCKET, SO_SNDBUF, 8 * 65536); |
245 |
} else { |
246 |
# |
247 |
# Default to pipe() if socketpair() doesn't work. |
248 |
# |
249 |
pipe(RH, WH); |
250 |
} |
251 |
} |
252 |
|
253 |
# |
254 |
# Run the transport program, which reads from RH and extracts the data. |
255 |
# |
256 |
my @Backups = $bpc->BackupInfoRead($RestoreReq{hostSrc}); |
257 |
my $xferArgs = { |
258 |
client => $client, |
259 |
host => $host, |
260 |
hostIP => $hostIP, |
261 |
type => "restore", |
262 |
shareName => $RestoreReq{shareDest}, |
263 |
pipeRH => *RH, |
264 |
pipeWH => *WH, |
265 |
XferLOG => $RestoreLOG, |
266 |
XferMethod => $Conf{XferMethod}, |
267 |
logLevel => $Conf{XferLogLevel}, |
268 |
bkupSrcHost => $RestoreReq{hostSrc}, |
269 |
bkupSrcShare => $RestoreReq{shareSrc}, |
270 |
bkupSrcNum => $RestoreReq{num}, |
271 |
backups => \@Backups, |
272 |
pathHdrSrc => $RestoreReq{pathHdrSrc}, |
273 |
pathHdrDest => $RestoreReq{pathHdrDest}, |
274 |
fileList => $RestoreReq{fileList}, |
275 |
pidHandler => \&pidHandler, |
276 |
}; |
277 |
|
278 |
$xfer->args($xferArgs); |
279 |
|
280 |
if ( !defined($logMsg = $xfer->start()) ) { |
281 |
UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); |
282 |
$stat{hostError} = "xfer start failed: ", $xfer->errStr; |
283 |
exit(RestoreCleanup($client)); |
284 |
} |
285 |
|
286 |
if ( $useTar ) { |
287 |
# |
288 |
# Now do the restore by running BackupPC_tarCreate |
289 |
# |
290 |
# The parent must close the read handle since the transport program |
291 |
# is using it. |
292 |
# |
293 |
close(RH); |
294 |
|
295 |
# |
296 |
# fork a child for BackupPC_tarCreate. TAR is a file handle |
297 |
# on which we (the parent) read the stderr from BackupPC_tarCreate. |
298 |
# |
299 |
my @tarPathOpts; |
300 |
if ( defined($RestoreReq{pathHdrDest}) |
301 |
&& $RestoreReq{pathHdrDest} ne $RestoreReq{pathHdrSrc} ) { |
302 |
@tarPathOpts = ("-r", $RestoreReq{pathHdrSrc}, |
303 |
"-p", $RestoreReq{pathHdrDest} |
304 |
); |
305 |
} |
306 |
my @tarArgs = ( |
307 |
"-h", $RestoreReq{hostSrc}, |
308 |
"-n", $RestoreReq{num}, |
309 |
"-s", $RestoreReq{shareSrc}, |
310 |
"-t", |
311 |
@tarPathOpts, |
312 |
@{$RestoreReq{fileList}}, |
313 |
); |
314 |
my $logMsg = "Running: " |
315 |
. $bpc->execCmd2ShellCmd("$BinDir/BackupPC_tarCreate", @tarArgs) |
316 |
. "\n"; |
317 |
$RestoreLOG->write(\$logMsg); |
318 |
if ( !defined($tarPid = open(TAR, "-|")) ) { |
319 |
close(WH); |
320 |
# FIX: need to cleanup xfer |
321 |
UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); |
322 |
$stat{hostError} = "Can't fork to run tar"; |
323 |
exit(RestoreCleanup($client)); |
324 |
} |
325 |
binmode(TAR); |
326 |
if ( !$tarPid ) { |
327 |
# |
328 |
# This is the tarCreate child. Clone STDERR to STDOUT, |
329 |
# STDOUT to WH, and then exec BackupPC_tarCreate. |
330 |
# |
331 |
setpgrp 0,0; |
332 |
close(STDERR); |
333 |
open(STDERR, ">&STDOUT"); |
334 |
close(STDOUT); |
335 |
open(STDOUT, ">&WH"); |
336 |
alarm(0); |
337 |
exec("$BinDir/BackupPC_tarCreate", @tarArgs); |
338 |
print(LOG $bpc->timeStamp, "can't exec $BinDir/BackupPC_tarCreate\n"); |
339 |
# FIX: need to cleanup xfer |
340 |
exit(0); |
341 |
} |
342 |
# |
343 |
# The parent must close the write handle since BackupPC_tarCreate |
344 |
# is using it. |
345 |
# |
346 |
close(WH); |
347 |
|
348 |
@xferPid = $xfer->xferPid; |
349 |
|
350 |
print(LOG $bpc->timeStamp, $logMsg, "\n"); |
351 |
print("started_restore\n"); |
352 |
|
353 |
pidHandler(@xferPid); |
354 |
|
355 |
# |
356 |
# Parse the output of the transfer program and BackupPC_tarCreate |
357 |
# while they run. Since we are reading from two or more children |
358 |
# we use a select. |
359 |
# |
360 |
my($FDread, $tarOut, $mesg); |
361 |
vec($FDread, fileno(TAR), 1) = 1; |
362 |
$xfer->setSelectMask(\$FDread); |
363 |
|
364 |
SCAN: while ( 1 ) { |
365 |
my $ein = $FDread; |
366 |
last if ( $FDread =~ /^\0*$/ ); |
367 |
alarm($Conf{ClientTimeout}); |
368 |
select(my $rout = $FDread, undef, $ein, undef); |
369 |
if ( vec($rout, fileno(TAR), 1) ) { |
370 |
if ( sysread(TAR, $mesg, 8192) <= 0 ) { |
371 |
vec($FDread, fileno(TAR), 1) = 0; |
372 |
if ( !close(TAR) ) { |
373 |
$tarCreateErrCnt = 1; |
374 |
$tarCreateErr = "BackupPC_tarCreate failed"; |
375 |
} |
376 |
} else { |
377 |
$tarOut .= $mesg; |
378 |
} |
379 |
} |
380 |
while ( $tarOut =~ /(.*?)[\n\r]+(.*)/s ) { |
381 |
$_ = $1; |
382 |
$tarOut = $2; |
383 |
$RestoreLOG->write(\"tarCreate: $_\n"); |
384 |
if ( /^Done: (\d+) files, (\d+) bytes, (\d+) dirs, (\d+) specials, (\d+) errors/ ) { |
385 |
$tarCreateFileCnt = $1; |
386 |
$tarCreateByteCnt = $2; |
387 |
$tarCreateErrCnt = $5; |
388 |
} |
389 |
} |
390 |
last if ( !$xfer->readOutput(\$FDread, $rout) ); |
391 |
while ( my $str = $xfer->logMsgGet ) { |
392 |
print(LOG $bpc->timeStamp, "xfer: $str\n"); |
393 |
} |
394 |
if ( $xfer->getStats->{fileCnt} == 1 ) { |
395 |
# |
396 |
# Make sure it is still the machine we expect. We do this while |
397 |
# the transfer is running to avoid a potential race condition if |
398 |
# the ip address was reassigned by dhcp just before we started |
399 |
# the transfer. |
400 |
# |
401 |
if ( my $errMsg = CorrectHostCheck($hostIP, $host) ) { |
402 |
$stat{hostError} = $errMsg; |
403 |
last SCAN; |
404 |
} |
405 |
} |
406 |
} |
407 |
} else { |
408 |
# |
409 |
# otherwise the xfer module does everything for us |
410 |
# |
411 |
print(LOG $bpc->timeStamp, "Starting restore\n"); |
412 |
print("started_restore\n"); |
413 |
($tarCreateFileCnt, $tarCreateByteCnt, |
414 |
$tarCreateErrCnt, $tarCreateErr) = $xfer->run(); |
415 |
} |
416 |
alarm(0); |
417 |
|
418 |
# |
419 |
# Merge the xfer status (need to accumulate counts) |
420 |
# |
421 |
my $newStat = $xfer->getStats; |
422 |
foreach my $k ( (keys(%stat), keys(%$newStat)) ) { |
423 |
next if ( !defined($newStat->{$k}) ); |
424 |
if ( $k =~ /Cnt$/ ) { |
425 |
$stat{$k} += $newStat->{$k}; |
426 |
delete($newStat->{$k}); |
427 |
next; |
428 |
} |
429 |
if ( !defined($stat{$k}) ) { |
430 |
$stat{$k} = $newStat->{$k}; |
431 |
delete($newStat->{$k}); |
432 |
next; |
433 |
} |
434 |
} |
435 |
|
436 |
exit(RestoreCleanup($client)); |
437 |
|
438 |
########################################################################### |
439 |
# Subroutines |
440 |
########################################################################### |
441 |
|
442 |
sub CorrectHostCheck |
443 |
{ |
444 |
my($hostIP, $host) = @_; |
445 |
return if ( $hostIP eq $host && !$Conf{FixedIPNetBiosNameCheck} |
446 |
|| $Conf{NmbLookupCmd} eq "" ); |
447 |
my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($hostIP); |
448 |
return "host $host has mismatching netbios name $netBiosHost" |
449 |
if ( $netBiosHost ne $host ); |
450 |
return; |
451 |
} |
452 |
|
453 |
sub catch_signal |
454 |
{ |
455 |
my $signame = shift; |
456 |
|
457 |
# |
458 |
# Children quit quietly on ALRM |
459 |
# |
460 |
exit(1) if ( $Pid != $$ && $signame eq "ALRM" ); |
461 |
|
462 |
# |
463 |
# Ignore signals in children |
464 |
# |
465 |
return if ( $Pid != $$ ); |
466 |
|
467 |
# |
468 |
# Note: needs to be tested for each kind of XferMethod |
469 |
# |
470 |
print(LOG $bpc->timeStamp, "cleaning up after signal $signame\n"); |
471 |
$SIG{$signame} = 'IGNORE'; |
472 |
$RestoreLOG->write(\"exiting after signal $signame\n"); |
473 |
$stat{xferOK} = 0; |
474 |
if ( $signame eq "INT" ) { |
475 |
$stat{hostError} = "aborted by user (signal=$signame)"; |
476 |
} else { |
477 |
$stat{hostError} = "aborted by signal=$signame"; |
478 |
} |
479 |
exit(RestoreCleanup($client)); |
480 |
} |
481 |
|
482 |
# |
483 |
# Cleanup and update the restore status |
484 |
# |
485 |
sub RestoreCleanup |
486 |
{ |
487 |
my($client) = @_; |
488 |
|
489 |
$stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} |
490 |
|| $tarCreateErr ); |
491 |
|
492 |
if ( !$stat{xferOK} ) { |
493 |
# |
494 |
# kill off the tranfer program, first nicely then forcefully |
495 |
# |
496 |
if ( @xferPid ) { |
497 |
kill($bpc->sigName2num("INT"), @xferPid); |
498 |
sleep(1); |
499 |
kill($bpc->sigName2num("KILL"), @xferPid); |
500 |
} |
501 |
# |
502 |
# kill off the tar process, first nicely then forcefully |
503 |
# |
504 |
if ( $tarPid > 0 ) { |
505 |
kill($bpc->sigName2num("INT"), $tarPid); |
506 |
sleep(1); |
507 |
kill($bpc->sigName2num("KILL"), $tarPid); |
508 |
} |
509 |
} |
510 |
|
511 |
my $lastNum = -1; |
512 |
my @Restores; |
513 |
|
514 |
# |
515 |
# Do one last check to make sure it is still the machine we expect. |
516 |
# |
517 |
if ( $stat{xferOK} && (my $errMsg = CorrectHostCheck($hostIP, $host)) ) { |
518 |
$stat{hostError} = $errMsg; |
519 |
$stat{xferOK} = 0; |
520 |
} |
521 |
@Restores = $bpc->RestoreInfoRead($client); |
522 |
for ( my $i = 0 ; $i < @Restores ; $i++ ) { |
523 |
$lastNum = $Restores[$i]{num} if ( $lastNum < $Restores[$i]{num} ); |
524 |
} |
525 |
$lastNum++; |
526 |
|
527 |
# |
528 |
# Run an optional post-restore command |
529 |
# |
530 |
UserCommandRun("RestorePostUserCmd") if ( $NeedPostCmd ); |
531 |
|
532 |
rename("$Dir/RestoreLOG$fileExt", "$Dir/RestoreLOG.$lastNum$fileExt"); |
533 |
rename("$Dir/$reqFileName", "$Dir/RestoreInfo.$lastNum"); |
534 |
my $endTime = time(); |
535 |
|
536 |
# |
537 |
# If the restore failed, clean up |
538 |
# |
539 |
if ( !$stat{xferOK} ) { |
540 |
# |
541 |
# wait a short while and see if the system is still alive |
542 |
# |
543 |
$stat{hostError} ||= $tarCreateErr if ( $tarCreateErr ne "" ); |
544 |
$stat{hostError} = $stat{lastOutputLine} if ( $stat{hostError} eq "" ); |
545 |
sleep(2); |
546 |
if ( $bpc->CheckHostAlive($hostIP) < 0 ) { |
547 |
$stat{hostAbort} = 1; |
548 |
} |
549 |
if ( $stat{hostAbort} && $stat{hostError} eq "" ) { |
550 |
$stat{hostError} = "lost network connection during restore"; |
551 |
} |
552 |
$RestoreLOG->write(\"Restore failed: $stat{hostError}\n") |
553 |
if ( defined($RestoreLOG) ); |
554 |
} |
555 |
|
556 |
$RestoreLOG->close() if ( defined($RestoreLOG) ); |
557 |
|
558 |
# |
559 |
# Add the new restore information to the restore file |
560 |
# |
561 |
@Restores = $bpc->RestoreInfoRead($client); |
562 |
my $i = @Restores; |
563 |
$Restores[$i]{num} = $lastNum; |
564 |
$Restores[$i]{startTime} = $startTime; |
565 |
$Restores[$i]{endTime} = $endTime; |
566 |
$Restores[$i]{result} = $stat{xferOK} ? "ok" : "failed"; |
567 |
$Restores[$i]{errorMsg} = $stat{hostError}; |
568 |
$Restores[$i]{nFiles} = $tarCreateFileCnt; |
569 |
$Restores[$i]{size} = $tarCreateByteCnt; |
570 |
$Restores[$i]{tarCreateErrs} = $tarCreateErrCnt; |
571 |
$Restores[$i]{xferErrs} = $stat{xferErrCnt} || 0; |
572 |
|
573 |
while ( @Restores > $Conf{RestoreInfoKeepCnt} ) { |
574 |
my $num = $Restores[0]{num}; |
575 |
unlink("$Dir/RestoreLOG.$num.z"); |
576 |
unlink("$Dir/RestoreLOG.$num"); |
577 |
unlink("$Dir/RestoreInfo.$num"); |
578 |
shift(@Restores); |
579 |
} |
580 |
$bpc->RestoreInfoWrite($client, @Restores); |
581 |
|
582 |
if ( !$stat{xferOK} ) { |
583 |
print(LOG $bpc->timeStamp, "Restore failed ($stat{hostError})\n"); |
584 |
print("restore failed: $stat{hostError}\n"); |
585 |
return 1; |
586 |
} else { |
587 |
print("restore complete\n"); |
588 |
return; |
589 |
} |
590 |
} |
591 |
|
592 |
# |
593 |
# The Xfer method might tell us from time to time about processes |
594 |
# it forks. We tell BackupPC about this (for status displays) and |
595 |
# keep track of the pids in case we cancel the backup |
596 |
# |
597 |
sub pidHandler |
598 |
{ |
599 |
@xferPid = @_; |
600 |
@xferPid = grep(/./, @xferPid); |
601 |
return if ( !@xferPid && $tarPid < 0 ); |
602 |
my @pids = @xferPid; |
603 |
push(@pids, $tarPid) if ( $tarPid > 0 ); |
604 |
my $str = join(",", @pids); |
605 |
$RestoreLOG->write(\"Xfer PIDs are now $str\n") if ( defined($RestoreLOG) ); |
606 |
print("xferPids $str\n"); |
607 |
} |
608 |
|
609 |
# |
610 |
# Run an optional pre- or post-dump command |
611 |
# |
612 |
sub UserCommandRun |
613 |
{ |
614 |
my($cmdType) = @_; |
615 |
|
616 |
return if ( !defined($Conf{$cmdType}) ); |
617 |
my $vars = { |
618 |
xfer => $xfer, |
619 |
client => $client, |
620 |
host => $host, |
621 |
hostIP => $hostIP, |
622 |
share => $RestoreReq{shareDest}, |
623 |
XferMethod => $Conf{XferMethod}, |
624 |
sshPath => $Conf{SshPath}, |
625 |
LOG => *LOG, |
626 |
user => $Hosts->{$client}{user}, |
627 |
moreUsers => $Hosts->{$client}{moreUsers}, |
628 |
XferLOG => $RestoreLOG, |
629 |
stat => \%stat, |
630 |
xferOK => $stat{xferOK} || 0, |
631 |
hostError => $stat{hostError}, |
632 |
type => "restore", |
633 |
bkupSrcHost => $RestoreReq{hostSrc}, |
634 |
bkupSrcShare => $RestoreReq{shareSrc}, |
635 |
bkupSrcNum => $RestoreReq{num}, |
636 |
backups => \@Backups, |
637 |
pathHdrSrc => $RestoreReq{pathHdrSrc}, |
638 |
pathHdrDest => $RestoreReq{pathHdrDest}, |
639 |
fileList => $RestoreReq{fileList}, |
640 |
cmdType => $cmdType, |
641 |
}; |
642 |
my $cmd = $bpc->cmdVarSubstitute($Conf{$cmdType}, $vars); |
643 |
$RestoreLOG->write(\"Executing $cmdType: @$cmd\n"); |
644 |
# |
645 |
# Run the user's command, dumping the stdout/stderr into the |
646 |
# Xfer log file. Also supply the optional $vars and %Conf in |
647 |
# case the command is really perl code instead of a shell |
648 |
# command. |
649 |
# |
650 |
$bpc->cmdSystemOrEval($cmd, |
651 |
sub { |
652 |
$RestoreLOG->write(\$_[0]); |
653 |
}, |
654 |
$vars, \%Conf); |
655 |
} |